A char plus or minus an integer is checked for overflow, so a trap names the true sum or the side of the range it left.

This commit is contained in:
Joseph Ferano 2026-09-26 15:27:07 +07:00
parent 7361830938
commit 570d2d88fb
6 changed files with 60 additions and 20 deletions

View File

@ -11271,16 +11271,17 @@ and char_step ~want loc name (a : Tast.expr) (b : Tast.expr) : Tast.expr =
let r = if op = Tast.Add then Int64.add x y else Int64.sub x y in
if scalar r then mk loc Types.Char (Tast.Int (r, Types.U32))
else not_scalar (Int64.to_string r)
(* A u64 is taken as itself, so no large one wraps round to a char. *)
| _ when k = Types.U64 ->
rt loc Types.Char "flan_char_step_u64"
[ widen loc (Types.Int Types.I32) c; n;
(* The runtime takes the integer as itself — a u64 unsigned, anything
else as an i64 — and checks the sum for overflow, so no large one
wraps round to a char or to a wrong number in the trap. *)
| _ ->
rt loc Types.Char
(if k = Types.U64 then "flan_char_step_u64" else "flan_char_step_i64")
[ widen loc (Types.Int Types.I32) c;
(if k = Types.U64 then n else i64 n);
mk loc (Types.Int Types.I32)
(Tast.Int ((if op = Tast.Sub then 1L else 0L), Types.I32));
here loc ]
| _ ->
let sum = mk loc dyn_i64 (Tast.Prim (op, [ i64 a; i64 b ])) in
rt loc Types.Char "flan_char_of" [ sum; here loc ])
here loc ])
| _ ->
char_arith (if a.Tast.ty = Types.Char then a.Tast.loc else b.Tast.loc) name;
assert false

View File

@ -5149,6 +5149,7 @@ declare i32 @flan_dyn_need_char(i64, ptr, i64)
declare i32 @flan_char_of(i64, ptr, i64)
declare i32 @flan_char_of_u64(i64, ptr, i64)
declare i32 @flan_char_step_u64(i32, i64, i32, ptr, i64)
declare i32 @flan_char_step_i64(i32, i64, i32, ptr, i64)
; A numeric cast written on a dyn answers which numeric tag the box holds;
; check.ml's [cast_dyn] branches on it and each arm is an ordinary need plus
; the ordinary cast. The two slices are the site's location and the target's

View File

