From a801b55f9fdaae8d41dc43151513f3949208142c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 07:02:42 +0000 Subject: [PATCH 1/3] test: add crash repro for null-initialized pointer local `var p: int* = 0;` segfaults the compiler. Root cause: gen(numeric_literal) falls through to `return nullptr` when the literal's inferred type doesn't match integer/float/double (a plain int literal used to initialize a pointer-typed local isn't handled), and gen_local() passes that nullptr straight into builder.CreateStore() without checking it, which dereferences it and crashes. Confirmed via gdb: SIGSEGV inside backend_llvm::gen_local, called from gen(decl_statement) -> gen(statement_block) -> gen(function) -- the same call chain that flows into the "TODO: this bit crashes sometimes" comment a few frames up in gen(function); this is a concrete, 100% reproducible instance of that class of bug (a null Value* from gen() reaching a raw LLVM builder call unchecked), not a fix. --- test/CMakeLists.txt | 1 + test/codegen/pointer_null_init.aw | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 test/codegen/pointer_null_init.aw diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6b1e523..2cb231d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ set(TESTS codegen/array_of_structs.aw codegen/chain.aw codegen/function.aw + codegen/pointer_null_init.aw modules/main.aw ) diff --git a/test/codegen/pointer_null_init.aw b/test/codegen/pointer_null_init.aw new file mode 100644 index 0000000..1378115 --- /dev/null +++ b/test/codegen/pointer_null_init.aw @@ -0,0 +1,4 @@ +func main() +{ + var p: int* = 0; +} From bbf399c14f049b97e8d77f77225de9149432e83e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 07:11:21 +0000 Subject: [PATCH 2/3] codegen: fix segfault on numeric literal assigned to a pointer type Root cause of the crash reproduced by codegen/pointer_null_init.aw: propagate_type(numeric_literal) would blindly assign any target type (including a pointer type) onto a literal's expr.type with no check, and gen(numeric_literal) had no case for a pointer-typed literal, so it fell through to `return nullptr`. That nullptr then flowed unchecked into gen_local()'s builder.CreateStore(), which dereferences it immediately and segfaults. Fix: - type_inference: allow only a literal `0` to propagate a pointer type (the null-pointer idiom); anything else assigned to a pointer type is now a clean "Type mismatch" diagnostic instead of silently producing bad IR (or worse, corrupting the compiler's own state). - codegen: gen(numeric_literal) now emits ConstantPointerNull for a pointer-typed literal, so `var p: T* = 0;` generates correct IR. - gen_local(): check gen(var.value) for null before handing it to CreateStore(), so any future unhandled case fails cleanly instead of segfaulting -- this is the same unchecked-null pattern flagged by the "TODO: this bit crashes sometimes" comment in gen(function). Added test/errors/pointer_nonzero_init.aw as a negative test for the new diagnostic. codegen/pointer_null_init.aw now passes cleanly. --- lang/source/codegen/llvm/backend_llvm.c++ | 10 ++++++++-- lang/source/semantic/type_inference.c++ | 5 +++++ test/CMakeLists.txt | 2 ++ test/errors/pointer_nonzero_init.aw | 4 ++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/errors/pointer_nonzero_init.aw diff --git a/lang/source/codegen/llvm/backend_llvm.c++ b/lang/source/codegen/llvm/backend_llvm.c++ index 6b6996d..07ff6e7 100644 --- a/lang/source/codegen/llvm/backend_llvm.c++ +++ b/lang/source/codegen/llvm/backend_llvm.c++ @@ -368,8 +368,12 @@ auto backend_llvm::gen_local(const middle::variable& var) -> llvm::Value* auto* alloca = builder.CreateAlloca(get_llvm_type(context, var.type), nullptr, var.name); - if (var.value) - builder.CreateStore(gen(var.value), alloca); + if (var.value) { + auto* init = gen(var.value); + if (!init) + return nullptr; + builder.CreateStore(init, alloca); + } return alloca; } @@ -497,6 +501,8 @@ auto backend_llvm::gen(const middle::numeric_literal& expr) -> llvm::Constant* return ConstantFP::get(context, APFloat(APFloat::IEEEsingle(), expr.value)); if (type->isDoubleTy()) return ConstantFP::get(context, APFloat(APFloat::IEEEdouble(), expr.value)); + if (type->isPointerTy()) + return ConstantPointerNull::get(cast(type)); } return nullptr; } diff --git a/lang/source/semantic/type_inference.c++ b/lang/source/semantic/type_inference.c++ index 9a61071..1a57aba 100644 --- a/lang/source/semantic/type_inference.c++ +++ b/lang/source/semantic/type_inference.c++ @@ -313,6 +313,11 @@ struct type_inference_visitor auto propagate_type(ir::type* type, numeric_literal& expr) -> ir::type* { + // Only a literal `0` may stand in for a null pointer; any other + // integer literal assigned to a pointer type is a real mismatch. + if (get_if(&type->kind) && expr.value != "0") + return error(diag, diagnostic_id::type_mismathch, location(), type->name, std::string("numeric_literal")); + return (expr.type = type); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2cb231d..f807103 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ set(TESTS errors/not_allowed_here.aw errors/not_implemented_yet.aw errors/recovery.aw + errors/pointer_nonzero_init.aw codegen/array.aw codegen/array_of_structs.aw codegen/chain.aw @@ -27,6 +28,7 @@ set(NEGATIVE_TESTS errors/not_allowed_here.aw errors/not_implemented_yet.aw errors/recovery.aw + errors/pointer_nonzero_init.aw ) set(RUNTIME_TESTS diff --git a/test/errors/pointer_nonzero_init.aw b/test/errors/pointer_nonzero_init.aw new file mode 100644 index 0000000..a11124c --- /dev/null +++ b/test/errors/pointer_nonzero_init.aw @@ -0,0 +1,4 @@ +func main() +{ + var p: int* = 5; +} From 6cd11d8d362e29c129607bf5b5bb03114903a365 Mon Sep 17 00:00:00 2001 From: Hudd Date: Thu, 16 Jul 2026 17:15:58 +0300 Subject: [PATCH 3/3] test: re-enable codegen/pointer.aw The crash is fixed, so the test can be re-enabled --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 9d3ead3..7624bab 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -56,4 +56,4 @@ jobs: - name: Test working-directory: ${{github.workspace}}/build - run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure -E "codegen/pointer.aw" + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure