feat(compiler): a slice index is checked against its length
A slice has carried { ptr, len, cap } since it existed and nothing ever read
the len: every index emitted a bare getelementptr. Running off the end of one
wrote into whatever the allocator had put next, and the program died somewhere
else entirely - a maroon-lake crash took a day to find because the stack named
a texture upload and the write was in a telemetry buffer five frames earlier.
Now each index loads the length and compares unsigned, which rejects a negative
index in the same instruction, and a failure aborts with the location the other
located errors use:
oob.ludic:9: index out of range: 7, len 3
`words` and the other raw buffers are unchanged - they are a bare malloc with no
length to check, which is the argument for moving off them.
The SPIR-V variants are regenerated in the same commit: shaders --check was
failing against the edited GLSL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f7f47152bd
commit
0998cb5a18
77 changed files with 803 additions and 696 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
|
@ -57,6 +57,28 @@ function emit_index_addr(e: Node) -> pointer {
|
||||||
let data = nreg()
|
let data = nreg()
|
||||||
emit(" "); emit(data); emit(" = load ptr, ptr "); emit(dp); emit("\n")
|
emit(" "); emit(data); emit(" = load ptr, ptr "); emit(dp); emit("\n")
|
||||||
let ix = emit_expr(e.b) # (evaluate index BEFORE setting
|
let ix = emit_expr(e.b) # (evaluate index BEFORE setting
|
||||||
|
# THE INDEX IS CHECKED AGAINST THE LENGTH. A slice has carried { ptr, len, cap } since it
|
||||||
|
# existed, and nothing ever read the len: every index was a bare getelementptr, so running
|
||||||
|
# off the end of one wrote into whatever the allocator had put next and the program died
|
||||||
|
# somewhere else entirely, minutes later, with a stack that named an innocent function.
|
||||||
|
# The comparison is UNSIGNED, which rejects a negative index in the same instruction.
|
||||||
|
if g_bounds {
|
||||||
|
g_uses_bounds = true
|
||||||
|
let lnp = nreg()
|
||||||
|
emit(" "); emit(lnp); emit(" = getelementptr inbounds %LSlice, ptr ")
|
||||||
|
emit(base.code); emit(", i32 0, i32 1\n")
|
||||||
|
let lnv = emit_bind(`load i32, ptr {lnp}`)
|
||||||
|
let okc = emit_bind(`icmp ult i32 {ix.code}, {lnv}`)
|
||||||
|
let lok = lbl("ixok")
|
||||||
|
let lbad = lbl("ixbad")
|
||||||
|
emit(` br i1 {okc}, label %{lok}, label %{lbad}\n`)
|
||||||
|
emit(`{lbad}:\n`)
|
||||||
|
let bmsg = emit_str_const(`{g_src_name}:{itoa(e.line)}: index out of range: `)
|
||||||
|
let bse = emit_bind(stdstream_rhs(2))
|
||||||
|
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr {bse}, ptr @.fmt_bounds, ptr {bmsg}, i32 {ix.code}, i32 {lnv})\n`)
|
||||||
|
emit(" call void @exit(i32 1)\n unreachable\n")
|
||||||
|
emit(`{lok}:\n`)
|
||||||
|
}
|
||||||
let r = nreg() # g_addr_ty — a member-access index
|
let r = nreg() # g_addr_ty — a member-access index
|
||||||
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el)) # overwrites it)
|
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el)) # overwrites it)
|
||||||
emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n")
|
emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n")
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ var g_uses_pak: bool = false # file_open was emitted -> emit the asset-pack ru
|
||||||
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
||||||
var g_uses_expect: bool = false # expect/expect_eq/expect_near was emitted -> emit the test-assert globals
|
var g_uses_expect: bool = false # expect/expect_eq/expect_near was emitted -> emit the test-assert globals
|
||||||
var g_uses_panic: bool = false # panic/assert was emitted -> declare @fprintf + the panic format
|
var g_uses_panic: bool = false # panic/assert was emitted -> declare @fprintf + the panic format
|
||||||
|
var g_uses_bounds: bool = false # a checked slice index was emitted -> declare the bounds format
|
||||||
|
var g_fprintf_declared: bool = false # @fprintf is declared once, by whoever needs it first
|
||||||
|
var g_bounds: bool = true # check every slice index (--no-bounds turns it off)
|
||||||
var g_uses_result: bool = false # ok()/err()/try was emitted -> define the %Result value type (issue #46)
|
var g_uses_result: bool = false # ok()/err()/try was emitted -> define the %Result value type (issue #46)
|
||||||
var g_uses_option: bool = false # some()/none() was emitted -> define the %Option value type (issue #53)
|
var g_uses_option: bool = false # some()/none() was emitted -> define the %Option value type (issue #53)
|
||||||
var g_tests: []Node # test "name" { ... } blocks collected by the parser
|
var g_tests: []Node # test "name" { ... } blocks collected by the parser
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ function emit_program() -> void {
|
||||||
g_uses_loopback = false
|
g_uses_loopback = false
|
||||||
g_uses_expect = false
|
g_uses_expect = false
|
||||||
g_uses_panic = false
|
g_uses_panic = false
|
||||||
|
g_fprintf_declared = false
|
||||||
g_uses_result = false
|
g_uses_result = false
|
||||||
g_uses_option = false
|
g_uses_option = false
|
||||||
g_uses_world_despawn = false # #84: set when a world_despawn call is emitted (below)
|
g_uses_world_despawn = false # #84: set when a world_despawn call is emitted (below)
|
||||||
|
|
@ -281,8 +282,13 @@ function emit_program() -> void {
|
||||||
if g_uses_datert { emit_datetime_prelude() } # @lp_days_from_civil / @lp_civil_from_days conversions
|
if g_uses_datert { emit_datetime_prelude() } # @lp_days_from_civil / @lp_civil_from_days conversions
|
||||||
if g_uses_panic { # panic/assert: located abort to stderr
|
if g_uses_panic { # panic/assert: located abort to stderr
|
||||||
emith("declare i32 @fprintf(ptr, ptr, ...)\n")
|
emith("declare i32 @fprintf(ptr, ptr, ...)\n")
|
||||||
|
g_fprintf_declared = true
|
||||||
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
||||||
}
|
}
|
||||||
|
if g_uses_bounds { # a slice index out of range: located abort
|
||||||
|
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
|
||||||
|
emith("@.fmt_bounds = private unnamed_addr constant [14 x i8] c\"%s%d, len %d\\0A\\00\"\n")
|
||||||
|
}
|
||||||
if g_uses_result { emith("%Result = type { i32, i32, ptr }\n") } # issue #46: ok/err/try value
|
if g_uses_result { emith("%Result = type { i32, i32, ptr }\n") } # issue #46: ok/err/try value
|
||||||
if g_uses_option { emith("%Option = type { i32, i32 }\n") } # issue #53: some/none value
|
if g_uses_option { emith("%Option = type { i32, i32 }\n") } # issue #53: some/none value
|
||||||
emit_cov_runtime() # issue #45: --coverage tables + exit dump
|
emit_cov_runtime() # issue #45: --coverage tables + exit dump
|
||||||
|
|
@ -318,7 +324,7 @@ function emit_cov_runtime() -> void {
|
||||||
let modec = emit_str_const("w")
|
let modec = emit_str_const("w")
|
||||||
emith("@.cov_filefmt = private unnamed_addr constant [9 x i8] c\"FILE %s\\0A\\00\"\n")
|
emith("@.cov_filefmt = private unnamed_addr constant [9 x i8] c\"FILE %s\\0A\\00\"\n")
|
||||||
emith("@.cov_rowfmt = private unnamed_addr constant [7 x i8] c\"%d %d\\0A\\00\"\n")
|
emith("@.cov_rowfmt = private unnamed_addr constant [7 x i8] c\"%d %d\\0A\\00\"\n")
|
||||||
if not g_uses_panic { emith("declare i32 @fprintf(ptr, ptr, ...)\n") }
|
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
|
||||||
emith("declare i32 @atexit(ptr)\n")
|
emith("declare i32 @atexit(ptr)\n")
|
||||||
|
|
||||||
# @cov_dump: open $LUDIC_COVERAGE (or "ludic.cov"), write a FILE header then one
|
# @cov_dump: open $LUDIC_COVERAGE (or "ludic.cov"), write a FILE header then one
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue