Procházet zdrojové kódy

Produce error diagnostics if overflow occurs in constant folding (#1488)

The negate operator does not check for overflow, add this too.

Fixes https://github.com/hyperledger/solang/issues/1484

Signed-off-by: Sean Young <sean@mess.org>
Co-authored-by: Lucas Steuernagel <38472950+LucasSte@users.noreply.github.com>
Sean Young před 2 roky
rodič
revize
37e7301ad9

+ 7 - 5
src/codegen/cfg.rs

@@ -572,7 +572,7 @@ impl ControlFlowGraph {
                 Expression::Subtract {
                     loc,
                     ty: Type::Uint(32),
-                    overflowing: false,
+                    overflowing: true,
                     left: Box::new(Expression::Variable {
                         loc,
                         ty: Type::Uint(32),
@@ -588,7 +588,7 @@ impl ControlFlowGraph {
                 Expression::Add {
                     loc,
                     ty: Type::Uint(32),
-                    overflowing: false,
+                    overflowing: true,
                     left: Box::new(Expression::Variable {
                         loc,
                         ty: Type::Uint(32),
@@ -1557,9 +1557,11 @@ pub fn optimize_and_check_cfg(
             return;
         }
     }
-    if opt.constant_folding {
-        constant_folding::constant_folding(cfg, ns);
-    }
+
+    // constant folding generates diagnostics, so always run it. This means that the diagnostics
+    // do not depend which passes are enabled. If the constant_folding is not enabled, run it
+    // dry mode.
+    constant_folding::constant_folding(cfg, !opt.constant_folding, ns);
     if opt.vector_to_slice {
         vector_to_slice::vector_to_slice(cfg, ns);
     }

+ 233 - 156
src/codegen/constant_folding.rs

@@ -3,8 +3,10 @@
 use super::cfg::{ControlFlowGraph, Instr};
 use super::reaching_definitions;
 use crate::codegen::{Builtin, Expression};
-use crate::sema::ast::RetrieveType;
-use crate::sema::ast::{Diagnostic, Namespace, StringLocation, Type};
+use crate::sema::{
+    ast::{Diagnostic, Namespace, RetrieveType, StringLocation, Type},
+    eval::overflow_diagnostic,
+};
 use num_bigint::{BigInt, Sign};
 use num_traits::{ToPrimitive, Zero};
 use ripemd::Ripemd160;
@@ -15,9 +17,10 @@ use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
 use tiny_keccak::{Hasher, Keccak};
 
 /// Constant folding pass on the given cfg. During constant folding, we may find issues
-/// like divide by zero, so this function returns a list of diagnostics which should
-/// be added to the namespace.
-pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
+/// like divide by zero, so this function may add diagnostics to the namespace. If dry_run
+/// is true, then diagnostics are generated but the CFG is not modified. This
+///
+pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Namespace) {
     // for each block, instruction
     for block_no in 0..cfg.blocks.len() {
         let mut vars = cfg.blocks[block_no].defs.clone();
@@ -31,11 +34,13 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         ns.var_constants.insert(*loc, expr.clone());
                     }
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Set {
-                        loc: *loc,
-                        res: *res,
-                        expr,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Set {
+                            loc: *loc,
+                            res: *res,
+                            expr,
+                        };
+                    }
                 }
                 Instr::Call {
                     res,
@@ -48,12 +53,14 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .map(|e| expression(e, Some(&vars), cfg, ns).0)
                         .collect();
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Call {
-                        res: res.clone(),
-                        call: call.clone(),
-                        args,
-                        return_tys: return_tys.clone(),
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Call {
+                            res: res.clone(),
+                            call: call.clone(),
+                            args,
+                            return_tys: return_tys.clone(),
+                        };
+                    }
                 }
                 Instr::Return { value } => {
                     let value = value
@@ -61,7 +68,9 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .map(|e| expression(e, Some(&vars), cfg, ns).0)
                         .collect();
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Return { value };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Return { value };
+                    }
                 }
                 Instr::BranchCond {
                     cond,
@@ -70,64 +79,78 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                 } => {
                     let (cond, _) = expression(cond, Some(&vars), cfg, ns);
 
-                    if let Expression::BoolLiteral { value: cond, .. } = cond {
-                        cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
-                            block: if cond { *true_block } else { *false_block },
-                        };
-                    } else {
-                        cfg.blocks[block_no].instr[instr_no] = Instr::BranchCond {
-                            cond,
-                            true_block: *true_block,
-                            false_block: *false_block,
-                        };
+                    if !dry_run {
+                        if let Expression::BoolLiteral { value: cond, .. } = cond {
+                            cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
+                                block: if cond { *true_block } else { *false_block },
+                            };
+                        } else {
+                            cfg.blocks[block_no].instr[instr_no] = Instr::BranchCond {
+                                cond,
+                                true_block: *true_block,
+                                false_block: *false_block,
+                            };
+                        }
                     }
                 }
                 Instr::Store { dest, data } => {
                     let (dest, _) = expression(dest, Some(&vars), cfg, ns);
                     let (data, _) = expression(data, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Store { dest, data };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Store { dest, data };
+                    }
                 }
                 Instr::AssertFailure {
                     encoded_args: Some(expr),
                 } => {
                     let (buf, _) = expression(expr, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::AssertFailure {
-                        encoded_args: Some(buf),
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::AssertFailure {
+                            encoded_args: Some(buf),
+                        };
+                    }
                 }
                 Instr::Print { expr } => {
                     let (expr, _) = expression(expr, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Print { expr };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Print { expr };
+                    }
                 }
                 Instr::ClearStorage { ty, storage } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::ClearStorage {
-                        ty: ty.clone(),
-                        storage,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::ClearStorage {
+                            ty: ty.clone(),
+                            storage,
+                        };
+                    }
                 }
                 Instr::SetStorage { ty, storage, value } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
                     let (value, _) = expression(value, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::SetStorage {
-                        ty: ty.clone(),
-                        storage,
-                        value,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::SetStorage {
+                            ty: ty.clone(),
+                            storage,
+                            value,
+                        };
+                    }
                 }
                 Instr::LoadStorage { ty, storage, res } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::LoadStorage {
-                        ty: ty.clone(),
-                        storage,
-                        res: *res,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::LoadStorage {
+                            ty: ty.clone(),
+                            storage,
+                            res: *res,
+                        };
+                    }
                 }
                 Instr::SetStorageBytes {
                     storage,
@@ -138,11 +161,13 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                     let (value, _) = expression(value, Some(&vars), cfg, ns);
                     let (offset, _) = expression(offset, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::SetStorageBytes {
-                        storage,
-                        value,
-                        offset,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::SetStorageBytes {
+                            storage,
+                            value,
+                            offset,
+                        };
+                    }
                 }
                 Instr::PushStorage {
                     res,
@@ -155,21 +180,25 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .as_ref()
                         .map(|expr| expression(expr, Some(&vars), cfg, ns).0);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::PushStorage {
-                        res: *res,
-                        ty: ty.clone(),
-                        storage,
-                        value,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::PushStorage {
+                            res: *res,
+                            ty: ty.clone(),
+                            storage,
+                            value,
+                        };
+                    }
                 }
                 Instr::PopStorage { res, ty, storage } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::PopStorage {
-                        res: *res,
-                        ty: ty.clone(),
-                        storage,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::PopStorage {
+                            res: *res,
+                            ty: ty.clone(),
+                            storage,
+                        };
+                    }
                 }
                 Instr::PushMemory {
                     res,
@@ -179,12 +208,14 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                 } => {
                     let (value, _) = expression(value, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::PushMemory {
-                        res: *res,
-                        ty: ty.clone(),
-                        array: *array,
-                        value: Box::new(value),
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::PushMemory {
+                            res: *res,
+                            ty: ty.clone(),
+                            array: *array,
+                            value: Box::new(value),
+                        };
+                    }
                 }
                 Instr::Constructor {
                     success,
@@ -218,20 +249,22 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .as_ref()
                         .map(|expr| expression(expr, Some(&vars), cfg, ns).0);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Constructor {
-                        success: *success,
-                        res: *res,
-                        contract_no: *contract_no,
-                        constructor_no: *constructor_no,
-                        encoded_args,
-                        value,
-                        gas,
-                        salt,
-                        address,
-                        seeds,
-                        loc: *loc,
-                        accounts,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Constructor {
+                            success: *success,
+                            res: *res,
+                            contract_no: *contract_no,
+                            constructor_no: *constructor_no,
+                            encoded_args,
+                            value,
+                            gas,
+                            salt,
+                            address,
+                            seeds,
+                            loc: *loc,
+                            accounts,
+                        };
+                    }
                 }
                 Instr::ExternalCall {
                     success,
@@ -261,23 +294,27 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .as_ref()
                         .map(|expr| expression(expr, Some(&vars), cfg, ns).0);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::ExternalCall {
-                        success: *success,
-                        address,
-                        accounts,
-                        seeds,
-                        payload,
-                        value,
-                        gas,
-                        callty: callty.clone(),
-                        contract_function_no: *contract_function_no,
-                        flags,
-                    };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::ExternalCall {
+                            success: *success,
+                            address,
+                            accounts,
+                            seeds,
+                            payload,
+                            value,
+                            gas,
+                            callty: callty.clone(),
+                            contract_function_no: *contract_function_no,
+                            flags,
+                        };
+                    }
                 }
                 Instr::SelfDestruct { recipient } => {
                     let (recipient, _) = expression(recipient, Some(&vars), cfg, ns);
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::SelfDestruct { recipient };
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::SelfDestruct { recipient };
+                    }
                 }
                 Instr::EmitEvent {
                     event_no,
@@ -289,10 +326,12 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .map(|e| expression(e, Some(&vars), cfg, ns).0)
                         .collect();
 
-                    cfg.blocks[block_no].instr[instr_no] = Instr::EmitEvent {
-                        event_no: *event_no,
-                        data: expression(data, Some(&vars), cfg, ns).0,
-                        topics,
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::EmitEvent {
+                            event_no: *event_no,
+                            data: expression(data, Some(&vars), cfg, ns).0,
+                            topics,
+                        };
                     }
                 }
                 Instr::MemCopy {
@@ -303,11 +342,14 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                     let bytes = expression(bytes, Some(&vars), cfg, ns);
                     let source = expression(source, Some(&vars), cfg, ns);
                     let destination = expression(destination, Some(&vars), cfg, ns);
-                    cfg.blocks[block_no].instr[instr_no] = Instr::MemCopy {
-                        source: source.0,
-                        destination: destination.0,
-                        bytes: bytes.0,
-                    };
+
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::MemCopy {
+                            source: source.0,
+                            destination: destination.0,
+                            bytes: bytes.0,
+                        };
+                    }
                 }
                 Instr::Switch {
                     cond,
@@ -320,43 +362,53 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         .map(|(exp, goto)| (expression(exp, Some(&vars), cfg, ns).0, *goto))
                         .collect::<Vec<(Expression, usize)>>();
 
-                    if let Expression::NumberLiteral { value: num, .. } = &cond.0 {
-                        let mut simplified_branch = None;
-                        for (match_item, block) in &cases {
-                            if let Expression::NumberLiteral {
-                                value: match_num, ..
-                            } = match_item
-                            {
-                                if match_num == num {
-                                    simplified_branch = Some(*block);
+                    if !dry_run {
+                        if let Expression::NumberLiteral { value: num, .. } = &cond.0 {
+                            let mut simplified_branch = None;
+                            for (match_item, block) in &cases {
+                                if let Expression::NumberLiteral {
+                                    value: match_num, ..
+                                } = match_item
+                                {
+                                    if match_num == num {
+                                        simplified_branch = Some(*block);
+                                    }
                                 }
                             }
+                            cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
+                                block: simplified_branch.unwrap_or(*default),
+                            };
+                            break;
                         }
-                        cfg.blocks[block_no].instr[instr_no] = Instr::Branch {
-                            block: simplified_branch.unwrap_or(*default),
+
+                        cfg.blocks[block_no].instr[instr_no] = Instr::Switch {
+                            cond: cond.0,
+                            cases,
+                            default: *default,
                         };
-                        continue;
                     }
-
-                    cfg.blocks[block_no].instr[instr_no] = Instr::Switch {
-                        cond: cond.0,
-                        cases,
-                        default: *default,
-                    };
                 }
                 Instr::ReturnData { data, data_len } => {
                     let data = expression(data, Some(&vars), cfg, ns);
                     let data_len = expression(data_len, Some(&vars), cfg, ns);
-                    cfg.blocks[block_no].instr[instr_no] = Instr::ReturnData {
-                        data: data.0,
-                        data_len: data_len.0,
-                    };
+
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::ReturnData {
+                            data: data.0,
+                            data_len: data_len.0,
+                        };
+                    }
                 }
                 Instr::WriteBuffer { buf, offset, value } => {
-                    cfg.blocks[block_no].instr[instr_no] = Instr::WriteBuffer {
-                        buf: buf.clone(),
-                        offset: expression(offset, Some(&vars), cfg, ns).0,
-                        value: expression(value, Some(&vars), cfg, ns).0,
+                    let offset = expression(offset, Some(&vars), cfg, ns).0;
+                    let value = expression(value, Some(&vars), cfg, ns).0;
+
+                    if !dry_run {
+                        cfg.blocks[block_no].instr[instr_no] = Instr::WriteBuffer {
+                            buf: buf.clone(),
+                            offset,
+                            value,
+                        };
                     }
                 }
                 _ => (),
@@ -472,7 +524,12 @@ fn expression(
         Expression::SignExt { loc, ty, expr } => sign_ext(loc, ty, expr, vars, cfg, ns),
         Expression::Trunc { loc, ty, expr } => trunc(loc, ty, expr, vars, cfg, ns),
         Expression::BitwiseNot { loc, ty, expr } => bitwise_not(loc, ty, expr, vars, cfg, ns),
-        Expression::Negate { loc, ty, expr } => negate(loc, ty, expr, vars, cfg, ns),
+        Expression::Negate {
+            loc,
+            ty,
+            overflowing,
+            expr,
+        } => negate(loc, ty, expr, *overflowing, vars, cfg, ns),
         Expression::Variable { loc, ty, var_no } => {
             reference_variable(loc, *var_no, ty, vars, cfg, ns, expr)
         }
@@ -604,29 +661,47 @@ fn expression(
     }
 }
 
-fn bigint_to_expression(loc: &Loc, ty: &Type, n: BigInt) -> (Expression, bool) {
-    let n = match ty {
+fn bigint_to_expression(
+    loc: &Loc,
+    ty: &Type,
+    value: BigInt,
+    overflowing: bool,
+    ns: &mut Namespace,
+) -> (Expression, bool) {
+    if !overflowing {
+        if let Some(diagnostic) = overflow_diagnostic(&value, ty, loc) {
+            ns.diagnostics.push(diagnostic);
+        }
+    }
+
+    let value = match ty {
         Type::Uint(bits) => {
-            if n.bits() > *bits as u64 {
-                let (_, mut bs) = n.to_bytes_le();
+            if value.sign() == Sign::Minus {
+                let mut bs = value.to_signed_bytes_le();
+                bs.resize(*bits as usize / 8, 0xff);
+
+                BigInt::from_bytes_le(Sign::Plus, &bs)
+            } else if value.bits() > *bits as u64 {
+                let (_, mut bs) = value.to_bytes_le();
                 bs.truncate(*bits as usize / 8);
 
                 BigInt::from_bytes_le(Sign::Plus, &bs)
             } else {
-                n
+                value
             }
         }
         Type::Int(bits) => {
-            if n.bits() > *bits as u64 {
-                let mut bs = n.to_signed_bytes_le();
+            let mut bs = value.to_signed_bytes_le();
+
+            if bs.len() * 8 > *bits as usize {
                 bs.truncate(*bits as usize / 8);
 
                 BigInt::from_signed_bytes_le(&bs)
             } else {
-                n
+                value
             }
         }
-        Type::StorageRef(..) => n,
+        Type::StorageRef(..) => value,
         _ => unreachable!(),
     };
 
@@ -634,7 +709,7 @@ fn bigint_to_expression(loc: &Loc, ty: &Type, n: BigInt) -> (Expression, bool) {
         Expression::NumberLiteral {
             loc: *loc,
             ty: ty.clone(),
-            value: n,
+            value,
         },
         true,
     )
@@ -692,7 +767,7 @@ fn add(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.add(right))
+        bigint_to_expression(loc, ty, left.add(right), overflowing, ns)
     } else {
         (
             Expression::Add {
@@ -725,7 +800,7 @@ fn subtract(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.sub(right))
+        bigint_to_expression(loc, ty, left.sub(right), overflowing, ns)
     } else {
         (
             Expression::Subtract {
@@ -782,7 +857,7 @@ fn multiply(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.mul(right))
+        bigint_to_expression(loc, ty, left.mul(right), overflowing, ns)
     } else {
         (
             Expression::Multiply {
@@ -814,7 +889,7 @@ fn bitwise_and(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.bitand(right))
+        bigint_to_expression(loc, ty, left.bitand(right), true, ns)
     } else {
         (
             Expression::BitwiseAnd {
@@ -845,7 +920,7 @@ fn bitwise_or(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.bitor(right))
+        bigint_to_expression(loc, ty, left.bitor(right), true, ns)
     } else {
         (
             Expression::BitwiseOr {
@@ -876,7 +951,7 @@ fn bitwise_xor(
         Expression::NumberLiteral { value: right, .. },
     ) = (&left.0, &right.0)
     {
-        bigint_to_expression(loc, ty, left.bitxor(right))
+        bigint_to_expression(loc, ty, left.bitxor(right), true, ns)
     } else {
         (
             Expression::BitwiseXor {
@@ -915,7 +990,7 @@ fn shift_left(
         } else {
             let right: u64 = right.to_u64().unwrap();
 
-            return bigint_to_expression(loc, ty, left.shl(&right));
+            return bigint_to_expression(loc, ty, left.shl(&right), true, ns);
         }
     }
     (
@@ -955,7 +1030,7 @@ fn shift_right(
         } else {
             let right: u64 = right.to_u64().unwrap();
 
-            return bigint_to_expression(loc, ty, left.shr(&right));
+            return bigint_to_expression(loc, ty, left.shr(&right), true, ns);
         }
     }
 
@@ -989,7 +1064,7 @@ fn power(
         Expression::NumberLiteral { value: right, .. },
     ) = (&base.0, &exp.0)
     {
-        if right.sign() == Sign::Minus || right >= &BigInt::from(u32::MAX) {
+        if right.sign() == Sign::Minus || right >= &BigInt::from(u16::MAX) {
             ns.diagnostics.push(Diagnostic::error(
                 *loc,
                 format!("power {right} not possible"),
@@ -997,7 +1072,7 @@ fn power(
         } else {
             let right: u32 = right.to_u32().unwrap();
 
-            return bigint_to_expression(loc, ty, left.pow(right));
+            return bigint_to_expression(loc, ty, left.pow(right), overflowing, ns);
         }
     }
 
@@ -1031,7 +1106,7 @@ fn divide(
             ns.diagnostics
                 .push(Diagnostic::error(*loc, String::from("divide by zero")));
         } else if let Expression::NumberLiteral { value: left, .. } = &left.0 {
-            return bigint_to_expression(loc, ty, left.div(right));
+            return bigint_to_expression(loc, ty, left.div(right), false, ns);
         }
     }
     (
@@ -1072,7 +1147,7 @@ fn modulo(
             ns.diagnostics
                 .push(Diagnostic::error(*loc, String::from("divide by zero")));
         } else if let Expression::NumberLiteral { value: left, .. } = &left.0 {
-            return bigint_to_expression(loc, ty, left.rem(right));
+            return bigint_to_expression(loc, ty, left.rem(right), false, ns);
         }
     }
 
@@ -1166,7 +1241,7 @@ fn trunc(
 ) -> (Expression, bool) {
     let expr = expression(expr, vars, cfg, ns);
     if let Expression::NumberLiteral { value, .. } = expr.0 {
-        bigint_to_expression(loc, ty, value)
+        bigint_to_expression(loc, ty, value, true, ns)
     } else {
         (
             Expression::Trunc {
@@ -1189,7 +1264,7 @@ fn bitwise_not(
 ) -> (Expression, bool) {
     let expr = expression(expr, vars, cfg, ns);
     if let Expression::NumberLiteral { value, .. } = expr.0 {
-        bigint_to_expression(loc, ty, !value)
+        bigint_to_expression(loc, ty, !value, true, ns)
     } else {
         (
             Expression::BitwiseNot {
@@ -1206,18 +1281,20 @@ fn negate(
     loc: &pt::Loc,
     ty: &Type,
     expr: &Expression,
+    overflowing: bool,
     vars: Option<&reaching_definitions::VarDefs>,
     cfg: &ControlFlowGraph,
     ns: &mut Namespace,
 ) -> (Expression, bool) {
     let expr = expression(expr, vars, cfg, ns);
     if let Expression::NumberLiteral { value, .. } = expr.0 {
-        bigint_to_expression(loc, ty, -value)
+        bigint_to_expression(loc, ty, -value, overflowing, ns)
     } else {
         (
             Expression::Negate {
                 loc: *loc,
                 ty: ty.clone(),
+                overflowing,
                 expr: Box::new(expr.0),
             },
             expr.1,

+ 12 - 3
src/codegen/expression.rs

@@ -23,7 +23,7 @@ use crate::sema::{
         StructType, Type,
     },
     diagnostics::Diagnostics,
-    eval::{eval_const_number, eval_const_rational},
+    eval::{eval_const_number, eval_const_rational, eval_constants_in_expression},
     expression::integers::bigint_to_expression,
     expression::ResolveTo,
 };
@@ -43,7 +43,10 @@ pub fn expression(
     vartab: &mut Vartable,
     opt: &Options,
 ) -> Expression {
-    match expr {
+    let evaluated = eval_constants_in_expression(expr, &mut Diagnostics::default());
+    let expr = evaluated.0.as_ref().unwrap_or(expr);
+
+    match &expr {
         ast::Expression::StorageVariable {
             loc,
             contract_no: var_contract_no,
@@ -320,9 +323,15 @@ pub fn expression(
             ty: ty.clone(),
             expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)),
         },
-        ast::Expression::Negate { loc, ty, expr } => Expression::Negate {
+        ast::Expression::Negate {
+            loc,
+            ty,
+            unchecked,
+            expr,
+        } => Expression::Negate {
             loc: *loc,
             ty: ty.clone(),
+            overflowing: *unchecked,
             expr: Box::new(expression(expr, cfg, contract_no, func, ns, vartab, opt)),
         },
         ast::Expression::StructLiteral { loc, ty, values } => Expression::StructLiteral {

+ 8 - 1
src/codegen/mod.rs

@@ -620,6 +620,7 @@ pub enum Expression {
     Negate {
         loc: pt::Loc,
         ty: Type,
+        overflowing: bool,
         expr: Box<Expression>,
     },
     Undefined {
@@ -1561,9 +1562,15 @@ impl Expression {
                     ty: ty.clone(),
                     expr: Box::new(filter(expr, ctx)),
                 },
-                Expression::Negate { loc, ty, expr } => Expression::Negate {
+                Expression::Negate {
+                    loc,
+                    ty,
+                    overflowing,
+                    expr,
+                } => Expression::Negate {
                     loc: *loc,
                     ty: ty.clone(),
+                    overflowing: *overflowing,
                     expr: Box::new(filter(expr, ctx)),
                 },
                 Expression::Subscript {

+ 5 - 1
src/codegen/subexpression_elimination/expression.rs

@@ -291,10 +291,14 @@ impl Expression {
             },
 
             Expression::Negate {
-                loc, ty: expr_type, ..
+                loc,
+                ty: expr_type,
+                overflowing,
+                ..
             } => Expression::Negate {
                 loc: *loc,
                 ty: expr_type.clone(),
+                overflowing: *overflowing,
                 expr: Box::new(operand.clone()),
             },
 

+ 8 - 1
src/codegen/subexpression_elimination/operator.rs

@@ -46,6 +46,7 @@ pub enum Operator {
     Cast(Type),
     BytesCast,
     Negate,
+    OverflowingNegate,
     BitwiseNot,
 }
 
@@ -97,7 +98,13 @@ impl Expression {
             Expression::Trunc { ty, .. } => Operator::Trunc(ty.clone()),
             Expression::Cast { ty, .. } => Operator::Cast(ty.clone()),
             Expression::BytesCast { .. } => Operator::BytesCast,
-            Expression::Negate { .. } => Operator::Negate,
+            Expression::Negate { overflowing, .. } => {
+                if *overflowing {
+                    Operator::OverflowingNegate
+                } else {
+                    Operator::Negate
+                }
+            }
             Expression::More { signed: true, .. } => Operator::SignedMore,
             Expression::More { signed: false, .. } => Operator::UnsignedMore,
             Expression::Less { signed: true, .. } => Operator::SignedLess,

+ 5 - 0
src/codegen/subexpression_elimination/tests.rs

@@ -231,6 +231,7 @@ fn not_tracked() {
     let minus = Expression::Negate {
         loc: Loc::Codegen,
         ty: Type::Int(32),
+        overflowing: true,
         expr: Box::new(load.clone()),
     };
     let exp = Expression::ShiftLeft {
@@ -326,6 +327,7 @@ fn complex_expression() {
     let unary = Expression::Negate {
         loc: Loc::Codegen,
         ty: Type::Int(44),
+        overflowing: true,
         expr: Box::new(modu.clone()),
     };
 
@@ -517,6 +519,7 @@ fn kill() {
     let unary = Expression::Negate {
         loc: Loc::Codegen,
         ty: Type::Int(44),
+        overflowing: true,
         expr: Box::new(modu.clone()),
     };
 
@@ -626,6 +629,7 @@ fn clone() {
     let unary = Expression::Negate {
         loc: Loc::Codegen,
         ty: Type::Int(44),
+        overflowing: true,
         expr: Box::new(modu.clone()),
     };
 
@@ -733,6 +737,7 @@ fn intersect() {
     let unary = Expression::Negate {
         loc: Loc::Codegen,
         ty: Type::Int(44),
+        overflowing: true,
         expr: Box::new(modu.clone()),
     };
 

+ 22 - 2
src/emit/expression.rs

@@ -1021,10 +1021,30 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_int_z_extend(e, ty.into_int_type(), "")
                 .into()
         }
-        Expression::Negate { expr, .. } => {
+        Expression::Negate {
+            loc,
+            expr,
+            overflowing,
+            ..
+        } => {
             let e = expression(target, bin, expr, vartab, function, ns).into_int_value();
 
-            bin.builder.build_int_neg(e, "").into()
+            if *overflowing {
+                bin.builder.build_int_neg(e, "").into()
+            } else {
+                build_binary_op_with_overflow_check(
+                    target,
+                    bin,
+                    function,
+                    e.get_type().const_zero(),
+                    e,
+                    BinaryOp::Subtract,
+                    true,
+                    ns,
+                    *loc,
+                )
+                .into()
+            }
         }
         Expression::SignExt { ty, expr, .. } => {
             let e = expression(target, bin, expr, vartab, function, ns).into_int_value();

+ 2 - 0
src/sema/ast.rs

@@ -1071,6 +1071,8 @@ pub enum Expression {
     Negate {
         loc: pt::Loc,
         ty: Type,
+        /// Do not check for overflow, i.e. in `unchecked {}` block
+        unchecked: bool,
         expr: Box<Expression>,
     },
 

+ 15 - 8
src/sema/dotgraphviz.rs

@@ -999,15 +999,22 @@ impl Dot {
 
                 self.add_expression(expr, func, ns, node, String::from("expr"));
             }
-            Expression::Negate { loc, ty, expr } => {
+            Expression::Negate {
+                loc,
+                ty,
+                unchecked,
+                expr,
+            } => {
+                let mut labels = vec![
+                    format!("unary minus {}", ty.to_string(ns)),
+                    ns.loc_to_string(PathDisplay::FullPath, loc),
+                ];
+                if *unchecked {
+                    labels.push(String::from("unchecked"));
+                }
+
                 let node = self.add_node(
-                    Node::new(
-                        "unary_minus",
-                        vec![
-                            format!("unary minus {}", ty.to_string(ns)),
-                            ns.loc_to_string(PathDisplay::FullPath, loc),
-                        ],
-                    ),
+                    Node::new("unary_minus", labels),
                     Some(parent),
                     Some(parent_rel),
                 );

+ 56 - 63
src/sema/eval.rs

@@ -300,26 +300,28 @@ pub(super) fn check_term_for_constant_overflow(expr: &Expression, ns: &mut Names
         | Expression::BitwiseAnd { .. }
         | Expression::BitwiseOr { .. }
         | Expression::BitwiseXor { .. }
-        | Expression::NumberLiteral { .. } => match eval_constants_in_expression(expr, ns) {
-            (
-                Some(Expression::NumberLiteral {
-                    loc,
-                    ty,
-                    value: result,
-                }),
-                _,
-            ) => {
-                if let Some(diagnostic) = overflow_check(&result, &ty, &loc) {
-                    ns.diagnostics.push(diagnostic);
-                }
+        | Expression::NumberLiteral { .. } => {
+            match eval_constants_in_expression(expr, &mut ns.diagnostics) {
+                (
+                    Some(Expression::NumberLiteral {
+                        loc,
+                        ty,
+                        value: result,
+                    }),
+                    _,
+                ) => {
+                    if let Some(diagnostic) = overflow_diagnostic(&result, &ty, &loc) {
+                        ns.diagnostics.push(diagnostic);
+                    }
 
-                return false;
-            }
-            (None, false) => {
-                return false;
+                    return false;
+                }
+                (None, false) => {
+                    return false;
+                }
+                _ => {}
             }
-            _ => {}
-        },
+        }
         _ => {}
     }
 
@@ -328,9 +330,9 @@ pub(super) fn check_term_for_constant_overflow(expr: &Expression, ns: &mut Names
 
 /// This function recursively folds number literals in a given expression.
 /// It returns an Option<Expression> which is the result of the folding if the operands are number literals, and a boolean flag that is set to false if the recursion should stop.
-fn eval_constants_in_expression(
+pub(crate) fn eval_constants_in_expression(
     expr: &Expression,
-    ns: &mut Namespace,
+    diagnostics: &mut Diagnostics,
 ) -> (Option<Expression>, bool) {
     match expr {
         Expression::Add {
@@ -340,8 +342,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -367,8 +369,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -395,8 +397,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -421,8 +423,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -430,8 +432,7 @@ fn eval_constants_in_expression(
             ) = (&left, &right)
             {
                 if right.is_zero() {
-                    ns.diagnostics
-                        .push(Diagnostic::error(*loc, "divide by zero".to_string()));
+                    diagnostics.push(Diagnostic::error(*loc, "divide by zero".to_string()));
                     (None, false)
                 } else {
                     (
@@ -454,8 +455,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -463,8 +464,7 @@ fn eval_constants_in_expression(
             ) = (&left, &right)
             {
                 if right.is_zero() {
-                    ns.diagnostics
-                        .push(Diagnostic::error(*loc, "divide by zero".to_string()));
+                    diagnostics.push(Diagnostic::error(*loc, "divide by zero".to_string()));
                     (None, false)
                 } else {
                     (
@@ -487,8 +487,8 @@ fn eval_constants_in_expression(
             base,
             exp,
         } => {
-            let base = eval_constants_in_expression(base, ns).0;
-            let exp = eval_constants_in_expression(exp, ns).0;
+            let base = eval_constants_in_expression(base, diagnostics).0;
+            let exp = eval_constants_in_expression(exp, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -499,8 +499,8 @@ fn eval_constants_in_expression(
                 }),
             ) = (&base, &exp)
             {
-                if overflow_check(right, &Type::Uint(16), right_loc).is_some() {
-                    ns.diagnostics.push(Diagnostic::error(
+                if overflow_diagnostic(right, &Type::Uint(16), right_loc).is_some() {
+                    diagnostics.push(Diagnostic::error(
                         *right_loc,
                         format!("power by {right} is not possible"),
                     ));
@@ -525,8 +525,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -537,20 +537,13 @@ fn eval_constants_in_expression(
                 }),
             ) = (&left, &right)
             {
-                if overflow_check(right, &Type::Uint(64), right_loc).is_some() {
-                    ns.diagnostics.push(Diagnostic::error(
+                if overflow_diagnostic(right, &Type::Uint(64), right_loc).is_some() {
+                    diagnostics.push(Diagnostic::error(
                         *right_loc,
                         format!("left shift by {right} is not possible"),
                     ));
                     (None, false)
                 } else {
-                    if right >= &BigInt::from(left.bits()) {
-                        ns.diagnostics.push(Diagnostic::warning(
-                            *right_loc,
-                            format!("left shift by {right} may overflow the final result"),
-                        ));
-                    }
-
                     (
                         Some(Expression::NumberLiteral {
                             loc: *loc,
@@ -572,8 +565,8 @@ fn eval_constants_in_expression(
             right,
             sign: _,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -584,8 +577,8 @@ fn eval_constants_in_expression(
                 }),
             ) = (&left, &right)
             {
-                if overflow_check(right, &Type::Uint(64), right_loc).is_some() {
-                    ns.diagnostics.push(Diagnostic::error(
+                if overflow_diagnostic(right, &Type::Uint(64), right_loc).is_some() {
+                    diagnostics.push(Diagnostic::error(
                         *right_loc,
                         format!("right shift by {right} is not possible"),
                     ));
@@ -610,8 +603,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -636,8 +629,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -662,8 +655,8 @@ fn eval_constants_in_expression(
             left,
             right,
         } => {
-            let left = eval_constants_in_expression(left, ns).0;
-            let right = eval_constants_in_expression(right, ns).0;
+            let left = eval_constants_in_expression(left, diagnostics).0;
+            let right = eval_constants_in_expression(right, diagnostics).0;
 
             if let (
                 Some(Expression::NumberLiteral { value: left, .. }),
@@ -683,7 +676,7 @@ fn eval_constants_in_expression(
             }
         }
         Expression::ZeroExt { loc, to, expr } => {
-            let expr = eval_constants_in_expression(expr, ns).0;
+            let expr = eval_constants_in_expression(expr, diagnostics).0;
             if let Some(Expression::NumberLiteral { value, .. }) = expr {
                 (
                     Some(Expression::NumberLiteral {
@@ -698,7 +691,7 @@ fn eval_constants_in_expression(
             }
         }
         Expression::SignExt { loc, to, expr } => {
-            let expr = eval_constants_in_expression(expr, ns).0;
+            let expr = eval_constants_in_expression(expr, diagnostics).0;
             if let Some(Expression::NumberLiteral { value, .. }) = expr {
                 (
                     Some(Expression::NumberLiteral {
@@ -717,8 +710,8 @@ fn eval_constants_in_expression(
     }
 }
 
-/// Function that takes a BigInt and an expected type. If the number of bits in the type required to represent the BigInt is not suffiecient, it will return a diagnostic.
-pub(super) fn overflow_check(result: &BigInt, ty: &Type, loc: &Loc) -> Option<Diagnostic> {
+/// Function that takes a BigInt and an expected type. If the number of bits in the type required to represent the BigInt is not sufficient, it will return a diagnostic.
+pub(crate) fn overflow_diagnostic(result: &BigInt, ty: &Type, loc: &Loc) -> Option<Diagnostic> {
     if result.bits() > 1024 {
         // Do not try to print large values. For example:
         // uint x = 80 ** 0x100000;

+ 1 - 0
src/sema/expression/resolve_expression.rs

@@ -550,6 +550,7 @@ fn negate(
                 Ok(Expression::Negate {
                     loc: *loc,
                     ty: expr_type,
+                    unchecked: context.unchecked,
                     expr: Box::new(expr),
                 })
             }

+ 2 - 2
src/sema/function_annotation.rs

@@ -3,7 +3,7 @@
 use super::{
     ast::{ConstructorAnnotation, Diagnostic, Expression, Function, Namespace, Type},
     diagnostics::Diagnostics,
-    eval::overflow_check,
+    eval::overflow_diagnostic,
     expression::literals::{hex_number_literal, unit_literal},
     expression::{ExprContext, ResolveTo},
     Symtable,
@@ -140,7 +140,7 @@ fn function_selector(
                 };
 
                 if let Ok(Expression::NumberLiteral { loc, value, .. }) = &expr {
-                    if let Some(diagnostic) = overflow_check(value, &uint8, loc) {
+                    if let Some(diagnostic) = overflow_diagnostic(value, &uint8, loc) {
                         diagnostics.push(diagnostic);
                     } else {
                         selector.push(value.to_u8().unwrap());

+ 1 - 13
src/sema/tests/mod.rs

@@ -320,19 +320,7 @@ fn constant_overflow_checks() {
 
     assert_eq!(errors.len(), 31);
 
-    assert_eq!(
-        warnings[0].message,
-        "left shift by 7 may overflow the final result"
-    );
-    assert_eq!(
-        warnings[1].message,
-        "left shift by 7 may overflow the final result"
-    );
-    assert_eq!(
-        warnings[2].message,
-        "left shift by 9 may overflow the final result"
-    );
-    assert_eq!(warnings.len(), 3);
+    assert_eq!(warnings.len(), 0);
 }
 
 #[test]

+ 3 - 3
tests/codegen_testcases/solidity/array_boundary_opt.sol

@@ -17,16 +17,16 @@ contract Array_bound_Test {
         // CHECK: ty:uint32 %array_length.temp.34 = uint32 20
         uint256[] d = new uint256[](20);
 
-        // CHECK: ty:uint32 %array_length.temp.32 = (%1.cse_temp + uint32 1)
+        // CHECK: ty:uint32 %array_length.temp.32 = (overflowing %1.cse_temp + uint32 1)
         a.push();
 
-        // CHECK: ty:uint32 %array_length.temp.33 = ((arg #2) - uint32 1)
+        // CHECK: ty:uint32 %array_length.temp.33 = (overflowing (arg #2) - uint32 1)
         c.pop();
 
         // CHECK: ty:uint32 %array_length.temp.34 = uint32 21
         d.push();
 
-        // CHECK: return (zext uint256 (((%array_length.temp.32 + (builtin ArrayLength ((arg #0)))) + ((arg #2) - uint32 1)) + uint32 21))
+        // CHECK: return (zext uint256 (((%array_length.temp.32 + (builtin ArrayLength ((arg #0)))) + (overflowing (arg #2) - uint32 1)) + uint32 21))
         return a.length + b.length + c.length + d.length;
     }
 

+ 3 - 3
tests/codegen_testcases/solidity/modifier_opt.sol

@@ -11,7 +11,7 @@ contract Test {
         // CHECK: branchcond (uint128((builtin Value ())) != uint128 0),
         // NOT-CHECK: uint128 1 - uint128 1
 
-        // CHECK: branchcond (uint128((builtin Value ())) != uint128 -1),
+        // CHECK: branchcond (uint128((builtin Value ())) != uint128 340282366920938463463374607431768211455),
         // NOT-CHECK: uint128 2 ** uint128 127
     }
 
@@ -22,7 +22,7 @@ contract Test {
         require(msg.value != 1 - 1);
 
         // CHECK: block0: # entry
-        // CHECK: branchcond (uint128((builtin Value ())) != uint128 -1),
+        // CHECK: branchcond (uint128((builtin Value ())) != uint128 340282366920938463463374607431768211455),
         // NOT-CHECK: uint128 2 ** uint128 127,
 
         // CHECK: branchcond (uint128((builtin Value ())) != uint128 0),
@@ -34,7 +34,7 @@ contract Test {
         return 2 ** 256 - 1;
 
         // CHECK: block0: # entry
-        // CHECK: return uint256 -1
+        // CHECK: return uint256 115792089237316195423570985008687907853269984665640564039457584007913129639935
         // NOT-CHECK: (uint256 2 ** uint256 256) - uint256 1
     }
 }

+ 1 - 1
tests/codegen_testcases/yul/binary_arithmetic_builtins.sol

@@ -52,7 +52,7 @@ contract testing {
             // CHECK: ty:uint256 %t = (overflowing uint256 22193982385802470 + uint256(false))
             let t := add(q, r)
 
-            // CHECK: ty:uint256 %u = uint256 -1
+            // CHECK: ty:uint256 %u = uint256 115792089237316195423570985008687907853269984665640564039457584007913129639935
             let u := sub(false, true)
 
             // CHECK: ty:uint256 %v = uint256((overflowing true + false))

+ 76 - 0
tests/contract_testcases/solana/expressions/overflow_constant_propagation.sol

@@ -0,0 +1,76 @@
+// Note: none of these errors will occur without --no-constant-folding
+contract foo {
+    function test() public pure {
+        uint32 x = 2147483648;
+        uint32 y = 2147483648;
+        uint32 z = x + y;
+        print("z: {}".format(z));
+
+        uint16 a1 = 256;
+        uint16 b1 = 257;
+        uint16 c1 = a1 * b1;
+        print("c1: {}".format(c1));
+
+        uint16 a2 = 10;
+        uint16 b2 = 5;
+        uint16 c2 = a2 ** b2;
+        print("c2: {}".format(c2));
+
+        uint16 a3 = 256;
+        uint16 b3 = 257;
+        uint16 c3 = a3 - b3;
+        print("c3: {}".format(c3));
+
+        int16 a4 = -0x8000;
+        int16 c4 = -a4;
+        print("c4: {}".format(c4));
+    }
+
+    function test_unchecked() public pure {
+        unchecked {
+            uint32 x = 2147483648;
+            uint32 y = 2147483648;
+            uint32 z = x + y;
+            print("z: {}".format(z));
+
+            uint16 a1 = 256;
+            uint16 b1 = 257;
+            uint16 c1 = a1 * b1;
+            print("c1: {}".format(c1));
+
+            uint16 a2 = 10;
+            uint16 b2 = 5;
+            uint16 c2 = a2 ** b2;
+            print("c2: {}".format(c2));
+
+            uint16 a3 = 256;
+            uint16 b3 = 257;
+            uint16 c3 = a3 - b3;
+            print("c3: {}".format(c3));
+
+            int16 a4 = -0x8000;
+            int16 c4 = -a4;
+            print("c4: {}".format(c4));
+        }
+    }
+
+    function test_big() public pure returns (uint256) {
+            uint256 a1 = 1 << 255;
+            uint64 b1 = 1 << 31;
+            uint256 c1 = a1**b1;
+            print("c1: {}".format(c1));
+
+            uint256 a2 = 1 << 255;
+            uint64 b2 = 1 << 3;
+            uint256 c2 = a2**b2;
+            print("c2: {}".format(c2));
+    }
+}
+// ---- Expect: diagnostics ----
+// error: 6:20-25: value 4294967296 does not fit into type uint32.
+// error: 11:21-28: value 65792 does not fit into type uint16.
+// error: 16:21-29: value 100000 does not fit into type uint16.
+// error: 21:21-28: negative value -1 does not fit into type uint16. Cannot implicitly convert signed literal to unsigned type.
+// error: 25:20-23: value 32768 does not fit into type int16.
+// error: 60:26-32: power 2147483648 not possible
+// error: 65:26-32: value is too large to fit into type uint256

+ 0 - 2
tests/contract_testcases/solana/large_ints.sol

@@ -8,7 +8,5 @@ contract c {
 
 // ---- Expect: diagnostics ----
 // error: 3:12-22: value is too large to fit into type uint256
-// warning: 4:11-23: left shift by 100000 may overflow the final result
 // error: 4:11-23: value is too large to fit into type int256
-// warning: 5:11-24: left shift by 100000 may overflow the final result
 // error: 5:11-24: value is too large to fit into type int256

+ 31 - 1
tests/solana_tests/primitives.rs

@@ -12,7 +12,7 @@ use std::ops::Rem;
 use std::ops::Shl;
 use std::ops::Shr;
 use std::ops::Sub;
-use std::ops::{Add, BitOr, BitXor, MulAssign, ShlAssign, ShrAssign, SubAssign};
+use std::ops::{Add, AddAssign, BitOr, BitXor, MulAssign, Neg, ShlAssign, ShrAssign, SubAssign};
 
 #[test]
 #[should_panic]
@@ -1171,6 +1171,10 @@ fn test_overflow_detect_signed() {
             function mul(intN a, intN b) public returns (intN) {
                 return a * b;
             }
+
+            function neg(intN a) public returns (intN) {
+                return -a;
+            }
         }"#
         .replace("intN", &format!("int{width}"));
         let mut contract = build_solidity(&src);
@@ -1230,6 +1234,32 @@ fn test_overflow_detect_signed() {
             ])
             .accounts(vec![("dataAccount", data_account)])
             .must_fail();
+
+        // neg fails when value -(2^N)
+        let upper_limit: BigInt = BigInt::from(2_u32).pow((width - 1) as u32);
+        let mut lower_limit: BigInt = upper_limit.clone().neg();
+
+        contract
+            .function("neg")
+            .arguments(&[BorshToken::Int {
+                width: width as u16,
+                value: lower_limit.clone(),
+            }])
+            .accounts(vec![("dataAccount", data_account)])
+            .must_fail();
+
+        lower_limit.add_assign(1usize);
+
+        let first_operand_rand = rng.gen_bigint_range(&lower_limit, &upper_limit);
+
+        contract
+            .function("neg")
+            .arguments(&[BorshToken::Int {
+                width: width as u16,
+                value: first_operand_rand,
+            }])
+            .accounts(vec![("dataAccount", data_account)])
+            .call();
     }
 }