aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-17 09:35:19 +0000
committer_ov4 <ov4@ov4.org>2026-10-17 09:35:19 +0000
commit6b332f91c82da6e19efad92b58ac1d9c1bc9eec0 (patch)
treea553b6aab0589dd7cafe5b2508cb274ac0ae9e98
parentb25cbba212354e44921d23490765782f61787bc8 (diff)
feat(runtime): write runtime diagnostics to stderr
Runtime check messages and the unhandled-exception message were printed to stdout by the generated program, so `cpsc x.cps -r >out.txt` captured them. They are now formatted with snprintf and written with write(2). Test expectations and docs are updated accordingly. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH.md4
-rw-r--r--AGENTS/CONVENTIONS.md4
-rw-r--r--AGENTS/EXTENDING.md2
-rw-r--r--AGENTS/PITFALLS.md2
-rw-r--r--AGENTS/SUBSYSTEMS.md2
-rw-r--r--doc/cpsc.1.md2
-rw-r--r--doc/language/function/cpsc-function-free.7.md2
-rw-r--r--doc/language/function/cpsc-function-malloc.7.md2
-rw-r--r--doc/language/function/cpsc-function-realloc.7.md2
-rw-r--r--doc/language/keyword/cpsc-keyword-try.7.md2
-rw-r--r--include/cps/RuntimeCheck.h10
-rw-r--r--lib/CodeGen/CodeGen.cc2
-rw-r--r--lib/CodeGen/FunctionGen.cc2
-rw-r--r--lib/CodeGen/RuntimeCheck.cc28
-rw-r--r--tests/i/caught-errors/expected-stderr.txt4
-rw-r--r--tests/i/caught-errors/expected.txt4
-rw-r--r--tests/i/class-delete-designators/expected-stderr.txt1
-rw-r--r--tests/i/class-delete-designators/expected.txt1
-rw-r--r--tests/i/class-delete/expected-stderr.txt2
-rw-r--r--tests/i/class-delete/expected.txt2
-rw-r--r--tests/i/class-unwind/expected-stderr.txt1
-rw-r--r--tests/i/class-unwind/expected.txt1
-rw-r--r--tests/i/enum-range/expected-stderr.txt1
-rw-r--r--tests/i/enum-range/expected.txt1
-rw-r--r--tests/i/function-call-statements/expected-stderr.txt1
-rw-r--r--tests/i/function-call-statements/expected.txt1
-rw-r--r--tests/i/input-targets/expected-stderr.txt2
-rw-r--r--tests/i/input-targets/expected.txt2
-rw-r--r--tests/i/memory-allocation/expected-stderr.txt4
-rw-r--r--tests/i/memory-allocation/expected.txt4
-rw-r--r--tests/i/memory-reallocation/expected-stderr.txt6
-rw-r--r--tests/i/memory-reallocation/expected.txt6
-rw-r--r--tests/i/memory-uncaught/expected-stderr.txt2
-rw-r--r--tests/i/memory-uncaught/expected.txt2
-rw-r--r--tests/i/multifile-runtime-catch/expected-stderr.txt1
-rw-r--r--tests/i/multifile-runtime-catch/expected.txt1
-rw-r--r--tests/i/multifile-uncaught/expected-stderr.txt1
-rw-r--r--tests/i/multifile-uncaught/expected.txt1
-rw-r--r--tests/i/signed-zero-division/expected-stderr.txt1
-rw-r--r--tests/i/signed-zero-division/expected.txt1
-rw-r--r--tests/i/uncaught-error/expected-stderr.txt2
-rw-r--r--tests/i/uncaught-error/expected.txt2
42 files changed, 74 insertions, 50 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index aef89ed..5ddddc1 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -75,9 +75,9 @@ Generated IR uses the C++ exception ABI; native compilation links its runtime
automatically. Manually linking emitted `.ll` files still requires a C++ driver
(use LLVM 23's `clang++`, or `clang++-23` on Debian/Ubuntu).
TRY/CATCH/ENDTRY and bare THROW are compiler extensions.
-Runtime checks print the source line and error reason to stdout before throwing
+Runtime checks print the source line and error reason to stderr before throwing
anonymous exceptions; the final handler in generated main
-prints `Unhandled exception. Program terminated.` to stdout and returns 1.
+prints `Unhandled exception. Program terminated.` to stderr and returns 1.
Exception execution with the embedded ORC JIT is verified on Linux with LLVM 23;
Windows/macOS await CI verification.
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index 2686760..f912e79 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -176,9 +176,9 @@ Its handler stack and catch depth are keyed by LLVM function, so synthesizing fi
helpers never inherits the caller's catches. User calls, file runtime calls and
THROW must use `ExceptionHandler::emitCall`; ordinary nonthrowing libc calls stay
plain calls. RETURN must finish all active catches after evaluating its value.
-`RuntimeCheck::emitCheck` prints the source line and error reason to stdout before
+`RuntimeCheck::emitCheck` prints the source line and error reason to stderr before
throwing. Generated main catches anything unhandled, prints the generic termination
-message to stdout, and returns 1. Explicit bare THROW prints no diagnostic itself.
+message to stderr, and returns 1. Explicit bare THROW prints no diagnostic itself.
Arrays in TRY and CATCH use separate block scopes; scalars retain existing function scope.
**Compilation units and entry points.** SourceUnit borrows its AST list; it and the
diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md
index b3f481a..3161407 100644
--- a/AGENTS/EXTENDING.md
+++ b/AGENTS/EXTENDING.md
@@ -62,7 +62,7 @@ collapse to a legacy node.
**New runtime check:** add `emitXxxCheck` in `RuntimeCheck` (build an i1
fail-condition and call `emitCheck` with a diagnostic format and arguments — it
splits the block and leaves the builder at the continuation), then call it from
-the emit site. Failure prints the source line and error reason to stdout before
+the emit site. Failure prints the source line and error reason to stderr before
throwing an anonymous exception; generated main supplies the uncaught message
and status 1. Precedents: `emitIndexCheck`, `emitEnumRangeCheck`, `emitNullDerefCheck`,
`emitArrayArgBoundsCheck` and FileHandler's direct `emitCheck` calls.
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index d811ea4..951a2f6 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -121,7 +121,7 @@ test programs.
ENDTRY. Bare THROW raises a new anonymous exception even inside CATCH; there is
no rethrow syntax, error object or what(). Runtime checks are catchable;
compiler errors are not. Runtime checks print the source line and error reason
- to stdout before throwing, even when caught. Uncaught exceptions also print the
+ to stderr before throwing, even when caught. Uncaught exceptions also print the
generic termination message and return 1. Bare THROW prints no diagnostic itself.
Native binaries must link the C++ runtime with LLVM 23's `clang++`
(`clang++-23` on Debian/Ubuntu).
diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md
index d21dfe8..bb59ebf 100644
--- a/AGENTS/SUBSYSTEMS.md
+++ b/AGENTS/SUBSYSTEMS.md
@@ -114,7 +114,7 @@ dereferences and array-parameter bounds contracts are checked at their emit site
`ArithmeticHandler` calls `emitDivZeroCheck` before `/`, DIV and MOD; the check accepts
integer and floating-point divisors, including both floating-point signed zeros.
`emitCheck(Cond, Msg, Args)` splits the block, prints the source line and error
-reason to stdout, and calls `ExceptionHandler::emitThrow` on failure. File helper
+reason to stderr, and calls `ExceptionHandler::emitThrow` on failure. File helper
checks receive the source line as a runtime argument.
**Exceptions** (`ExceptionHandler`): holds Context/Builder/Module refs and a map of
diff --git a/doc/cpsc.1.md b/doc/cpsc.1.md
index 2bb1f1c..5b1369d 100644
--- a/doc/cpsc.1.md
+++ b/doc/cpsc.1.md
@@ -112,7 +112,7 @@ An uncaught program exception returns 1 after printing `Unhandled exception. Pro
# NOTES
-Compiler diagnostics go to stderr. With multiple input files, lexer, parser and code generation diagnostics include the source path after the line number. Failed source compilation does not write output; failed native linking does not replace an existing output file. Successful compilation overwrites an existing output file, but an output path that refers to any input source file (including a symbolic or hard link) is rejected.
+Compiler diagnostics go to stderr. With multiple input files, lexer, parser and code generation diagnostics include the source path after the line number. Failed source compilation does not write output; failed native linking does not replace an existing output file. Successful compilation overwrites an existing output file, but an output path that refers to any input source file (including a symbolic or hard link) is rejected. Runtime diagnostics of the compiled program (for example `[Fatal] line N: ...` and `Unhandled exception. Program terminated.`) are also written to stderr, so `OUTPUT` text alone appears on stdout.
Missing input, including invoking `cpsc` without arguments, reports `cpsc: fatal error: no input files`. Ordinary lexical, syntax and semantic errors use `error:`; failures that prevent compilation, such as file I/O errors, use `fatal error:`. Compilation and argument failures end with one `compilation terminated.` line. Execution-stage failures do not print that line.
diff --git a/doc/language/function/cpsc-function-free.7.md b/doc/language/function/cpsc-function-free.7.md
index 61a3558..366b317 100644
--- a/doc/language/function/cpsc-function-free.7.md
+++ b/doc/language/function/cpsc-function-free.7.md
@@ -12,7 +12,7 @@ FREE(pointer)
# DESCRIPTION
-FREE is a compiler extension. Its argument and return type are C `void*`. Explicitly cast other pointer types to a `void*` alias before passing them. A null pointer is accepted and returned. A non-null pointer must be the start of an active explicit MALLOC, REALLOC or keyword NEW allocation; internal STRING/ARRAY storage, stack addresses, interior addresses and already released allocations are rejected with a source-line diagnostic on stdout and an anonymous exception.
+FREE is a compiler extension. Its argument and return type are C `void*`. Explicitly cast other pointer types to a `void*` alias before passing them. A null pointer is accepted and returned. A non-null pointer must be the start of an active explicit MALLOC, REALLOC or keyword NEW allocation; internal STRING/ARRAY storage, stack addresses, interior addresses and already released allocations are rejected with a source-line diagnostic on stderr and an anonymous exception.
FREE returns its input address after releasing the allocation. It does not clear the caller's variable. The returned address and all aliases are invalid for memory access. Calling FREE again on that address throws unless a subsequent allocation has reused the address. Tracking cannot distinguish stale aliases once an address is reused.
diff --git a/doc/language/function/cpsc-function-malloc.7.md b/doc/language/function/cpsc-function-malloc.7.md
index 909903a..c0ca1a6 100644
--- a/doc/language/function/cpsc-function-malloc.7.md
+++ b/doc/language/function/cpsc-function-malloc.7.md
@@ -12,7 +12,7 @@ MALLOC(bytes)
# DESCRIPTION
-MALLOC is a compiler extension returning a C `void*` to the beginning of an uninitialized allocation. The size is in bytes and accepts INTEGER or a C integer, converted to the host size_t using C integer conversion rules. Negative values are not separately rejected. MALLOC(0) returns null without allocating. A nonzero allocation failure prints the source line and reason to stdout, then throws an anonymous exception catchable with TRY/CATCH. Allocation tracking failures behave the same way.
+MALLOC is a compiler extension returning a C `void*` to the beginning of an uninitialized allocation. The size is in bytes and accepts INTEGER or a C integer, converted to the host size_t using C integer conversion rules. Negative values are not separately rejected. MALLOC(0) returns null without allocating. A nonzero allocation failure prints the source line and reason to stderr, then throws an anonymous exception catchable with TRY/CATCH. Allocation tracking failures behave the same way.
Declare a `void*` alias with C_TYPE and explicitly cast the result to a C or pseudocode pointer before dereferencing. MALLOC does not initialize fields or call constructors; it is unrelated to the CIE class NEW construct. The caller must supply enough bytes and initialize values before reading them. User memory is not automatically released.
diff --git a/doc/language/function/cpsc-function-realloc.7.md b/doc/language/function/cpsc-function-realloc.7.md
index 19fcfdf..dc2d61f 100644
--- a/doc/language/function/cpsc-function-realloc.7.md
+++ b/doc/language/function/cpsc-function-realloc.7.md
@@ -14,7 +14,7 @@ REALLOC(pointer, bytes)
REALLOC is a compiler extension returning C `void*`. The pointer argument must also be C `void*`; explicitly cast other pointer types. The size accepts INTEGER or a C integer and converts to host size_t, just as MALLOC does.
-A null pointer behaves as MALLOC(bytes). A non-null pointer must start an active explicit MALLOC or REALLOC allocation, even when bytes is zero. Invalid addresses print the source line and reason to stdout and throw an anonymous exception before touching the memory or allocation record.
+A null pointer behaves as MALLOC(bytes). A non-null pointer must start an active explicit MALLOC or REALLOC allocation, even when bytes is zero. Invalid addresses print the source line and reason to stderr and throw an anonymous exception before touching the memory or allocation record.
For a valid pointer and zero bytes, REALLOC releases the block and returns null. This rule is independent of libc's zero-size behavior. For a nonzero size, successful resizing preserves the smaller of the old and new sizes; added bytes are uninitialized. The returned address may differ from the input. Use the returned pointer afterward; previous aliases must not be used.
diff --git a/doc/language/keyword/cpsc-keyword-try.7.md b/doc/language/keyword/cpsc-keyword-try.7.md
index 8ebe89d..eea0510 100644
--- a/doc/language/keyword/cpsc-keyword-try.7.md
+++ b/doc/language/keyword/cpsc-keyword-try.7.md
@@ -24,7 +24,7 @@ Normal execution skips CATCH. An exception stops the current operation, unwinds
THROW takes no argument and always raises a new anonymous exception. An exception raised inside CATCH propagates to an enclosing TRY or a caller's handler; the same CATCH cannot catch it again. RETURN may leave either body, and leaving a handler finishes its catch state.
-Runtime checks throw the same anonymous exceptions for division by zero, array index errors, null pointer dereferences, invalid enumeration values, array-parameter bounds mismatches, and file misuse. Before throwing, a runtime check prints the source line and error reason to standard output, even when CATCH handles the exception. Explicit THROW prints no diagnostic itself. Compiler errors cannot be caught. If no handler catches an exception, the program prints `Unhandled exception. Program terminated.` to standard output and returns status 1.
+Runtime checks throw the same anonymous exceptions for division by zero, array index errors, null pointer dereferences, invalid enumeration values, array-parameter bounds mismatches, and file misuse. Before throwing, a runtime check prints the source line and error reason to standard error, even when CATCH handles the exception. Explicit THROW prints no diagnostic itself. Compiler errors cannot be caught. If no handler catches an exception, the program prints `Unhandled exception. Program terminated.` to standard error and returns status 1.
Arrays declared inside either body remain local to that block. Stack unwinding does not automatically free heap strings or arrays, close files, or run user-defined destructors. Exception execution is verified on Linux with LLVM 23; Windows and macOS remain unverified.
diff --git a/include/cps/RuntimeCheck.h b/include/cps/RuntimeCheck.h
index f5a8aa7..8f33e99 100644
--- a/include/cps/RuntimeCheck.h
+++ b/include/cps/RuntimeCheck.h
@@ -14,7 +14,10 @@ class RuntimeCheck {
llvm::LLVMContext &TheContext;
llvm::IRBuilder<> &Builder;
- llvm::FunctionCallee PrintfFunc;
+ llvm::FunctionCallee SnprintfFunc;
+ llvm::FunctionCallee WriteFunc;
+ llvm::FunctionCallee StrLenFunc;
+ llvm::GlobalVariable *ErrBuf;
ExceptionHandler &Exceptions;
llvm::Value *DivZeroMsg;
llvm::Value *OutOfBoundsMsg;
@@ -29,6 +32,9 @@ public:
RuntimeCheck(llvm::Module &M, llvm::LLVMContext &C, llvm::IRBuilder<> &B,
ExceptionHandler &EH);
+ // Format a runtime diagnostic (printf-style) and write it to stderr.
+ void emitStderr(llvm::Value *Msg, llvm::ArrayRef<llvm::Value *> FmtArgs = {});
+
void emitDivZeroCheck(llvm::Value *Divisor, int Line);
void emitIndexCheck(llvm::Value *Index, llvm::Value *Lower, llvm::Value *Upper, int Line);
@@ -44,7 +50,7 @@ public:
void emitArrayArgBoundsCheck(llvm::Value *ActualLower, llvm::Value *ActualUpper,
int64_t DeclaredLower, int64_t DeclaredUpper, int Line);
- // Print the diagnostic, then throw an anonymous exception on failure.
+ // Print the diagnostic to stderr, then throw an anonymous exception on failure.
// Runtime-computed variant: FmtArgs are the printf varargs after Msg
// (e.g. {i32 line, ptr filename}). Needed when the failure site is inside
// a synthesized helper function, where the line number is an argument
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index eba7d77..b81fe2b 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -340,7 +340,7 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
Exceptions->endCatch();
Value *Msg = Builder->CreateGlobalString("Unhandled exception. Program terminated.\n",
"err_unhandled", 0, TheModule.get());
- Builder->CreateCall(PrintfFunc, {Msg});
+ RuntimeChecker->emitStderr(Msg);
Builder->CreateRet(ConstantInt::get(Type::getInt32Ty(*TheContext), 1));
if (verifyModule(*TheModule, &errs())) {
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index 47971cf..4e70faf 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -14,7 +14,7 @@ static bool isReservedRuntimeName(const std::string &Name) {
static const char *Reserved[] = {
"__cps_main", "printf", "scanf", "malloc", "free", "strlen", "memcpy",
"strcpy", "strcat", "toupper", "tolower", "sprintf", "snprintf", "strtol", "strtoll",
- "strtod", "memset", "exit",
+ "strtod", "memset", "exit", "write",
"fopen", "fclose", "fgetc", "ungetc", "fputs", "fputc",
"realloc", "strcmp", "__cxa_allocate_exception", "__cxa_throw",
"__cxa_begin_catch", "__cxa_end_catch", "__gxx_personality_v0",
diff --git a/lib/CodeGen/RuntimeCheck.cc b/lib/CodeGen/RuntimeCheck.cc
index 215702e..b1eb677 100644
--- a/lib/CodeGen/RuntimeCheck.cc
+++ b/lib/CodeGen/RuntimeCheck.cc
@@ -3,6 +3,7 @@
#include "cps/TypeSystem.h" // kEnumOrdinalBase
#include "llvm/IR/Function.h"
#include "llvm/IR/Type.h"
+#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Constants.h"
using namespace llvm;
@@ -14,10 +15,18 @@ RuntimeCheck::RuntimeCheck(Module &M, LLVMContext &C, IRBuilder<> &B, ExceptionH
}
void RuntimeCheck::setupExternalFunctions() {
- std::vector<Type*> PrintfArgs;
- PrintfArgs.push_back(PointerType::getUnqual(TheContext));
- FunctionType *PrintfType = FunctionType::get(Type::getInt32Ty(TheContext), PrintfArgs, true);
- PrintfFunc = TheModule.getOrInsertFunction("printf", PrintfType);
+ Type *PtrTy = PointerType::getUnqual(TheContext);
+ Type *I32 = Type::getInt32Ty(TheContext);
+ Type *I64 = Type::getInt64Ty(TheContext);
+ SnprintfFunc = TheModule.getOrInsertFunction(
+ "snprintf", FunctionType::get(I32, {PtrTy, I64, PtrTy}, true));
+ WriteFunc = TheModule.getOrInsertFunction(
+ "write", FunctionType::get(I32, {I32, PtrTy, I64}, false));
+ StrLenFunc = TheModule.getOrInsertFunction(
+ "strlen", FunctionType::get(I64, {PtrTy}, false));
+ ArrayType *BufTy = ArrayType::get(Type::getInt8Ty(TheContext), 1024);
+ ErrBuf = new GlobalVariable(TheModule, BufTy, false, GlobalValue::InternalLinkage,
+ ConstantAggregateZero::get(BufTy), "__cps_errbuf");
DivZeroMsg = Builder.CreateGlobalString("[Fatal] line %d: Division by zero\n", "err_div_zero", 0, &TheModule);
OutOfBoundsMsg = Builder.CreateGlobalString("[Fatal] line %d: Array index out of bounds\n", "err_bounds", 0, &TheModule);
@@ -28,6 +37,15 @@ void RuntimeCheck::setupExternalFunctions() {
"err_arr_arg_bounds", 0, &TheModule);
}
+void RuntimeCheck::emitStderr(Value *Msg, ArrayRef<Value *> FmtArgs) {
+ // Format into a fixed buffer, then write(2) so diagnostics bypass stdout.
+ std::vector<Value *> Args = {ErrBuf, ConstantInt::get(Type::getInt64Ty(TheContext), 1024), Msg};
+ Args.insert(Args.end(), FmtArgs.begin(), FmtArgs.end());
+ Builder.CreateCall(SnprintfFunc, Args);
+ Value *Len = Builder.CreateCall(StrLenFunc, {ErrBuf});
+ Builder.CreateCall(WriteFunc, {ConstantInt::get(Type::getInt32Ty(TheContext), 2), ErrBuf, Len});
+}
+
void RuntimeCheck::emitCheck(Value *Condition, Value *Msg, int Line) {
emitCheck(Condition, Msg, {ConstantInt::get(TheContext, APInt(32, Line))});
}
@@ -44,7 +62,7 @@ void RuntimeCheck::emitCheck(Value *Condition, Value *Msg, ArrayRef<Value *> Fmt
std::vector<Value*> PrintArgs;
PrintArgs.push_back(Msg);
PrintArgs.insert(PrintArgs.end(), FmtArgs.begin(), FmtArgs.end());
- Builder.CreateCall(PrintfFunc, PrintArgs);
+ emitStderr(Msg, ArrayRef<Value *>(PrintArgs).drop_front());
Exceptions.emitThrow();
diff --git a/tests/i/caught-errors/expected-stderr.txt b/tests/i/caught-errors/expected-stderr.txt
new file mode 100644
index 0000000..7c5bfd1
--- /dev/null
+++ b/tests/i/caught-errors/expected-stderr.txt
@@ -0,0 +1,4 @@
+[Fatal] line 7: Division by zero
+[Fatal] line 12: Array index out of bounds
+[Fatal] line 17: Dereference of an unset pointer
+[Fatal] line 22: File 'closed.txt' is not open
diff --git a/tests/i/caught-errors/expected.txt b/tests/i/caught-errors/expected.txt
index ae32ca6..6a677de 100644
--- a/tests/i/caught-errors/expected.txt
+++ b/tests/i/caught-errors/expected.txt
@@ -1,10 +1,6 @@
-[Fatal] line 7: Division by zero
division caught
-[Fatal] line 12: Array index out of bounds
bounds caught
-[Fatal] line 17: Dereference of an unset pointer
pointer caught
-[Fatal] line 22: File 'closed.txt' is not open
file caught
throw caught
alive
diff --git a/tests/i/class-delete-designators/expected-stderr.txt b/tests/i/class-delete-designators/expected-stderr.txt
new file mode 100644
index 0000000..268ec7f
--- /dev/null
+++ b/tests/i/class-delete-designators/expected-stderr.txt
@@ -0,0 +1 @@
+[Fatal] line 27: Invalid memory address (not an active allocation)
diff --git a/tests/i/class-delete-designators/expected.txt b/tests/i/class-delete-designators/expected.txt
index 3a62de6..b63409e 100644
--- a/tests/i/class-delete-designators/expected.txt
+++ b/tests/i/class-delete-designators/expected.txt
@@ -1,7 +1,6 @@
item:1
item:1
item:2
-[Fatal] line 27: Invalid memory address (not an active allocation)
array caught
item:3
item:0
diff --git a/tests/i/class-delete/expected-stderr.txt b/tests/i/class-delete/expected-stderr.txt
new file mode 100644
index 0000000..bbd6b69
--- /dev/null
+++ b/tests/i/class-delete/expected-stderr.txt
@@ -0,0 +1,2 @@
+[Fatal] line 24: Invalid memory address (not an active allocation)
+[Fatal] line 30: Invalid memory address (not an active allocation)
diff --git a/tests/i/class-delete/expected.txt b/tests/i/class-delete/expected.txt
index c7e7246..bb2c73d 100644
--- a/tests/i/class-delete/expected.txt
+++ b/tests/i/class-delete/expected.txt
@@ -3,9 +3,7 @@ pair
pair
leaf:2
leaf:1
-[Fatal] line 24: Invalid memory address (not an active allocation)
double caught
-[Fatal] line 30: Invalid memory address (not an active allocation)
stack caught
leaf:2
pair
diff --git a/tests/i/class-unwind/expected-stderr.txt b/tests/i/class-unwind/expected-stderr.txt
new file mode 100644
index 0000000..e9431ec
--- /dev/null
+++ b/tests/i/class-unwind/expected-stderr.txt
@@ -0,0 +1 @@
+Unhandled exception. Program terminated.
diff --git a/tests/i/class-unwind/expected.txt b/tests/i/class-unwind/expected.txt
index 3503cb5..e563d2c 100644
--- a/tests/i/class-unwind/expected.txt
+++ b/tests/i/class-unwind/expected.txt
@@ -11,4 +11,3 @@ uncaught next
destroy:2
destroy:1
destroy:3
-Unhandled exception. Program terminated.
diff --git a/tests/i/enum-range/expected-stderr.txt b/tests/i/enum-range/expected-stderr.txt
new file mode 100644
index 0000000..b0c5ea2
--- /dev/null
+++ b/tests/i/enum-range/expected-stderr.txt
@@ -0,0 +1 @@
+[Fatal] line 5: Value out of range for enum Season
diff --git a/tests/i/enum-range/expected.txt b/tests/i/enum-range/expected.txt
index f91d556..e6e3e04 100644
--- a/tests/i/enum-range/expected.txt
+++ b/tests/i/enum-range/expected.txt
@@ -1,3 +1,2 @@
-[Fatal] line 5: Value out of range for enum Season
enum caught
TRUE
diff --git a/tests/i/function-call-statements/expected-stderr.txt b/tests/i/function-call-statements/expected-stderr.txt
new file mode 100644
index 0000000..1741571
--- /dev/null
+++ b/tests/i/function-call-statements/expected-stderr.txt
@@ -0,0 +1 @@
+[Fatal] line 33: Division by zero
diff --git a/tests/i/function-call-statements/expected.txt b/tests/i/function-call-statements/expected.txt
index d2576ac..b35d820 100644
--- a/tests/i/function-call-statements/expected.txt
+++ b/tests/i/function-call-statements/expected.txt
@@ -2,5 +2,4 @@
23
7
19
-[Fatal] line 33: Division by zero
caught
diff --git a/tests/i/input-targets/expected-stderr.txt b/tests/i/input-targets/expected-stderr.txt
new file mode 100644
index 0000000..a53ae1f
--- /dev/null
+++ b/tests/i/input-targets/expected-stderr.txt
@@ -0,0 +1,2 @@
+[Fatal] line 45: Array index out of bounds
+[Fatal] line 51: Dereference of an unset pointer
diff --git a/tests/i/input-targets/expected.txt b/tests/i/input-targets/expected.txt
index 75640cd..ba27972 100644
--- a/tests/i/input-targets/expected.txt
+++ b/tests/i/input-targets/expected.txt
@@ -4,8 +4,6 @@ TRUE:-7
60:70
index evaluated
90
-[Fatal] line 45: Array index out of bounds
bounds caught
-[Fatal] line 51: Dereference of an unset pointer
pointer caught
100
diff --git a/tests/i/memory-allocation/expected-stderr.txt b/tests/i/memory-allocation/expected-stderr.txt
new file mode 100644
index 0000000..72d9e66
--- /dev/null
+++ b/tests/i/memory-allocation/expected-stderr.txt
@@ -0,0 +1,4 @@
+[Fatal] line 34: Invalid memory address (not an active allocation)
+[Fatal] line 40: Invalid memory address (not an active allocation)
+[Fatal] line 48: Invalid memory address (not an active allocation)
+[Fatal] line 54: Memory allocation failed
diff --git a/tests/i/memory-allocation/expected.txt b/tests/i/memory-allocation/expected.txt
index 330c9ba..e007979 100644
--- a/tests/i/memory-allocation/expected.txt
+++ b/tests/i/memory-allocation/expected.txt
@@ -3,12 +3,8 @@ TRUE
42
11:22
TRUE
-[Fatal] line 34: Invalid memory address (not an active allocation)
double free caught
-[Fatal] line 40: Invalid memory address (not an active allocation)
stack address caught
-[Fatal] line 48: Invalid memory address (not an active allocation)
interior address caught
-[Fatal] line 54: Memory allocation failed
allocation failure caught
99
diff --git a/tests/i/memory-reallocation/expected-stderr.txt b/tests/i/memory-reallocation/expected-stderr.txt
new file mode 100644
index 0000000..5478518
--- /dev/null
+++ b/tests/i/memory-reallocation/expected-stderr.txt
@@ -0,0 +1,6 @@
+[Fatal] line 28: Memory allocation failed
+[Fatal] line 40: Invalid memory address (not an active allocation)
+[Fatal] line 47: Invalid memory address (not an active allocation)
+[Fatal] line 63: Invalid memory address (not an active allocation)
+[Fatal] line 69: Invalid memory address (not an active allocation)
+[Fatal] line 77: Invalid memory address (not an active allocation)
diff --git a/tests/i/memory-reallocation/expected.txt b/tests/i/memory-reallocation/expected.txt
index 9e9ccc9..bb43f4f 100644
--- a/tests/i/memory-reallocation/expected.txt
+++ b/tests/i/memory-reallocation/expected.txt
@@ -1,17 +1,11 @@
TRUE
12:34
12
-[Fatal] line 28: Memory allocation failed
preserved:12
56
TRUE
-[Fatal] line 40: Invalid memory address (not an active allocation)
released address caught
-[Fatal] line 47: Invalid memory address (not an active allocation)
invalid zero resize caught
71
-[Fatal] line 63: Invalid memory address (not an active allocation)
-[Fatal] line 69: Invalid memory address (not an active allocation)
nested catch
-[Fatal] line 77: Invalid memory address (not an active allocation)
internal buffer caught
diff --git a/tests/i/memory-uncaught/expected-stderr.txt b/tests/i/memory-uncaught/expected-stderr.txt
new file mode 100644
index 0000000..448a57b
--- /dev/null
+++ b/tests/i/memory-uncaught/expected-stderr.txt
@@ -0,0 +1,2 @@
+[Fatal] line 5: Invalid memory address (not an active allocation)
+Unhandled exception. Program terminated.
diff --git a/tests/i/memory-uncaught/expected.txt b/tests/i/memory-uncaught/expected.txt
index 448a57b..e69de29 100644
--- a/tests/i/memory-uncaught/expected.txt
+++ b/tests/i/memory-uncaught/expected.txt
@@ -1,2 +0,0 @@
-[Fatal] line 5: Invalid memory address (not an active allocation)
-Unhandled exception. Program terminated.
diff --git a/tests/i/multifile-runtime-catch/expected-stderr.txt b/tests/i/multifile-runtime-catch/expected-stderr.txt
new file mode 100644
index 0000000..833ca4b
--- /dev/null
+++ b/tests/i/multifile-runtime-catch/expected-stderr.txt
@@ -0,0 +1 @@
+[Fatal] line 2: Division by zero
diff --git a/tests/i/multifile-runtime-catch/expected.txt b/tests/i/multifile-runtime-catch/expected.txt
index 731df7f..6a0b8ed 100644
--- a/tests/i/multifile-runtime-catch/expected.txt
+++ b/tests/i/multifile-runtime-catch/expected.txt
@@ -1,3 +1,2 @@
-[Fatal] line 2: Division by zero
runtime caught
5
diff --git a/tests/i/multifile-uncaught/expected-stderr.txt b/tests/i/multifile-uncaught/expected-stderr.txt
new file mode 100644
index 0000000..e9431ec
--- /dev/null
+++ b/tests/i/multifile-uncaught/expected-stderr.txt
@@ -0,0 +1 @@
+Unhandled exception. Program terminated.
diff --git a/tests/i/multifile-uncaught/expected.txt b/tests/i/multifile-uncaught/expected.txt
index e9431ec..e69de29 100644
--- a/tests/i/multifile-uncaught/expected.txt
+++ b/tests/i/multifile-uncaught/expected.txt
@@ -1 +0,0 @@
-Unhandled exception. Program terminated.
diff --git a/tests/i/signed-zero-division/expected-stderr.txt b/tests/i/signed-zero-division/expected-stderr.txt
new file mode 100644
index 0000000..77a3113
--- /dev/null
+++ b/tests/i/signed-zero-division/expected-stderr.txt
@@ -0,0 +1 @@
+[Fatal] line 4: Division by zero
diff --git a/tests/i/signed-zero-division/expected.txt b/tests/i/signed-zero-division/expected.txt
index 0376278..a0e6b3f 100644
--- a/tests/i/signed-zero-division/expected.txt
+++ b/tests/i/signed-zero-division/expected.txt
@@ -1,2 +1 @@
-[Fatal] line 4: Division by zero
zero caught
diff --git a/tests/i/uncaught-error/expected-stderr.txt b/tests/i/uncaught-error/expected-stderr.txt
new file mode 100644
index 0000000..8b66756
--- /dev/null
+++ b/tests/i/uncaught-error/expected-stderr.txt
@@ -0,0 +1,2 @@
+[Fatal] line 3: Division by zero
+Unhandled exception. Program terminated.
diff --git a/tests/i/uncaught-error/expected.txt b/tests/i/uncaught-error/expected.txt
index 8b66756..e69de29 100644
--- a/tests/i/uncaught-error/expected.txt
+++ b/tests/i/uncaught-error/expected.txt
@@ -1,2 +0,0 @@
-[Fatal] line 3: Division by zero
-Unhandled exception. Program terminated.