Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: correctly detect signed/unsigned integer overflows/underflows #5375

Merged
merged 3 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions compiler/noirc_evaluator/src/ssa/ir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,31 @@ impl NumericType {

/// Returns true if the given Field value is within the numeric limits
/// for the current NumericType.
pub(crate) fn value_is_within_limits(self, field: FieldElement) -> bool {
///
/// Note that if `negative` is true, it's expected that the field value holds the negative value
/// already casted to the range of `self` (for example, `-1` for a `i8` or `u8` type is
/// should be passed as `255`).
pub(crate) fn value_is_within_limits(self, field: FieldElement, negative: bool) -> bool {
match self {
NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => {
NumericType::Unsigned { bit_size } => {
if negative {
return false;
}
let max = 2u128.pow(bit_size) - 1;
field <= max.into()
}
NumericType::Signed { bit_size } => {
if negative {
let base = 1_u128 << bit_size;
// Recover the original value (a similar operation is done in compiler/noirc_frontend/src/monomorphization/mod.rs)
let field = FieldElement::from(base) - field;
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
let max = 2u128.pow(bit_size - 1);
field <= max.into()
} else {
let max = 2u128.pow(bit_size - 1) - 1;
field <= max.into()
}
}
NumericType::NativeField => true,
}
}
Expand Down Expand Up @@ -210,3 +229,36 @@ impl std::fmt::Display for NumericType {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_u8_is_within_limits() {
let u8 = NumericType::Unsigned { bit_size: 8 };
assert!(!u8.value_is_within_limits(
FieldElement::from(256_i128) - FieldElement::from(1_i128),
true
));
assert!(u8.value_is_within_limits(FieldElement::from(0_i128), false));
assert!(u8.value_is_within_limits(FieldElement::from(255_i128), false));
assert!(!u8.value_is_within_limits(FieldElement::from(256_i128), false));
}

#[test]
fn test_i8_is_within_limits() {
let i8 = NumericType::Signed { bit_size: 8 };
assert!(!i8.value_is_within_limits(
FieldElement::from(256_i128) - FieldElement::from(129_i128),
true
));
assert!(i8.value_is_within_limits(
FieldElement::from(256_i128) - FieldElement::from(128_i128),
true
));
assert!(i8.value_is_within_limits(FieldElement::from(0_i128), false));
assert!(i8.value_is_within_limits(FieldElement::from(127_i128), false));
assert!(!i8.value_is_within_limits(FieldElement::from(128_i128), false));
}
}
3 changes: 2 additions & 1 deletion compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,13 @@ impl<'a> FunctionContext<'a> {
pub(super) fn checked_numeric_constant(
&mut self,
value: impl Into<FieldElement>,
negative: bool,
typ: Type,
) -> Result<ValueId, RuntimeError> {
let value = value.into();

if let Type::Numeric(typ) = typ {
if !typ.value_is_within_limits(value) {
if !typ.value_is_within_limits(value, negative) {
let call_stack = self.builder.get_call_stack();
return Err(RuntimeError::IntegerOutOfBounds { value, typ, call_stack });
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,10 @@
_ => unreachable!("ICE: unexpected slice literal type, got {}", array.typ),
})
}
ast::Literal::Integer(value, typ, location) => {
ast::Literal::Integer(value, negative, typ, location) => {
self.builder.set_location(*location);
let typ = Self::convert_non_tuple_type(typ);
self.checked_numeric_constant(*value, typ).map(Into::into)
self.checked_numeric_constant(*value, *negative, typ).map(Into::into)
}
ast::Literal::Bool(value) => {
// Don't need to call checked_numeric_constant here since `value` can only be true or false
Expand Down Expand Up @@ -472,7 +472,7 @@
/// br loop_entry(v0)
/// loop_entry(i: Field):
/// v2 = lt i v1
/// brif v2, then: loop_body, else: loop_end

Check warning on line 475 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// loop_body():
/// v3 = ... codegen body ...
/// v4 = add 1, i
Expand Down Expand Up @@ -531,7 +531,7 @@
/// For example, the expression `if cond { a } else { b }` is codegen'd as:
///
/// v0 = ... codegen cond ...
/// brif v0, then: then_block, else: else_block

Check warning on line 534 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// then_block():
/// v1 = ... codegen a ...
/// br end_if(v1)
Expand All @@ -544,7 +544,7 @@
/// As another example, the expression `if cond { a }` is codegen'd as:
///
/// v0 = ... codegen cond ...
/// brif v0, then: then_block, else: end_block

Check warning on line 547 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// then_block:
/// v1 = ... codegen a ...
/// br end_if()
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/monomorphization/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub struct For {
pub enum Literal {
Array(ArrayLiteral),
Slice(ArrayLiteral),
Integer(FieldElement, Type, Location),
Integer(FieldElement, /*sign*/ bool, Type, Location), // false for positive integer and true for negative
Bool(bool),
Unit,
Str(String),
Expand Down
24 changes: 16 additions & 8 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,16 +420,16 @@ impl<'interner> Monomorphizer<'interner> {

if sign {
match typ {
ast::Type::Field => Literal(Integer(-value, typ, location)),
ast::Type::Field => Literal(Integer(-value, sign, typ, location)),
ast::Type::Integer(_, bit_size) => {
let bit_size: u32 = bit_size.into();
let base = 1_u128 << bit_size;
Literal(Integer(FieldElement::from(base) - value, typ, location))
Literal(Integer(FieldElement::from(base) - value, sign, typ, location))
}
_ => unreachable!("Integer literal must be numeric"),
}
} else {
Literal(Integer(value, typ, location))
Literal(Integer(value, sign, typ, location))
}
}
HirExpression::Literal(HirLiteral::Array(array)) => match array {
Expand Down Expand Up @@ -911,7 +911,7 @@ impl<'interner> Monomorphizer<'interner> {
let value = FieldElement::from(value as u128);
let location = self.interner.id_location(expr_id);
let typ = Self::convert_type(&typ, ident.location)?;
ast::Expression::Literal(ast::Literal::Integer(value, typ, location))
ast::Expression::Literal(ast::Literal::Integer(value, false, typ, location))
}
};

Expand Down Expand Up @@ -1268,7 +1268,9 @@ impl<'interner> Monomorphizer<'interner> {
let bits = (FieldElement::max_num_bits() as u128).into();
let typ =
ast::Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour);
Some(ast::Expression::Literal(ast::Literal::Integer(bits, typ, location)))
Some(ast::Expression::Literal(ast::Literal::Integer(
bits, false, typ, location,
)))
}
"zeroed" => {
let location = self.interner.expr_location(expr_id);
Expand Down Expand Up @@ -1308,7 +1310,12 @@ impl<'interner> Monomorphizer<'interner> {
let int_type = Type::Integer(crate::ast::Signedness::Unsigned, arr_elem_bits);

let bytes_as_expr = vecmap(bytes, |byte| {
Expression::Literal(Literal::Integer((byte as u128).into(), int_type.clone(), location))
Expression::Literal(Literal::Integer(
(byte as u128).into(),
false,
int_type.clone(),
location,
))
});

let typ = Type::Slice(Box::new(int_type));
Expand Down Expand Up @@ -1595,7 +1602,7 @@ impl<'interner> Monomorphizer<'interner> {
match typ {
ast::Type::Field | ast::Type::Integer(..) => {
let typ = typ.clone();
ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), typ, location))
ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), false, typ, location))
}
ast::Type::Bool => ast::Expression::Literal(ast::Literal::Bool(false)),
ast::Type::Unit => ast::Expression::Literal(ast::Literal::Unit),
Expand Down Expand Up @@ -1750,7 +1757,8 @@ impl<'interner> Monomorphizer<'interner> {
let operator =
if matches!(operator.kind, Less | Greater) { Equal } else { NotEqual };

let int_value = ast::Literal::Integer(ordering_value, ast::Type::Field, location);
let int_value =
ast::Literal::Integer(ordering_value, false, ast::Type::Field, location);
let rhs = Box::new(ast::Expression::Literal(int_value));
let lhs = Box::new(ast::Expression::ExtractTupleField(Box::new(result), 0));

Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/monomorphization/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl AstPrinter {
self.print_comma_separated(&array.contents, f)?;
write!(f, "]")
}
super::ast::Literal::Integer(x, _, _) => x.fmt(f),
super::ast::Literal::Integer(x, _, _, _) => x.fmt(f),
super::ast::Literal::Bool(x) => x.fmt(f),
super::ast::Literal::Str(s) => s.fmt(f),
super::ast::Literal::FmtStr(s, _, _) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "integer_literal_overflow"
name = "signed_integer_literal_overflow"
type = "bin"
authors = [""]
[dependencies]
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
foo(128)
}

fn foo(_x: i8) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "signed_integer_literal_underflow"
type = "bin"
authors = [""]
[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
foo(-129)
}

fn foo(_x: i8) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "unsigned_integer_literal_overflow"
type = "bin"
authors = [""]
[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "unsigned_integer_literal_underflow"
type = "bin"
authors = [""]
[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
foo(-1)
}

fn foo(_x: u8) {}
asterite marked this conversation as resolved.
Show resolved Hide resolved
Loading