Commit ec358d6db5

Techatrix <19954306+Techatrix@users.noreply.github.com>
2023-10-30 00:02:57
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)`.
1 parent 4ace1f5
src/Sema.zig
@@ -16140,6 +16140,10 @@ fn analyzeArithmetic(
                         return casted_lhs;
                     }
                     if (maybe_lhs_val) |lhs_val| {
+                        if (lhs_val.isUndef(mod)) {
+                            return mod.undefRef(resolved_type);
+                        }
+
                         const val = if (scalar_tag == .ComptimeInt)
                             try sema.intAdd(lhs_val, rhs_val, resolved_type, undefined)
                         else
@@ -16202,7 +16206,7 @@ fn analyzeArithmetic(
             },
             .subwrap => {
                 // Integers only; floats are checked above.
-                // If the RHS is zero, then the other operand is returned, even if it is undefined.
+                // If the RHS is zero, then the LHS is returned, even if it is undefined.
                 // If either of the operands are undefined, the result is undefined.
                 if (maybe_rhs_val) |rhs_val| {
                     if (rhs_val.isUndef(mod)) {
@@ -16223,8 +16227,8 @@ fn analyzeArithmetic(
             },
             .sub_sat => {
                 // Integers only; floats are checked above.
-                // If the RHS is zero, result is LHS.
-                // If either of the operands are undefined, result is undefined.
+                // If the RHS is zero, then the LHS is returned, even if it is undefined.
+                // If either of the operands are undefined, the result is undefined.
                 if (maybe_rhs_val) |rhs_val| {
                     if (rhs_val.isUndef(mod)) {
                         return mod.undefRef(resolved_type);
@@ -16255,7 +16259,9 @@ fn analyzeArithmetic(
                 // If either of the operands are undefined, it's a compile error
                 // because there is a possible value for which the addition would
                 // overflow (max_int), causing illegal behavior.
-                // For floats: either operand being undef makes the result undef.
+                //
+                // For floats:
+                // If either of the operands are undefined, the result is undefined.
                 // If either of the operands are inf, and the other operand is zero,
                 // the result is nan.
                 // If either of the operands are nan, the result is nan.
@@ -38093,7 +38099,7 @@ fn numberAddWrapScalar(
     ty: Type,
 ) !Value {
     const mod = sema.mod;
-    if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.undef;
+    if (lhs.isUndef(mod) or rhs.isUndef(mod)) return mod.undefValue(ty);
 
     if (ty.zigTypeTag(mod) == .ComptimeInt) {
         return sema.intAdd(lhs, rhs, ty, undefined);
@@ -38183,7 +38189,7 @@ fn numberSubWrapScalar(
     ty: Type,
 ) !Value {
     const mod = sema.mod;
-    if (lhs.isUndef(mod) or rhs.isUndef(mod)) return Value.undef;
+    if (lhs.isUndef(mod) or rhs.isUndef(mod)) return mod.undefValue(ty);
 
     if (ty.zigTypeTag(mod) == .ComptimeInt) {
         return sema.intSub(lhs, rhs, ty, undefined);
test/cases/compile_errors/add_on_undefined_value.zig
@@ -1,10 +1,24 @@
 comptime {
-    const a: i64 = undefined;
-    _ = a + a;
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, then the other operand is returned.
+    @compileLog(undef + 0);
+    @compileLog(not_undef + 0);
+    @compileLog(0 + undef);
+    @compileLog(0 + not_undef);
+
+    _ = undef + undef;
 }
 
 // error
 // backend=stage2
 // target=native
 //
-// :3:13: error: use of undefined value here causes undefined behavior
+// :11:17: error: use of undefined value here causes undefined behavior
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
test/cases/compile_errors/add_sat_on_undefined_value.zig
@@ -0,0 +1,31 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, then the other operand is returned.
+    @compileLog(undef +| 0);
+    @compileLog(not_undef +| 0);
+    @compileLog(0 +| undef);
+    @compileLog(0 +| not_undef);
+    // If either of the operands are undefined, the result is undefined.
+    @compileLog(undef +| 1);
+    @compileLog(not_undef +| 1);
+    @compileLog(1 +| undef);
+    @compileLog(1 +| not_undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 33)
+// @as(i64, undefined)
+// @as(i64, 33)
test/cases/compile_errors/add_wrap_on_undefined_value.zig
@@ -0,0 +1,31 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, then the other operand is returned.
+    @compileLog(undef +% 0);
+    @compileLog(not_undef +% 0);
+    @compileLog(0 +% undef);
+    @compileLog(0 +% not_undef);
+    // If either of the operands are undefined, the result is undefined.
+    @compileLog(undef +% 1);
+    @compileLog(not_undef +% 1);
+    @compileLog(1 +% undef);
+    @compileLog(1 +% not_undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 33)
+// @as(i64, undefined)
+// @as(i64, 33)
test/cases/compile_errors/mult_on_undefined_value.zig
@@ -1,10 +1,38 @@
 comptime {
-    const a: i64 = undefined;
-    _ = a * a;
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, the result is zero.
+    @compileLog(undef * 0);
+    @compileLog(not_undef * 0);
+    @compileLog(0 * undef);
+    @compileLog(0 * not_undef);
+
+    // If either of the operands are one, the result is the other
+    // operand, even if it is undefined.
+    @compileLog(undef * 1);
+    @compileLog(not_undef * 1);
+    @compileLog(1 * undef);
+    @compileLog(1 * not_undef);
+
+    // If either of the operands are undefined, it's a compile error
+    // because there is a possible value for which the addition would
+    // overflow (max_int), causing illegal behavior.
+    _ = undef * undef;
 }
 
 // error
 // backend=stage2
 // target=native
 //
-// :3:13: error: use of undefined value here causes undefined behavior
+// :21:17: error: use of undefined value here causes undefined behavior
+//
+// Compile Log Output:
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
test/cases/compile_errors/mult_sat_on_undefined_value.zig
@@ -0,0 +1,38 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, the result is zero.
+    @compileLog(undef *| 0);
+    @compileLog(not_undef *| 0);
+    @compileLog(0 *| undef);
+    @compileLog(0 *| not_undef);
+
+    // If either of the operands are one, result is the other operand.
+    @compileLog(undef *| 1);
+    @compileLog(not_undef *| 1);
+    @compileLog(1 *| undef);
+    @compileLog(1 *| not_undef);
+
+    // If either of the operands are undefined, result is undefined.
+    @compileLog(undef *| 2);
+    @compileLog(2 *| undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, undefined)
test/cases/compile_errors/mult_wrap_on_undefined_value.zig
@@ -0,0 +1,38 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If either of the operands are zero, the result is zero.
+    @compileLog(undef *% 0);
+    @compileLog(not_undef *% 0);
+    @compileLog(0 *% undef);
+    @compileLog(0 *% not_undef);
+
+    // If either of the operands are one, result is the other operand.
+    @compileLog(undef *% 1);
+    @compileLog(not_undef *% 1);
+    @compileLog(1 *% undef);
+    @compileLog(1 *% not_undef);
+
+    // If either of the operands are undefined, result is undefined.
+    @compileLog(undef *% 2);
+    @compileLog(2 *% undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, 0)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, undefined)
test/cases/compile_errors/sub_on_undefined_value.zig
@@ -1,10 +1,20 @@
 comptime {
-    const a: i64 = undefined;
-    _ = a - a;
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If the rhs is zero, then the other operand is returned, even if it is undefined.
+    @compileLog(undef - 0);
+    @compileLog(not_undef - 0);
+
+    _ = undef - undef;
 }
 
 // error
 // backend=stage2
 // target=native
 //
-// :3:13: error: use of undefined value here causes undefined behavior
+// :9:17: error: use of undefined value here causes undefined behavior
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
test/cases/compile_errors/sub_sat_on_undefined_value.zig
@@ -0,0 +1,25 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If the RHS is zero, then the LHS is returned, even if it is undefined.
+    @compileLog(undef -| 0);
+    @compileLog(not_undef -| 0);
+    // If either of the operands are undefined, the result is undefined.
+    @compileLog(undef -| not_undef);
+    @compileLog(not_undef -| undef);
+    @compileLog(undef -| undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, undefined)
+// @as(i64, undefined)
test/cases/compile_errors/sub_wrap_on_undefined_value.zig
@@ -0,0 +1,25 @@
+comptime {
+    const undef: i64 = undefined;
+    const not_undef: i64 = 32;
+
+    // If the RHS is zero, then the LHS is returned, even if it is undefined.
+    @compileLog(undef -% 0);
+    @compileLog(not_undef -% 0);
+    // If either of the operands are undefined, the result is undefined.
+    @compileLog(undef -% not_undef);
+    @compileLog(not_undef -% undef);
+    @compileLog(undef -% undef);
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :6:5: error: found compile log statement
+//
+// Compile Log Output:
+// @as(i64, undefined)
+// @as(i64, 32)
+// @as(i64, undefined)
+// @as(i64, undefined)
+// @as(i64, undefined)