diff --git a/runtime/include/hemlock_value.h b/runtime/include/hemlock_value.h index 506184c9..2006ccf5 100644 --- a/runtime/include/hemlock_value.h +++ b/runtime/include/hemlock_value.h @@ -617,16 +617,26 @@ static inline void hml_array_set_i32_fast(HmlArray *arr, int32_t index, HmlValue hml_runtime_error("Array index %d out of bounds (length %d)", index, arr->length); } -// Fast path: Increment i32 variable in-place +// Fast path: Increment i32 variable in-place (with overflow check) // Returns the new value static inline HmlValue hml_i32_inc(HmlValue val) { - return (HmlValue){ .type = HML_VAL_I32, .as.as_i32 = val.as.as_i32 + 1 }; + int32_t result; + if (__builtin_add_overflow(val.as.as_i32, 1, &result)) { + extern __attribute__((noreturn)) void hml_runtime_error(const char *fmt, ...); + hml_runtime_error("Integer overflow: i32 increment"); + } + return (HmlValue){ .type = HML_VAL_I32, .as.as_i32 = result }; } -// Fast path: Decrement i32 variable in-place +// Fast path: Decrement i32 variable in-place (with overflow check) // Returns the new value static inline HmlValue hml_i32_dec(HmlValue val) { - return (HmlValue){ .type = HML_VAL_I32, .as.as_i32 = val.as.as_i32 - 1 }; + int32_t result; + if (__builtin_sub_overflow(val.as.as_i32, 1, &result)) { + extern __attribute__((noreturn)) void hml_runtime_error(const char *fmt, ...); + hml_runtime_error("Integer overflow: i32 decrement"); + } + return (HmlValue){ .type = HML_VAL_I32, .as.as_i32 = result }; } #endif // HEMLOCK_VALUE_H diff --git a/runtime/src/builtins_ops.c b/runtime/src/builtins_ops.c index a64c2747..a97e092f 100644 --- a/runtime/src/builtins_ops.c +++ b/runtime/src/builtins_ops.c @@ -86,6 +86,30 @@ static inline HmlValueType promote_types(HmlValueType a, HmlValueType b) { return hml_tk_to_val(result); } +// Create an integer result value with overflow checking for signed narrowing +HmlValue make_int_result_checked(HmlValueType result_type, int64_t value) { + switch (result_type) { + case HML_VAL_I8: + if (value < INT8_MIN || value > INT8_MAX) + hml_runtime_error("Integer overflow: i8 arithmetic"); + return hml_val_i8((int8_t)value); + case HML_VAL_I16: + if (value < INT16_MIN || value > INT16_MAX) + hml_runtime_error("Integer overflow: i16 arithmetic"); + return hml_val_i16((int16_t)value); + case HML_VAL_I32: + if (value < INT32_MIN || value > INT32_MAX) + hml_runtime_error("Integer overflow: i32 arithmetic"); + return hml_val_i32((int32_t)value); + case HML_VAL_I64: return hml_val_i64(value); + case HML_VAL_U8: return hml_val_u8((uint8_t)value); + case HML_VAL_U16: return hml_val_u16((uint16_t)value); + case HML_VAL_U32: return hml_val_u32((uint32_t)value); + case HML_VAL_U64: return hml_val_u64((uint64_t)value); + default: return hml_val_i64(value); + } +} + // Create an integer result value with the correct type HmlValue make_int_result(HmlValueType result_type, int64_t value) { switch (result_type) { @@ -119,9 +143,21 @@ HmlValue hml_binary_op(HmlBinaryOp op, HmlValue left, HmlValue right) { int32_t l = left.as.as_i32; int32_t r = right.as.as_i32; switch (op) { - case HML_OP_ADD: return hml_val_i32(l + r); - case HML_OP_SUB: return hml_val_i32(l - r); - case HML_OP_MUL: return hml_val_i32(l * r); + case HML_OP_ADD: { + int32_t result; + if (__builtin_add_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i32 addition"); + return hml_val_i32(result); + } + case HML_OP_SUB: { + int32_t result; + if (__builtin_sub_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i32 subtraction"); + return hml_val_i32(result); + } + case HML_OP_MUL: { + int32_t result; + if (__builtin_mul_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i32 multiplication"); + return hml_val_i32(result); + } case HML_OP_MOD: if (r == 0) hml_runtime_error("Division by zero"); return hml_val_i32(l % r); @@ -149,9 +185,21 @@ HmlValue hml_binary_op(HmlBinaryOp op, HmlValue left, HmlValue right) { int64_t l = left.as.as_i64; int64_t r = right.as.as_i64; switch (op) { - case HML_OP_ADD: return hml_val_i64(l + r); - case HML_OP_SUB: return hml_val_i64(l - r); - case HML_OP_MUL: return hml_val_i64(l * r); + case HML_OP_ADD: { + int64_t result; + if (__builtin_add_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i64 addition"); + return hml_val_i64(result); + } + case HML_OP_SUB: { + int64_t result; + if (__builtin_sub_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i64 subtraction"); + return hml_val_i64(result); + } + case HML_OP_MUL: { + int64_t result; + if (__builtin_mul_overflow(l, r, &result)) hml_runtime_error("Integer overflow: i64 multiplication"); + return hml_val_i64(result); + } case HML_OP_DIV: if (r == 0) hml_runtime_error("Division by zero"); return hml_val_i64(l / r); @@ -338,12 +386,24 @@ HmlValue hml_binary_op(HmlBinaryOp op, HmlValue left, HmlValue right) { int64_t r = hml_to_i64(right); switch (op) { - case HML_OP_ADD: - return make_int_result(result_type, l + r); - case HML_OP_SUB: - return make_int_result(result_type, l - r); - case HML_OP_MUL: - return make_int_result(result_type, l * r); + case HML_OP_ADD: { + int64_t result; + if (__builtin_add_overflow(l, r, &result)) + hml_runtime_error("Integer overflow: i64 addition"); + return make_int_result_checked(result_type, result); + } + case HML_OP_SUB: { + int64_t result; + if (__builtin_sub_overflow(l, r, &result)) + hml_runtime_error("Integer overflow: i64 subtraction"); + return make_int_result_checked(result_type, result); + } + case HML_OP_MUL: { + int64_t result; + if (__builtin_mul_overflow(l, r, &result)) + hml_runtime_error("Integer overflow: i64 multiplication"); + return make_int_result_checked(result_type, result); + } case HML_OP_DIV: if (r == 0) { hml_runtime_error("Division by zero"); @@ -403,9 +463,22 @@ HmlValue hml_unary_op(HmlUnaryOp op, HmlValue operand) { } else if (operand.type == HML_VAL_F32) { return hml_val_f32(-operand.as.as_f32); } else if (operand.type == HML_VAL_I64) { + if (operand.as.as_i64 == INT64_MIN) + hml_runtime_error("Integer overflow: i64 negation"); return hml_val_i64(-operand.as.as_i64); + } else if (operand.type == HML_VAL_I8) { + if (operand.as.as_i8 == INT8_MIN) + hml_runtime_error("Integer overflow: i8 negation"); + return hml_val_i8(-operand.as.as_i8); + } else if (operand.type == HML_VAL_I16) { + if (operand.as.as_i16 == INT16_MIN) + hml_runtime_error("Integer overflow: i16 negation"); + return hml_val_i16(-operand.as.as_i16); } else { - return hml_val_i32(-hml_to_i32(operand)); + int32_t v = hml_to_i32(operand); + if (v == INT32_MIN) + hml_runtime_error("Integer overflow: i32 negation"); + return hml_val_i32(-v); } case HML_UNARY_BIT_NOT: diff --git a/src/backends/compiler/codegen_expr.c b/src/backends/compiler/codegen_expr.c index 7c5b17a0..f2e1858d 100644 --- a/src/backends/compiler/codegen_expr.c +++ b/src/backends/compiler/codegen_expr.c @@ -937,21 +937,7 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) { } // Check if variable is unboxed (native C type) // Skip if captured variable - captured vars are always HmlValue in closure env - if (ctx->optimize && ctx->type_ctx && !codegen_is_main_var(ctx, raw_var) && - !is_captured_variable(ctx, raw_var)) { - CheckedTypeKind native_type = type_check_get_unboxable(ctx->type_ctx, raw_var); - if (native_type != CHECKED_UNKNOWN && checked_kind_is_numeric(native_type)) { - const char *box_func = checked_type_to_box_func(native_type); - if (box_func) { - // Unboxed variable - use simple C increment - codegen_writeln(ctx, "++%s;", var); - codegen_writeln(ctx, "HmlValue %s = %s(%s);", result, box_func, var); - if (safe_var) free(safe_var); - break; - } - } - } - // Fast path for i32, fallback to generic binary_op + // Fast path for i32 (with overflow check), fallback to generic binary_op codegen_writeln(ctx, "%s = %s.type == HML_VAL_I32 ? hml_i32_inc(%s) : hml_binary_op(HML_OP_ADD, %s, hml_val_i32(1));", var, var, var, var); // If captured variable, update closure environment if (ctx->current_closure && ctx->current_closure->num_captured > 0) { @@ -1023,23 +1009,7 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) { safe_var = codegen_sanitize_ident(raw_var); var = safe_var; } - // Check if variable is unboxed (native C type) - // Skip if captured variable - captured vars are always HmlValue in closure env - if (ctx->optimize && ctx->type_ctx && !codegen_is_main_var(ctx, raw_var) && - !is_captured_variable(ctx, raw_var)) { - CheckedTypeKind native_type = type_check_get_unboxable(ctx->type_ctx, raw_var); - if (native_type != CHECKED_UNKNOWN && checked_kind_is_numeric(native_type)) { - const char *box_func = checked_type_to_box_func(native_type); - if (box_func) { - // Unboxed variable - use simple C decrement - codegen_writeln(ctx, "--%s;", var); - codegen_writeln(ctx, "HmlValue %s = %s(%s);", result, box_func, var); - if (safe_var) free(safe_var); - break; - } - } - } - // Fast path for i32, fallback to generic binary_op + // Fast path for i32 (with overflow check), fallback to generic binary_op codegen_writeln(ctx, "%s = %s.type == HML_VAL_I32 ? hml_i32_dec(%s) : hml_binary_op(HML_OP_SUB, %s, hml_val_i32(1));", var, var, var, var); // If captured variable, update closure environment if (ctx->current_closure && ctx->current_closure->num_captured > 0) { @@ -1112,25 +1082,9 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) { safe_var = codegen_sanitize_ident(raw_var); var = safe_var; } - // Check if variable is unboxed (native C type) - // Skip if captured variable - captured vars are always HmlValue in closure env - if (ctx->optimize && ctx->type_ctx && !codegen_is_main_var(ctx, raw_var) && - !is_captured_variable(ctx, raw_var)) { - CheckedTypeKind native_type = type_check_get_unboxable(ctx->type_ctx, raw_var); - if (native_type != CHECKED_UNKNOWN && checked_kind_is_numeric(native_type)) { - const char *box_func = checked_type_to_box_func(native_type); - if (box_func) { - // Unboxed variable - return old value, then increment - codegen_writeln(ctx, "HmlValue %s = %s(%s);", result, box_func, var); - codegen_writeln(ctx, "%s++;", var); - if (safe_var) free(safe_var); - break; - } - } - } codegen_writeln(ctx, "HmlValue %s = %s;", result, var); codegen_writeln(ctx, "hml_retain_if_needed(&%s);", result); - // Fast path for i32, fallback to generic binary_op + // Fast path for i32 (with overflow check), fallback to generic binary_op codegen_writeln(ctx, "%s = %s.type == HML_VAL_I32 ? hml_i32_inc(%s) : hml_binary_op(HML_OP_ADD, %s, hml_val_i32(1));", var, var, var, var); // If captured variable, update closure environment if (ctx->current_closure && ctx->current_closure->num_captured > 0) { @@ -1200,25 +1154,9 @@ char* codegen_expr(CodegenContext *ctx, Expr *expr) { safe_var = codegen_sanitize_ident(raw_var); var = safe_var; } - // Check if variable is unboxed (native C type) - // Skip if captured variable - captured vars are always HmlValue in closure env - if (ctx->optimize && ctx->type_ctx && !codegen_is_main_var(ctx, raw_var) && - !is_captured_variable(ctx, raw_var)) { - CheckedTypeKind native_type = type_check_get_unboxable(ctx->type_ctx, raw_var); - if (native_type != CHECKED_UNKNOWN && checked_kind_is_numeric(native_type)) { - const char *box_func = checked_type_to_box_func(native_type); - if (box_func) { - // Unboxed variable - return old value, then decrement - codegen_writeln(ctx, "HmlValue %s = %s(%s);", result, box_func, var); - codegen_writeln(ctx, "%s--;", var); - if (safe_var) free(safe_var); - break; - } - } - } codegen_writeln(ctx, "HmlValue %s = %s;", result, var); codegen_writeln(ctx, "hml_retain_if_needed(&%s);", result); - // Fast path for i32, fallback to generic binary_op + // Fast path for i32 (with overflow check), fallback to generic binary_op codegen_writeln(ctx, "%s = %s.type == HML_VAL_I32 ? hml_i32_dec(%s) : hml_binary_op(HML_OP_SUB, %s, hml_val_i32(1));", var, var, var, var); // If captured variable, update closure environment if (ctx->current_closure && ctx->current_closure->num_captured > 0) { diff --git a/src/backends/compiler/codegen_expr_binary.c b/src/backends/compiler/codegen_expr_binary.c index 199b768c..e20f88b4 100644 --- a/src/backends/compiler/codegen_expr_binary.c +++ b/src/backends/compiler/codegen_expr_binary.c @@ -78,6 +78,9 @@ char* codegen_native_expr(CodegenContext *ctx, Expr *expr, CheckedTypeKind *out_ if (expr->as.binary.op == OP_AND || expr->as.binary.op == OP_OR) return NULL; // Shifts need validation (negative/oversized) - use safe runtime path if (expr->as.binary.op == OP_BIT_LSHIFT || expr->as.binary.op == OP_BIT_RSHIFT) return NULL; + // Arithmetic ops need overflow/zero checks - use safe runtime path + if (expr->as.binary.op == OP_ADD || expr->as.binary.op == OP_SUB || + expr->as.binary.op == OP_MUL || expr->as.binary.op == OP_MOD) return NULL; CheckedTypeKind left_type, right_type; char *left = codegen_native_expr(ctx, expr->as.binary.left, &left_type); @@ -517,21 +520,11 @@ char* codegen_expr_binary(CodegenContext *ctx, Expr *expr, char *result) { switch (expr->as.binary.op) { case OP_ADD: - codegen_writeln(ctx, "HmlValue %s = %s(%s + %s);", result, box_func, left_var, right_var); - break; case OP_SUB: - codegen_writeln(ctx, "HmlValue %s = %s(%s - %s);", result, box_func, left_var, right_var); - break; case OP_MUL: - codegen_writeln(ctx, "HmlValue %s = %s(%s * %s);", result, box_func, left_var, right_var); - break; case OP_MOD: - if (checked_kind_is_integer(left_native)) { - codegen_writeln(ctx, "if (%s == 0) hml_runtime_error(\"Division by zero\");", right_var); - codegen_writeln(ctx, "HmlValue %s = %s(%s %% %s);", result, box_func, left_var, right_var); - } else { - codegen_writeln(ctx, "HmlValue %s = hml_val_f64(fmod(%s, %s));", result, left_var, right_var); - } + // Arithmetic ops need overflow/zero checks - fall through to safe runtime path + handled = 0; break; case OP_DIV: // Division always returns float; check for zero with integer operands diff --git a/src/backends/interpreter/runtime/binary_ops.c b/src/backends/interpreter/runtime/binary_ops.c index e8636d42..d5b149c9 100644 --- a/src/backends/interpreter/runtime/binary_ops.c +++ b/src/backends/interpreter/runtime/binary_ops.c @@ -625,11 +625,27 @@ Value eval_binary_expr(Expr *expr, Environment *env, ExecutionContext *ctx) { runtime_error_at(ctx, expr->line, "Division by zero"); goto binary_cleanup; } - int8_t result = (expr->as.binary.op == OP_ADD) ? (l + r) : - (expr->as.binary.op == OP_SUB) ? (l - r) : - (expr->as.binary.op == OP_MUL) ? (l * r) : - (expr->as.binary.op == OP_DIV) ? (l / r) : (l % r); - binary_result = val_i8(result); + if (expr->as.binary.op == OP_DIV) { + if (l == INT8_MIN && r == -1) { + runtime_error_at(ctx, expr->line, "Integer overflow: i8 arithmetic"); + goto binary_cleanup; + } + binary_result = val_i8(l / r); + goto binary_cleanup; + } + if (expr->as.binary.op == OP_MOD) { + binary_result = val_i8(l % r); + goto binary_cleanup; + } + // Overflow-checked add/sub/mul for i8 + int32_t wide = (expr->as.binary.op == OP_ADD) ? ((int32_t)l + r) : + (expr->as.binary.op == OP_SUB) ? ((int32_t)l - r) : + ((int32_t)l * r); + if (wide < INT8_MIN || wide > INT8_MAX) { + runtime_error_at(ctx, expr->line, "Integer overflow: i8 arithmetic"); + goto binary_cleanup; + } + binary_result = val_i8((int8_t)wide); goto binary_cleanup; } case VAL_I16: { @@ -639,11 +655,27 @@ Value eval_binary_expr(Expr *expr, Environment *env, ExecutionContext *ctx) { runtime_error_at(ctx, expr->line, "Division by zero"); goto binary_cleanup; } - int16_t result = (expr->as.binary.op == OP_ADD) ? (l + r) : - (expr->as.binary.op == OP_SUB) ? (l - r) : - (expr->as.binary.op == OP_MUL) ? (l * r) : - (expr->as.binary.op == OP_DIV) ? (l / r) : (l % r); - binary_result = val_i16(result); + if (expr->as.binary.op == OP_DIV) { + if (l == INT16_MIN && r == -1) { + runtime_error_at(ctx, expr->line, "Integer overflow: i16 arithmetic"); + goto binary_cleanup; + } + binary_result = val_i16(l / r); + goto binary_cleanup; + } + if (expr->as.binary.op == OP_MOD) { + binary_result = val_i16(l % r); + goto binary_cleanup; + } + // Overflow-checked add/sub/mul for i16 + int32_t wide = (expr->as.binary.op == OP_ADD) ? ((int32_t)l + r) : + (expr->as.binary.op == OP_SUB) ? ((int32_t)l - r) : + ((int32_t)l * r); + if (wide < INT16_MIN || wide > INT16_MAX) { + runtime_error_at(ctx, expr->line, "Integer overflow: i16 arithmetic"); + goto binary_cleanup; + } + binary_result = val_i16((int16_t)wide); goto binary_cleanup; } case VAL_I32: { diff --git a/src/backends/interpreter/runtime/expressions.c b/src/backends/interpreter/runtime/expressions.c index 424503aa..942cebcc 100644 --- a/src/backends/interpreter/runtime/expressions.c +++ b/src/backends/interpreter/runtime/expressions.c @@ -45,12 +45,35 @@ Value value_add_one(Value val, ExecutionContext *ctx) { double v = value_to_float(val); return (val.type == VAL_F32) ? val_f32((float)(v + 1.0)) : val_f64(v + 1.0); } else if (is_integer(val)) { - int64_t v = value_to_int(val); - ValueType result_type = val.type; - return promote_value(val_i32((int32_t)(v + 1)), result_type); + // Overflow-checked increment for each integer type + switch (val.type) { + case VAL_I8: + if (val.as.as_i8 == INT8_MAX) { runtime_error(ctx, "Integer overflow: i8 increment"); return val_null(); } + return val_i8(val.as.as_i8 + 1); + case VAL_I16: + if (val.as.as_i16 == INT16_MAX) { runtime_error(ctx, "Integer overflow: i16 increment"); return val_null(); } + return val_i16(val.as.as_i16 + 1); + case VAL_I32: { + int32_t result; + if (__builtin_add_overflow(val.as.as_i32, 1, &result)) { runtime_error(ctx, "Integer overflow: i32 increment"); return val_null(); } + return val_i32(result); + } + case VAL_I64: { + int64_t result; + if (__builtin_add_overflow(val.as.as_i64, (int64_t)1, &result)) { runtime_error(ctx, "Integer overflow: i64 increment"); return val_null(); } + return val_i64(result); + } + case VAL_U8: return val_u8(val.as.as_u8 + 1); // unsigned wraps (well-defined) + case VAL_U16: return val_u16(val.as.as_u16 + 1); + case VAL_U32: return val_u32(val.as.as_u32 + 1); + case VAL_U64: return val_u64(val.as.as_u64 + 1); + default: break; + } + runtime_error(ctx, "Can only increment numeric values"); + return val_null(); } else { runtime_error(ctx, "Can only increment numeric values"); - return val_null(); // Unreachable + return val_null(); } } @@ -60,12 +83,35 @@ Value value_sub_one(Value val, ExecutionContext *ctx) { double v = value_to_float(val); return (val.type == VAL_F32) ? val_f32((float)(v - 1.0)) : val_f64(v - 1.0); } else if (is_integer(val)) { - int64_t v = value_to_int(val); - ValueType result_type = val.type; - return promote_value(val_i32((int32_t)(v - 1)), result_type); + // Overflow-checked decrement for each integer type + switch (val.type) { + case VAL_I8: + if (val.as.as_i8 == INT8_MIN) { runtime_error(ctx, "Integer overflow: i8 decrement"); return val_null(); } + return val_i8(val.as.as_i8 - 1); + case VAL_I16: + if (val.as.as_i16 == INT16_MIN) { runtime_error(ctx, "Integer overflow: i16 decrement"); return val_null(); } + return val_i16(val.as.as_i16 - 1); + case VAL_I32: { + int32_t result; + if (__builtin_sub_overflow(val.as.as_i32, 1, &result)) { runtime_error(ctx, "Integer overflow: i32 decrement"); return val_null(); } + return val_i32(result); + } + case VAL_I64: { + int64_t result; + if (__builtin_sub_overflow(val.as.as_i64, (int64_t)1, &result)) { runtime_error(ctx, "Integer overflow: i64 decrement"); return val_null(); } + return val_i64(result); + } + case VAL_U8: return val_u8(val.as.as_u8 - 1); // unsigned wraps (well-defined) + case VAL_U16: return val_u16(val.as.as_u16 - 1); + case VAL_U32: return val_u32(val.as.as_u32 - 1); + case VAL_U64: return val_u64(val.as.as_u64 - 1); + default: break; + } + runtime_error(ctx, "Can only decrement numeric values"); + return val_null(); } else { runtime_error(ctx, "Can only decrement numeric values"); - return val_null(); // Unreachable + return val_null(); } } @@ -116,12 +162,20 @@ Value eval_expr(Expr *expr, Environment *env, ExecutionContext *ctx) { if (is_float(operand)) { unary_result = val_f64(-value_to_float(operand)); } else if (is_integer(operand)) { - // Preserve the original type when negating + // Preserve the original type when negating (with overflow checks for INT_MIN) switch (operand.type) { - case VAL_I8: unary_result = val_i8(-operand.as.as_i8); break; - case VAL_I16: unary_result = val_i16(-operand.as.as_i16); break; - case VAL_I32: unary_result = val_i32(-operand.as.as_i32); break; - case VAL_I64: unary_result = val_i64(-operand.as.as_i64); break; + case VAL_I8: + if (operand.as.as_i8 == INT8_MIN) { runtime_error(ctx, "Integer overflow: i8 negation"); break; } + unary_result = val_i8(-operand.as.as_i8); break; + case VAL_I16: + if (operand.as.as_i16 == INT16_MIN) { runtime_error(ctx, "Integer overflow: i16 negation"); break; } + unary_result = val_i16(-operand.as.as_i16); break; + case VAL_I32: + if (operand.as.as_i32 == INT32_MIN) { runtime_error(ctx, "Integer overflow: i32 negation"); break; } + unary_result = val_i32(-operand.as.as_i32); break; + case VAL_I64: + if (operand.as.as_i64 == INT64_MIN) { runtime_error(ctx, "Integer overflow: i64 negation"); break; } + unary_result = val_i64(-operand.as.as_i64); break; case VAL_U8: unary_result = val_i16(-(int16_t)operand.as.as_u8); break; // promote to i16 case VAL_U16: unary_result = val_i32(-(int32_t)operand.as.as_u16); break; // promote to i32 case VAL_U32: unary_result = val_i64(-(int64_t)operand.as.as_u32); break; // promote to i64 diff --git a/tests/parity/language/ub_arithmetic_overflow.expected b/tests/parity/language/ub_arithmetic_overflow.expected new file mode 100644 index 00000000..3564b067 --- /dev/null +++ b/tests/parity/language/ub_arithmetic_overflow.expected @@ -0,0 +1,11 @@ +i8 add overflow: caught +i8 sub overflow: caught +i8 mul overflow: caught +i8 add ok: 127 +i16 add overflow: caught +i16 sub overflow: caught +i16 mul overflow: caught +i16 add ok: 32767 +i64 add overflow: caught +i64 sub overflow: caught +PASS diff --git a/tests/parity/language/ub_arithmetic_overflow.hml b/tests/parity/language/ub_arithmetic_overflow.hml new file mode 100644 index 00000000..12d9a86b --- /dev/null +++ b/tests/parity/language/ub_arithmetic_overflow.hml @@ -0,0 +1,89 @@ +// Test: arithmetic overflow detection for all signed integer types +// Ensures C undefined behavior is caught as runtime errors + +// === i8 overflow === +try { + let a: i8 = 127; + let b: i8 = 1; + let c = a + b; + print("FAIL: i8 add overflow not caught"); +} catch (e) { + print("i8 add overflow: caught"); +} + +try { + let a: i8 = -128; + let b: i8 = 1; + let c = a - b; + print("FAIL: i8 sub overflow not caught"); +} catch (e) { + print("i8 sub overflow: caught"); +} + +try { + let a: i8 = 127; + let b: i8 = 2; + let c = a * b; + print("FAIL: i8 mul overflow not caught"); +} catch (e) { + print("i8 mul overflow: caught"); +} + +// i8 within range (should succeed) +let x: i8 = 100; +let y: i8 = 27; +print("i8 add ok: " + (x + y)); + +// === i16 overflow === +try { + let a: i16 = 32767; + let b: i16 = 1; + let c = a + b; + print("FAIL: i16 add overflow not caught"); +} catch (e) { + print("i16 add overflow: caught"); +} + +try { + let a: i16 = -32768; + let b: i16 = 1; + let c = a - b; + print("FAIL: i16 sub overflow not caught"); +} catch (e) { + print("i16 sub overflow: caught"); +} + +try { + let a: i16 = 32767; + let b: i16 = 2; + let c = a * b; + print("FAIL: i16 mul overflow not caught"); +} catch (e) { + print("i16 mul overflow: caught"); +} + +// i16 within range (should succeed) +let p: i16 = 32000; +let q: i16 = 767; +print("i16 add ok: " + (p + q)); + +// === i64 overflow === +try { + let a: i64 = 9223372036854775807; + let b: i64 = 1; + let c = a + b; + print("FAIL: i64 add overflow not caught"); +} catch (e) { + print("i64 add overflow: caught"); +} + +try { + let a: i64 = -9223372036854775807; + let b: i64 = a - 1; // b = INT64_MIN + let c = b - 1; // overflow + print("FAIL: i64 sub overflow not caught"); +} catch (e) { + print("i64 sub overflow: caught"); +} + +print("PASS"); diff --git a/tests/parity/language/ub_increment_overflow.expected b/tests/parity/language/ub_increment_overflow.expected new file mode 100644 index 00000000..88a8b587 --- /dev/null +++ b/tests/parity/language/ub_increment_overflow.expected @@ -0,0 +1,7 @@ +i32 inc overflow: caught +i32 dec underflow: caught +i32 prefix inc overflow: caught +i32 prefix dec underflow: caught +normal inc: 101 +normal dec: 100 +PASS diff --git a/tests/parity/language/ub_increment_overflow.hml b/tests/parity/language/ub_increment_overflow.hml new file mode 100644 index 00000000..8542f2bd --- /dev/null +++ b/tests/parity/language/ub_increment_overflow.hml @@ -0,0 +1,47 @@ +// Test: increment/decrement overflow detection +// Ensures ++/-- at integer boundaries are caught + +// === i32 increment overflow === +try { + let x: i32 = 2147483647; + x++; + print("FAIL: i32 inc overflow not caught"); +} catch (e) { + print("i32 inc overflow: caught"); +} + +// === i32 decrement underflow === +try { + let x: i32 = -2147483648; + x--; + print("FAIL: i32 dec underflow not caught"); +} catch (e) { + print("i32 dec underflow: caught"); +} + +// === prefix increment overflow === +try { + let x: i32 = 2147483647; + ++x; + print("FAIL: i32 prefix inc overflow not caught"); +} catch (e) { + print("i32 prefix inc overflow: caught"); +} + +// === prefix decrement underflow === +try { + let x: i32 = -2147483648; + --x; + print("FAIL: i32 prefix dec underflow not caught"); +} catch (e) { + print("i32 prefix dec underflow: caught"); +} + +// Normal increment/decrement (should work) +let a: i32 = 100; +a++; +print("normal inc: " + a); +a--; +print("normal dec: " + a); + +print("PASS"); diff --git a/tests/parity/language/ub_negation_overflow.expected b/tests/parity/language/ub_negation_overflow.expected new file mode 100644 index 00000000..2983ad43 --- /dev/null +++ b/tests/parity/language/ub_negation_overflow.expected @@ -0,0 +1,7 @@ +i32 negation overflow: caught +i64 negation overflow: caught +i8 negation overflow: caught +i16 negation overflow: caught +negate 42: -42 +negate -100: 100 +PASS diff --git a/tests/parity/language/ub_negation_overflow.hml b/tests/parity/language/ub_negation_overflow.hml new file mode 100644 index 00000000..ba4ecd51 --- /dev/null +++ b/tests/parity/language/ub_negation_overflow.hml @@ -0,0 +1,47 @@ +// Test: negation of minimum integer values (INT_MIN) +// -INT_MIN is undefined behavior in C + +// === i32 negation overflow === +try { + let x: i32 = -2147483648; + let y = -x; + print("FAIL: i32 negation overflow not caught"); +} catch (e) { + print("i32 negation overflow: caught"); +} + +// === i64 negation overflow === +try { + let a: i64 = -9223372036854775807; + let x: i64 = a - 1; // x = INT64_MIN + let y = -x; + print("FAIL: i64 negation overflow not caught"); +} catch (e) { + print("i64 negation overflow: caught"); +} + +// === i8 negation overflow === +try { + let x: i8 = -128; + let y = -x; + print("FAIL: i8 negation overflow not caught"); +} catch (e) { + print("i8 negation overflow: caught"); +} + +// === i16 negation overflow === +try { + let x: i16 = -32768; + let y = -x; + print("FAIL: i16 negation overflow not caught"); +} catch (e) { + print("i16 negation overflow: caught"); +} + +// Normal negation (should work) +let a: i32 = 42; +print("negate 42: " + (-a)); +let b: i32 = -100; +print("negate -100: " + (-b)); + +print("PASS");