@ -3091,14 +3091,23 @@ static int char_arith(const uint8_t *loc, int64_t loclen, const char *op,
*out = flan_dyn_from_i64((int64_t)dyn_payload(a) - (int64_t)dyn_payload(b));
return 1;
}
if (ca && flan_dyn_tag(b) == FLAN_DYN_TAG_INT)
n = (int64_t)((uint64_t)dyn_payload(a) +
(op[0] == '-' ? -(uint64_t)dyn_int_value(b)
: (uint64_t)dyn_int_value(b)));
else if (op[0] == '+' && cb && flan_dyn_tag(a) == FLAN_DYN_TAG_INT)
n = (int64_t)((uint64_t)dyn_int_value(a) + (uint64_t)dyn_payload(b));
else
int64_t cp, m;
int sub = op[0] == '-';
if (ca && flan_dyn_tag(b) == FLAN_DYN_TAG_INT) {
cp = (int64_t)dyn_payload(a);
m = dyn_int_value(b);
} else if (!sub && cb && flan_dyn_tag(a) == FLAN_DYN_TAG_INT) {
cp = (int64_t)dyn_payload(b);
m = dyn_int_value(a);
} else
return 0;
/* Checked, so a trap names the true sum or which side it left. */
if (sub ? __builtin_sub_overflow(cp, m, &n) : __builtin_add_overflow(cp, m, &n)) {
flan_say(loc, loclen, "dyn %s: %lld %s %lld is %s, so it is not a char",
op, (long long)cp, sub ? "minus" : "plus", (long long)m,
(sub ? m < 0 : m > 0) ? "past 0x10FFFF" : "below zero");
dyn_trap((const uint8_t *)"InvalidChar", 11);
}
if (!is_scalar(n)) {
flan_say(loc, loclen,
"dyn %s: %lld is not a Unicode scalar value, so it is not a char",

View File

@ -3009,6 +3009,23 @@ uint32_t flan_char_step_u64(int32_t cp, uint64_t n, int32_t sub,
return flan_char_of_u64(r, loc, loclen);
}
/* A char plus or minus any other integer, as an i64 ([sub] 1 for minus).
* The sum is checked for overflow first, so a trap names the true result or
* says which side of the range it left, never a wrapped number. */
uint32_t flan_char_step_i64(int32_t cp, int64_t n, int32_t sub,
const uint8_t *loc, int64_t loclen) {
int64_t r;
int over = sub ? __builtin_sub_overflow((int64_t)cp, n, &r)
: __builtin_add_overflow((int64_t)cp, n, &r);
if (over) {
flan_say(loc, loclen, "%lld %s %lld is %s, so it is not a char",
(long long)cp, sub ? "minus" : "plus", (long long)n,
(sub ? n < 0 : n > 0) ? "past 0x10FFFF" : "below zero");
rt_trap((const uint8_t *)"InvalidChar", 11);
}
return flan_char_of(r, loc, loclen);
}
/* [n] elements from [src] onto the end of a Vec, growing it once. [src] may
* point into the Vec's own block — (append s (str s)) — so where it lies is
* found before the grow and read again after it: the grow frees the old

View File

@ -3,7 +3,8 @@
;;;; is the distance, an i32. Dyn chars do the same. Byte code beside it is
;;;; unchanged. With "past" a char past U+10FFFF traps, with "surrogate" one
;;;; landing on a surrogate does, with "dyn" a dyn char below zero does, and
;;;; with "u64" a char plus the largest u64 does rather than wrapping round.
;;;; with "u64" a char plus the largest u64 does rather than wrapping round,
;;;; and with "i64" and "dyn-i64" a char plus the largest i64 does, naming it.
(defn show [x] () (println x))
(defn add [a b] dyn (+ a b))
@ -17,6 +18,7 @@
(push v (- a b))
(+ (- a b) (at v 0) 1)))
(defn plus-u64 [c char n u64] char (+ c n))
(defn plus-i64 [c char n i64] char (+ c n))
(defn main [args [str]] i32
;; The fork case with arithmetic: a let-bound char stays a char.
@ -52,6 +54,10 @@
(println (+ (char 0xD7FF) (- k 1)))
(= (at args 1) "u64")
(println (plus-u64 \a (- (u64 0) (u64 (- k 1)))))
(= (at args 1) "i64")
(println (plus-i64 \a (- (max-value i64) (i64 (- k 2)))))
(= (at args 1) "dyn-i64")
(println (add \a (- (max-value i64) (i64 (- k 2)))))
:else
(println (sub \a (* k 100))))))
0)

View File

@ -5623,14 +5623,20 @@ level "1"
%d)\n wanted: %S (exit 134)\n"
arg (if x86 then ", --x86" else "") text code want
end)
[ ("past", "programs/char-arith.flan:50:18: 1114112 is not a \
[ ("past", "programs/char-arith.flan:52:18: 1114112 is not a \
Unicode scalar value, so it is not a char");
("surrogate", "programs/char-arith.flan:52:18: 55296 is not a \
("surrogate", "programs/char-arith.flan:54:18: 55296 is not a \
Unicode scalar value, so it is not a char");
("u64", "programs/char-arith.flan:19:36: 97 plus \
("u64", "programs/char-arith.flan:20:36: 97 plus \
18446744073709551615 is past 0x10FFFF, so it is not a \
char");
("dyn", "programs/char-arith.flan:10:21: dyn -: -103 is not a \
("i64", "programs/char-arith.flan:21:36: 97 plus \
9223372036854775807 is past 0x10FFFF, so it is not a \
char");
("dyn-i64", "programs/char-arith.flan:10:21: dyn +: 97 plus \
9223372036854775807 is past 0x10FFFF, so it is not \
a char");
("dyn", "programs/char-arith.flan:11:21: dyn -: -103 is not a \
Unicode scalar value, so it is not a char") ])
[ false; true ];
(* A String, and a str made from one, cross into dyn as text measured