i dunno lol
This commit is contained in:
parent
2f38738f84
commit
c476d05f7d
8
NEXT.md
8
NEXT.md
@ -102,8 +102,12 @@ test/programs/bounds.flan:25:29: slice [2 1) is out of bounds for length 5 (ex
|
||||
Three check sites, and the third is the one with the trap in it:
|
||||
|
||||
- **`at` on `[n T]`** — the bound is static, so LLVM folds the check away for a
|
||||
literal index. A literal that is *out* of bounds still only traps at runtime;
|
||||
rejecting it in `check.ml` is a separate job.
|
||||
literal index. A literal that is *out* of bounds never reaches emit at all:
|
||||
`check.ml` rejects it, along with a negative literal index (wrong whatever
|
||||
the target) and a literal `slice` range that runs backwards. Only literals —
|
||||
a `defconst` is a global in the typed IR, not a folded constant, so
|
||||
`(at a k)` stays a runtime trap. A slice bound may sit one past the end and
|
||||
an index may not, which is the one place the two rules differ.
|
||||
- **`at` on a slice or string** — the bound is the runtime len.
|
||||
- **`slice`** — *two* comparisons, `lo <= hi` and `hi <= len`, both non-strict
|
||||
because a slice ending at len (or an empty one at `lo = len`) is legal and
|
||||
|
||||
42
lib/check.ml
42
lib/check.ml
@ -529,6 +529,25 @@ and check_place ctx loc (p : Ast.place) : Tast.place * Types.t =
|
||||
| other ->
|
||||
fail loc "deref takes a (Ptr T), found %s" (Types.to_string other))
|
||||
|
||||
(* An index or a slice bound that is a literal is known now, so it is an error
|
||||
now rather than a trap later. Only literals: a [defconst] is a global in the
|
||||
typed IR, not a folded constant, so [(at arr size)] still traps at runtime —
|
||||
which is what the emitted bounds check is for. A negative literal is wrong
|
||||
whatever the target, but a length is static only for [n T]. *)
|
||||
and static_index loc (ty : Types.t) ~past_end what k =
|
||||
if k < 0L then
|
||||
fail loc "%s %Ld is negative — indices count from 0" what k;
|
||||
match ty with
|
||||
(* [past_end] is the difference between an index and a slice bound: the last
|
||||
valid index is len - 1, but a slice may end at len. *)
|
||||
| Types.Array (n, _) when if past_end then k > n else k >= n ->
|
||||
fail loc "%s %Ld is out of bounds for length %Ld" what k n
|
||||
| _ -> ()
|
||||
|
||||
(* The literal value of a checked expression, if it has one. *)
|
||||
and literal (e : Tast.expr) =
|
||||
match e.Tast.e with Tast.Int (k, _) -> Some k | _ -> None
|
||||
|
||||
(* [(at a i)] and [(at grid row col)]: one index per dimension. *)
|
||||
and indexed ctx (target : Tast.expr) (idx : Ast.expr list) =
|
||||
let rec go ty = function
|
||||
@ -540,7 +559,11 @@ and indexed ctx (target : Tast.expr) (idx : Ast.expr list) =
|
||||
| other ->
|
||||
fail i.Ast.loc "%s cannot be indexed" (Types.to_string other)
|
||||
in
|
||||
let loc = i.Ast.loc in
|
||||
let i = check ctx ~want:index_ty i in
|
||||
(match literal i with
|
||||
| Some k -> static_index loc ty ~past_end:false "index" k
|
||||
| None -> ());
|
||||
let rest, ty = go elem rest in
|
||||
i :: rest, ty
|
||||
in
|
||||
@ -616,8 +639,23 @@ and named_call ctx ~want loc name args =
|
||||
(Types.to_string other)
|
||||
in
|
||||
prim Tast.Slice (Types.Slice elem)
|
||||
(let lo = check ctx ~want:index_ty lo in
|
||||
[ target; lo; check ctx ~want:index_ty hi ])
|
||||
(let lo_loc = lo.Ast.loc and hi_loc = hi.Ast.loc in
|
||||
let lo = check ctx ~want:index_ty lo in
|
||||
let hi = check ctx ~want:index_ty hi in
|
||||
let ty = target.Tast.ty in
|
||||
(* A bound may sit one past the end, so the length is checked against
|
||||
lo and hi both, not against the last valid index. *)
|
||||
(match literal lo with
|
||||
| Some k -> static_index lo_loc ty ~past_end:true "slice bound" k
|
||||
| None -> ());
|
||||
(match literal hi with
|
||||
| Some k -> static_index hi_loc ty ~past_end:true "slice bound" k
|
||||
| None -> ());
|
||||
(match literal lo, literal hi with
|
||||
| Some a, Some b when a > b ->
|
||||
fail loc "slice [%Ld %Ld) runs backwards — lo must not exceed hi" a b
|
||||
| _ -> ());
|
||||
[ target; lo; hi ])
|
||||
| _ -> assert false)
|
||||
|
||||
(* ── pointers ──────────────────────────────────────────────────── *)
|
||||
|
||||
@ -403,6 +403,41 @@ let () =
|
||||
rejects_check "if branches disagree"
|
||||
"(defn f [] i32 (if true 1 true))" ~needle:"expected i32";
|
||||
|
||||
(* ── Static bounds ─────────────────────────────────────────────── *)
|
||||
(* A literal index into a fixed array is known now, so it is an error now
|
||||
rather than a trap later; everything else is the emitted bounds check's
|
||||
job. A [defconst] is a global in the typed IR, not a folded constant, so
|
||||
it deliberately stays a runtime trap. *)
|
||||
let arr = "(defvar a [3 i32]) " in
|
||||
accepts "last valid index" (arr ^ "(defn f [] i32 (at a 2))");
|
||||
rejects_check "index past the end" (arr ^ "(defn f [] i32 (at a 3))")
|
||||
~needle:"out of bounds for length 3";
|
||||
rejects_check "negative index" (arr ^ "(defn f [] i32 (at a -1))")
|
||||
~needle:"is negative";
|
||||
rejects_check "index past the end of an inner dimension"
|
||||
"(defvar g [2 [4 i32]]) (defn f [] i32 (at g 1 4))"
|
||||
~needle:"out of bounds for length 4";
|
||||
accepts "a variable index is checked at runtime, not here"
|
||||
(arr ^ "(defn f [i i32] i32 (at a i))");
|
||||
accepts "a defconst index is not folded"
|
||||
("(defconst k 9) " ^ arr ^ "(defn f [] i32 (at a k))");
|
||||
|
||||
(* A slice bound may sit one past the end; an index may not. *)
|
||||
accepts "slice ending at len" (arr ^ "(defn f [] [i32] (slice a 1 3))");
|
||||
accepts "empty slice at len" (arr ^ "(defn f [] [i32] (slice a 3 3))");
|
||||
rejects_check "slice bound past len" (arr ^ "(defn f [] [i32] (slice a 1 4))")
|
||||
~needle:"out of bounds for length 3";
|
||||
rejects_check "negative slice bound" (arr ^ "(defn f [] [i32] (slice a -1 2))")
|
||||
~needle:"is negative";
|
||||
rejects_check "reversed slice" (arr ^ "(defn f [] [i32] (slice a 2 1))")
|
||||
~needle:"runs backwards";
|
||||
(* A slice has no static length, so only the two target-independent rules
|
||||
apply to one. *)
|
||||
rejects_check "reversed slice of a slice"
|
||||
"(defn f [s [u8]] [u8] (slice s 2 1))" ~needle:"runs backwards";
|
||||
accepts "a slice's length is not known here"
|
||||
"(defn f [s [u8]] [u8] (slice s 0 99))";
|
||||
|
||||
(* ── Structs, fields and auto-deref ────────────────────────────── *)
|
||||
let cursor = "(defstruct Cursor [src [u8] pos i32]) " in
|
||||
accepts "struct literal, omitted field zeroed"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user