authorgravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2023-10-30 00:02:57+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-16 16:27:31-08:00
logec358d6db5a10989590e11bbbbd3bed9f81cf1f4
treebd5affb8ff6bbbb4ea8ac22554c0a782d73bb261
parent4ace1f5a7ffeb569e97dc78807a1eacc5565a272

sema: fix safe integer arithmetic operations on undefined values

Previously `@as(i64, undefined) +% 1` would produce `@as(@TypeOf(undefined), undefined)` which now gives `@as(i64, undefined)`. Previously `@as(i64, undefined) +| 1` would hit an assertion which now gives `@as(i64, undefined)`.

10 files changed, 261 insertions(+), 15 deletions(-)

src/Sema.zig+12-6
......@@ -16140,6 +16140,10 @@ fn analyzeArithmetic(
1614016140 return casted_lhs;
1614116141 }
1614216142 if (maybe_lhs_val) |lhs_val| {
16143 if (lhs_val.isUndef(mod)) {
16144 return mod.undefRef(resolved_type);
16145 }
16146
1614316147 const val = if (scalar_tag == .ComptimeInt)
1614416148 try sema.intAdd(lhs_val, rhs_val, resolved_type, undefined)
1614516149 else
......@@ -16202,7 +16206,7 @@ fn analyzeArithmetic(
1620216206 },
1620316207 .subwrap => {
1620416208 // Integers only; floats are checked above.
16205 // If the RHS is zero, then the other operand is returned, even if it is undefined.
16209 // If the RHS is zero, then the LHS is returned, even if it is undefined.
1620616210 // If either of the operands are undefined, the result is undefined.
1620716211 if (maybe_rhs_val) |rhs_val| {
1620816212 if (rhs_val.isUndef(mod)) {
......@@ -16223,8 +16227,8 @@ fn analyzeArithmetic(
1622316227 },
1622416228 .sub_sat => {
1622516229 // Integers only; floats are checked above.
16226 // If the RHS is zero, result is LHS.
16227 // If either of the operands are undefined, result is undefined.
16230 // If the RHS is zero, then the LHS is returned, even if it is undefined.
16231 // If either of the operands are undefined, the result is undefined.
1622816232 if (maybe_rhs_val) |rhs_val| {
1622916233 if (rhs_val.isUndef(mod)) {
1623016234 return mod.undefRef(resolved_type);
......@@ -16255,7 +16259,9 @@ fn analyzeArithmetic(
1625516259 // If either of the operands are undefined, it's a compile error
1625616260 // because there is a possible value for which the addition would
1625716261 // overflow (max_int), causing illegal behavior.
16258 // For floats: either operand being undef makes the result undef.
16262 //
16263 // For floats:
16264 // If either of the operands are undefined, the result is undefined.
1625916265 // If either of the operands are inf, and the other operand is zero,
1626016266 // the result is nan.
1626116267 // If either of the operands are nan, the result is nan.
......@@ -38093,7 +38099,7 @@ fn numberAddWrapScalar(
3809338099 ty: Type,
3809438100) !Value {
3809538101 const mod = sema.mod;
38096 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.undef;
38102 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return mod.undefValue(ty);
3809738103
3809838104 if (ty.zigTypeTag(mod) == .ComptimeInt) {
3809938105 return sema.intAdd(lhs, rhs, ty, undefined);
......@@ -38183,7 +38189,7 @@ fn numberSubWrapScalar(
3818338189 ty: Type,
3818438190) !Value {
3818538191 const mod = sema.mod;
38186 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.undef;
38192 if (lhs.isUndef(mod) or rhs.isUndef(mod)) return mod.undefValue(ty);
3818738193
3818838194 if (ty.zigTypeTag(mod) == .ComptimeInt) {
3818938195 return sema.intSub(lhs, rhs, ty, undefined);
test/cases/compile_errors/add_on_undefined_value.zig+17-3
......@@ -1,10 +1,24 @@
11comptime {
2 const a: i64 = undefined;
3 _ = a + a;
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, then the other operand is returned.
6 @compileLog(undef + 0);
7 @compileLog(not_undef + 0);
8 @compileLog(0 + undef);
9 @compileLog(0 + not_undef);
10
11 _ = undef + undef;
412}
513
614// error
715// backend=stage2
816// target=native
917//
10// :3:13: error: use of undefined value here causes undefined behavior
18// :11:17: error: use of undefined value here causes undefined behavior
19//
20// Compile Log Output:
21// @as(i64, undefined)
22// @as(i64, 32)
23// @as(i64, undefined)
24// @as(i64, 32)
test/cases/compile_errors/add_sat_on_undefined_value.zig created+31
......@@ -0,0 +1,31 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, then the other operand is returned.
6 @compileLog(undef +| 0);
7 @compileLog(not_undef +| 0);
8 @compileLog(0 +| undef);
9 @compileLog(0 +| not_undef);
10 // If either of the operands are undefined, the result is undefined.
11 @compileLog(undef +| 1);
12 @compileLog(not_undef +| 1);
13 @compileLog(1 +| undef);
14 @compileLog(1 +| not_undef);
15}
16
17// error
18// backend=stage2
19// target=native
20//
21// :6:5: error: found compile log statement
22//
23// Compile Log Output:
24// @as(i64, undefined)
25// @as(i64, 32)
26// @as(i64, undefined)
27// @as(i64, 32)
28// @as(i64, undefined)
29// @as(i64, 33)
30// @as(i64, undefined)
31// @as(i64, 33)
test/cases/compile_errors/add_wrap_on_undefined_value.zig created+31
......@@ -0,0 +1,31 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, then the other operand is returned.
6 @compileLog(undef +% 0);
7 @compileLog(not_undef +% 0);
8 @compileLog(0 +% undef);
9 @compileLog(0 +% not_undef);
10 // If either of the operands are undefined, the result is undefined.
11 @compileLog(undef +% 1);
12 @compileLog(not_undef +% 1);
13 @compileLog(1 +% undef);
14 @compileLog(1 +% not_undef);
15}
16
17// error
18// backend=stage2
19// target=native
20//
21// :6:5: error: found compile log statement
22//
23// Compile Log Output:
24// @as(i64, undefined)
25// @as(i64, 32)
26// @as(i64, undefined)
27// @as(i64, 32)
28// @as(i64, undefined)
29// @as(i64, 33)
30// @as(i64, undefined)
31// @as(i64, 33)
test/cases/compile_errors/mult_on_undefined_value.zig+31-3
......@@ -1,10 +1,38 @@
11comptime {
2 const a: i64 = undefined;
3 _ = a * a;
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, the result is zero.
6 @compileLog(undef * 0);
7 @compileLog(not_undef * 0);
8 @compileLog(0 * undef);
9 @compileLog(0 * not_undef);
10
11 // If either of the operands are one, the result is the other
12 // operand, even if it is undefined.
13 @compileLog(undef * 1);
14 @compileLog(not_undef * 1);
15 @compileLog(1 * undef);
16 @compileLog(1 * not_undef);
17
18 // If either of the operands are undefined, it's a compile error
19 // because there is a possible value for which the addition would
20 // overflow (max_int), causing illegal behavior.
21 _ = undef * undef;
422}
523
624// error
725// backend=stage2
826// target=native
927//
10// :3:13: error: use of undefined value here causes undefined behavior
28// :21:17: error: use of undefined value here causes undefined behavior
29//
30// Compile Log Output:
31// @as(i64, 0)
32// @as(i64, 0)
33// @as(i64, 0)
34// @as(i64, 0)
35// @as(i64, undefined)
36// @as(i64, 32)
37// @as(i64, undefined)
38// @as(i64, 32)
test/cases/compile_errors/mult_sat_on_undefined_value.zig created+38
......@@ -0,0 +1,38 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, the result is zero.
6 @compileLog(undef *| 0);
7 @compileLog(not_undef *| 0);
8 @compileLog(0 *| undef);
9 @compileLog(0 *| not_undef);
10
11 // If either of the operands are one, result is the other operand.
12 @compileLog(undef *| 1);
13 @compileLog(not_undef *| 1);
14 @compileLog(1 *| undef);
15 @compileLog(1 *| not_undef);
16
17 // If either of the operands are undefined, result is undefined.
18 @compileLog(undef *| 2);
19 @compileLog(2 *| undef);
20}
21
22// error
23// backend=stage2
24// target=native
25//
26// :6:5: error: found compile log statement
27//
28// Compile Log Output:
29// @as(i64, 0)
30// @as(i64, 0)
31// @as(i64, 0)
32// @as(i64, 0)
33// @as(i64, undefined)
34// @as(i64, 32)
35// @as(i64, undefined)
36// @as(i64, 32)
37// @as(i64, undefined)
38// @as(i64, undefined)
test/cases/compile_errors/mult_wrap_on_undefined_value.zig created+38
......@@ -0,0 +1,38 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If either of the operands are zero, the result is zero.
6 @compileLog(undef *% 0);
7 @compileLog(not_undef *% 0);
8 @compileLog(0 *% undef);
9 @compileLog(0 *% not_undef);
10
11 // If either of the operands are one, result is the other operand.
12 @compileLog(undef *% 1);
13 @compileLog(not_undef *% 1);
14 @compileLog(1 *% undef);
15 @compileLog(1 *% not_undef);
16
17 // If either of the operands are undefined, result is undefined.
18 @compileLog(undef *% 2);
19 @compileLog(2 *% undef);
20}
21
22// error
23// backend=stage2
24// target=native
25//
26// :6:5: error: found compile log statement
27//
28// Compile Log Output:
29// @as(i64, 0)
30// @as(i64, 0)
31// @as(i64, 0)
32// @as(i64, 0)
33// @as(i64, undefined)
34// @as(i64, 32)
35// @as(i64, undefined)
36// @as(i64, 32)
37// @as(i64, undefined)
38// @as(i64, undefined)
test/cases/compile_errors/sub_on_undefined_value.zig+13-3
......@@ -1,10 +1,20 @@
11comptime {
2 const a: i64 = undefined;
3 _ = a - a;
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If the rhs is zero, then the other operand is returned, even if it is undefined.
6 @compileLog(undef - 0);
7 @compileLog(not_undef - 0);
8
9 _ = undef - undef;
410}
511
612// error
713// backend=stage2
814// target=native
915//
10// :3:13: error: use of undefined value here causes undefined behavior
16// :9:17: error: use of undefined value here causes undefined behavior
17//
18// Compile Log Output:
19// @as(i64, undefined)
20// @as(i64, 32)
test/cases/compile_errors/sub_sat_on_undefined_value.zig created+25
......@@ -0,0 +1,25 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If the RHS is zero, then the LHS is returned, even if it is undefined.
6 @compileLog(undef -| 0);
7 @compileLog(not_undef -| 0);
8 // If either of the operands are undefined, the result is undefined.
9 @compileLog(undef -| not_undef);
10 @compileLog(not_undef -| undef);
11 @compileLog(undef -| undef);
12}
13
14// error
15// backend=stage2
16// target=native
17//
18// :6:5: error: found compile log statement
19//
20// Compile Log Output:
21// @as(i64, undefined)
22// @as(i64, 32)
23// @as(i64, undefined)
24// @as(i64, undefined)
25// @as(i64, undefined)
test/cases/compile_errors/sub_wrap_on_undefined_value.zig created+25
......@@ -0,0 +1,25 @@
1comptime {
2 const undef: i64 = undefined;
3 const not_undef: i64 = 32;
4
5 // If the RHS is zero, then the LHS is returned, even if it is undefined.
6 @compileLog(undef -% 0);
7 @compileLog(not_undef -% 0);
8 // If either of the operands are undefined, the result is undefined.
9 @compileLog(undef -% not_undef);
10 @compileLog(not_undef -% undef);
11 @compileLog(undef -% undef);
12}
13
14// error
15// backend=stage2
16// target=native
17//
18// :6:5: error: found compile log statement
19//
20// Compile Log Output:
21// @as(i64, undefined)
22// @as(i64, 32)
23// @as(i64, undefined)
24// @as(i64, undefined)
25// @as(i64, undefined)