runtime array indexing

This commit is contained in:
Protryon 2021-03-22 09:38:01 -07:00 committed by collin
parent f6e602347a
commit a763075e99
17 changed files with 491 additions and 74 deletions

View File

@ -180,6 +180,14 @@ impl AsgConvertError {
Self::new_from_span(format!("tuple index out of bounds: '{}'", index), span) Self::new_from_span(format!("tuple index out of bounds: '{}'", index), span)
} }
pub fn array_index_out_of_bounds(index: usize, span: &Span) -> Self {
Self::new_from_span(format!("array index out of bounds: '{}'", index), span)
}
pub fn unknown_array_size(span: &Span) -> Self {
Self::new_from_span("array size cannot be inferred, add explicit types".to_string(), span)
}
pub fn unexpected_call_argument_count(expected: usize, got: usize, span: &Span) -> Self { pub fn unexpected_call_argument_count(expected: usize, got: usize, span: &Span) -> Self {
Self::new_from_span( Self::new_from_span(
format!("function call expected {} arguments, got {}", expected, got), format!("function call expected {} arguments, got {}", expected, got),

View File

@ -89,8 +89,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
&*value.array, &*value.array,
Some(PartialType::Array(expected_type.map(Box::new), None)), Some(PartialType::Array(expected_type.map(Box::new), None)),
)?; )?;
match array.get_type() { let array_len = match array.get_type() {
Some(Type::Array(..)) => (), Some(Type::Array(_, len)) => len,
type_ => { type_ => {
return Err(AsgConvertError::unexpected_type( return Err(AsgConvertError::unexpected_type(
"array", "array",
@ -98,7 +98,7 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
&value.span, &value.span,
)); ));
} }
} };
let index = <&Expression<'a>>::from_ast( let index = <&Expression<'a>>::from_ast(
scope, scope,
@ -106,10 +106,17 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
Some(PartialType::Integer(None, Some(IntegerType::U32))), Some(PartialType::Integer(None, Some(IntegerType::U32))),
)?; )?;
if !index.is_consty() { if let Some(index) = index
return Err(AsgConvertError::unexpected_nonconst( .const_value()
&index.span().cloned().unwrap_or_default(), .map(|x| x.int().map(|x| x.to_usize()).flatten())
)); .flatten()
{
if index > array_len {
return Err(AsgConvertError::array_index_out_of_bounds(
index,
&array.span().cloned().unwrap_or_default(),
));
}
} }
Ok(ArrayAccessExpression { Ok(ArrayAccessExpression {

View File

@ -26,6 +26,9 @@ pub struct ArrayRangeAccessExpression<'a> {
pub array: Cell<&'a Expression<'a>>, pub array: Cell<&'a Expression<'a>>,
pub left: Cell<Option<&'a Expression<'a>>>, pub left: Cell<Option<&'a Expression<'a>>>,
pub right: Cell<Option<&'a Expression<'a>>>, pub right: Cell<Option<&'a Expression<'a>>>,
// this is either const(right) - const(left) OR the length inferred by type checking
// special attention must be made to update this if semantic-altering changes are made to left or right.
pub length: usize,
} }
impl<'a> Node for ArrayRangeAccessExpression<'a> { impl<'a> Node for ArrayRangeAccessExpression<'a> {
@ -55,25 +58,12 @@ impl<'a> ExpressionNode<'a> for ArrayRangeAccessExpression<'a> {
} }
fn get_type(&self) -> Option<Type<'a>> { fn get_type(&self) -> Option<Type<'a>> {
let (element, array_len) = match self.array.get().get_type() { let element = match self.array.get().get_type() {
Some(Type::Array(element, len)) => (element, len), Some(Type::Array(element, _)) => element,
_ => return None, _ => return None,
}; };
let const_left = match self.left.get().map(|x| x.const_value()) {
Some(Some(ConstValue::Int(x))) => x.to_usize()?,
None => 0,
_ => return None,
};
let const_right = match self.right.get().map(|x| x.const_value()) {
Some(Some(ConstValue::Int(x))) => x.to_usize()?,
None => array_len,
_ => return None,
};
if const_left > const_right || const_right > array_len {
return None;
}
Some(Type::Array(element, const_right - const_left)) Some(Type::Array(element, self.length))
} }
fn is_mut_ref(&self) -> bool { fn is_mut_ref(&self) -> bool {
@ -113,9 +103,9 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
value: &leo_ast::ArrayRangeAccessExpression, value: &leo_ast::ArrayRangeAccessExpression,
expected_type: Option<PartialType<'a>>, expected_type: Option<PartialType<'a>>,
) -> Result<ArrayRangeAccessExpression<'a>, AsgConvertError> { ) -> Result<ArrayRangeAccessExpression<'a>, AsgConvertError> {
let expected_array = match expected_type { let (expected_array, expected_len) = match expected_type.clone() {
Some(PartialType::Array(element, _len)) => Some(PartialType::Array(element, None)), Some(PartialType::Array(element, len)) => (Some(PartialType::Array(element, None)), len),
None => None, None => (None, None),
Some(x) => { Some(x) => {
return Err(AsgConvertError::unexpected_type( return Err(AsgConvertError::unexpected_type(
&x.to_string(), &x.to_string(),
@ -126,8 +116,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
}; };
let array = <&Expression<'a>>::from_ast(scope, &*value.array, expected_array)?; let array = <&Expression<'a>>::from_ast(scope, &*value.array, expected_array)?;
let array_type = array.get_type(); let array_type = array.get_type();
match array_type { let (parent_element, parent_size) = match array_type {
Some(Type::Array(_, _)) => (), Some(Type::Array(inner, size)) => (inner, size),
type_ => { type_ => {
return Err(AsgConvertError::unexpected_type( return Err(AsgConvertError::unexpected_type(
"array", "array",
@ -135,7 +125,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
&value.span, &value.span,
)); ));
} }
} };
let left = value let left = value
.left .left
.as_deref() .as_deref()
@ -151,26 +142,72 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
}) })
.transpose()?; .transpose()?;
if let Some(left) = left.as_ref() { let const_left = match left.map(|x| x.const_value()) {
if !left.is_consty() { Some(Some(ConstValue::Int(x))) => x.to_usize(),
return Err(AsgConvertError::unexpected_nonconst( None => Some(0),
&left.span().cloned().unwrap_or_default(), _ => None,
)); };
let const_right = match right.map(|x| x.const_value()) {
Some(Some(ConstValue::Int(value))) => {
let value = value.to_usize();
if let Some(value) = value {
if value > parent_size {
return Err(AsgConvertError::array_index_out_of_bounds(
value,
&right.unwrap().span().cloned().unwrap_or_default(),
));
} else if let Some(left) = const_left {
if left > value {
return Err(AsgConvertError::array_index_out_of_bounds(
value,
&right.unwrap().span().cloned().unwrap_or_default(),
));
}
}
}
value
} }
} None => Some(parent_size),
if let Some(right) = right.as_ref() { _ => None,
if !right.is_consty() { };
return Err(AsgConvertError::unexpected_nonconst(
&right.span().cloned().unwrap_or_default(), let mut length = if let (Some(left), Some(right)) = (const_left, const_right) {
)); Some(right - left)
} else {
None
};
if let Some(expected_len) = expected_len {
if let Some(length) = length {
if length != expected_len {
let concrete_type = Type::Array(parent_element, length);
return Err(AsgConvertError::unexpected_type(
&expected_type.as_ref().unwrap().to_string(),
Some(&concrete_type.to_string()),
&value.span,
));
}
} }
if let Some(value) = const_left {
if value + expected_len > parent_size {
return Err(AsgConvertError::array_index_out_of_bounds(
value,
&left.unwrap().span().cloned().unwrap_or_default(),
));
}
}
length = Some(expected_len);
} }
if length.is_none() {
return Err(AsgConvertError::unknown_array_size(&value.span));
}
Ok(ArrayRangeAccessExpression { Ok(ArrayRangeAccessExpression {
parent: Cell::new(None), parent: Cell::new(None),
span: Some(value.span.clone()), span: Some(value.span.clone()),
array: Cell::new(array), array: Cell::new(array),
left: Cell::new(left), left: Cell::new(left),
right: Cell::new(right), right: Cell::new(right),
length: length.unwrap(),
}) })
} }
} }

View File

@ -85,12 +85,24 @@ impl ExpressionError {
Self::new_from_span(message, span) Self::new_from_span(message, span)
} }
pub fn index_out_of_bounds(index: usize, span: &Span) -> Self { pub fn tuple_index_out_of_bounds(index: usize, span: &Span) -> Self {
let message = format!("cannot access index {} of tuple out of bounds", index); let message = format!("cannot access index {} of tuple out of bounds", index);
Self::new_from_span(message, span) Self::new_from_span(message, span)
} }
pub fn array_index_out_of_bounds(index: usize, span: &Span) -> Self {
let message = format!("cannot access index {} of array out of bounds", index);
Self::new_from_span(message, span)
}
pub fn array_invalid_slice_length(span: &Span) -> Self {
let message = "illegal length of slice".to_string();
Self::new_from_span(message, span)
}
pub fn invalid_dimensions(expected: &ArrayDimensions, actual: &ArrayDimensions, span: &Span) -> Self { pub fn invalid_dimensions(expected: &ArrayDimensions, actual: &ArrayDimensions, span: &Span) -> Self {
let message = format!( let message = format!(
"expected array dimensions {}, found array dimensions {}", "expected array dimensions {}, found array dimensions {}",

View File

@ -61,6 +61,12 @@ impl StatementError {
Self::new_from_span(message, span) Self::new_from_span(message, span)
} }
pub fn array_assign_index_const(span: &Span) -> Self {
let message = "Cannot assign to non-const array index".to_string();
Self::new_from_span(message, span)
}
pub fn array_assign_interior_index(span: &Span) -> Self { pub fn array_assign_interior_index(span: &Span) -> Self {
let message = "Cannot assign single index to interior of array of values".to_string(); let message = "Cannot assign single index to interior of array of values".to_string();
@ -217,4 +223,10 @@ impl StatementError {
Self::new_from_span(message, span) Self::new_from_span(message, span)
} }
pub fn loop_index_const(span: &Span) -> Self {
let message = "iteration range must be const".to_string();
Self::new_from_span(message, span)
}
} }

View File

@ -16,10 +16,22 @@
//! Enforces array access in a compiled Leo program. //! Enforces array access in a compiled Leo program.
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; use crate::{
use leo_asg::{Expression, Span}; arithmetic::*,
errors::ExpressionError,
program::ConstrainedProgram,
relational::*,
value::{ConstrainedValue, Integer},
GroupType,
};
use leo_asg::{ConstInt, Expression, Span};
use snarkvm_fields::PrimeField; use snarkvm_fields::PrimeField;
use snarkvm_gadgets::utilities::{
boolean::Boolean,
eq::{EqGadget, EvaluateEqGadget},
select::CondSelectGadget,
};
use snarkvm_r1cs::ConstraintSystem; use snarkvm_r1cs::ConstraintSystem;
impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> { impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
@ -31,13 +43,58 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
index: &'a Expression<'a>, index: &'a Expression<'a>,
span: &Span, span: &Span,
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> { ) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
let array = match self.enforce_expression(cs, array)? { let mut array = match self.enforce_expression(cs, array)? {
ConstrainedValue::Array(array) => array, ConstrainedValue::Array(array) => array,
value => return Err(ExpressionError::undefined_array(value.to_string(), span)), value => return Err(ExpressionError::undefined_array(value.to_string(), span)),
}; };
let index_resolved = self.enforce_index(cs, index, span)?; let index_resolved = self.enforce_index(cs, index, span)?;
Ok(array[index_resolved].to_owned()) if let Some(resolved) = index_resolved.to_usize() {
if resolved >= array.len() {
return Err(ExpressionError::array_index_out_of_bounds(resolved, span));
}
Ok(array[resolved].to_owned())
} else {
if array.is_empty() {
return Err(ExpressionError::array_index_out_of_bounds(0, span));
}
{
let bounds_check = evaluate_lt::<F, G, CS>(
cs,
ConstrainedValue::Integer(index_resolved.clone()),
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(array.len() as u32))),
span,
)?;
let bounds_check = match bounds_check {
ConstrainedValue::Boolean(b) => b,
_ => unimplemented!("illegal non-Integer returned from lt"),
};
let namespace_string = format!("evaluate array access bounds {}:{}", span.line_start, span.col_start);
let mut unique_namespace = cs.ns(|| namespace_string);
bounds_check
.enforce_equal(&mut unique_namespace, &Boolean::Constant(true))
.map_err(|e| ExpressionError::cannot_enforce("array bounds check".to_string(), e, span))?;
}
let mut current_value = array.pop().unwrap();
for (i, item) in array.into_iter().enumerate() {
let namespace_string = format!("evaluate array access eq {} {}:{}", i, span.line_start, span.col_start);
let eq_namespace = cs.ns(|| namespace_string);
//todo: bounds check static index
let const_index = ConstInt::U32(i as u32).cast_to(&index_resolved.get_type());
let index_comparison = index_resolved
.evaluate_equal(eq_namespace, &Integer::new(&const_index))
.map_err(|_| ExpressionError::cannot_evaluate("==".to_string(), span))?;
//todo: handle out of bounds
let unique_namespace =
cs.ns(|| format!("select array access {} {}:{}", i, span.line_start, span.col_start));
let value =
ConstrainedValue::conditionally_select(unique_namespace, &index_comparison, &item, &current_value)
.map_err(|e| ExpressionError::cannot_enforce("conditional select".to_string(), e, span))?;
current_value = value;
}
Ok(current_value)
}
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -47,6 +104,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
array: &'a Expression<'a>, array: &'a Expression<'a>,
left: Option<&'a Expression<'a>>, left: Option<&'a Expression<'a>>,
right: Option<&'a Expression<'a>>, right: Option<&'a Expression<'a>>,
length: usize,
span: &Span, span: &Span,
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> { ) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
let array = match self.enforce_expression(cs, array)? { let array = match self.enforce_expression(cs, array)? {
@ -56,12 +114,98 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
let from_resolved = match left { let from_resolved = match left {
Some(from_index) => self.enforce_index(cs, from_index, span)?, Some(from_index) => self.enforce_index(cs, from_index, span)?,
None => 0usize, // Array slice starts at index 0 None => Integer::new(&ConstInt::U32(0)), // Array slice starts at index 0
}; };
let to_resolved = match right { let to_resolved = match right {
Some(to_index) => self.enforce_index(cs, to_index, span)?, Some(to_index) => self.enforce_index(cs, to_index, span)?,
None => array.len(), // Array slice ends at array length // todo: handle out of bounds for array len
None => Integer::new(&ConstInt::U32(array.len() as u32)), // Array slice ends at array length
}; };
Ok(ConstrainedValue::Array(array[from_resolved..to_resolved].to_owned())) let const_dimensions = match (from_resolved.to_usize(), to_resolved.to_usize()) {
(Some(from), Some(to)) => Some((from, to)),
(Some(from), None) => Some((from, from + length)),
(None, Some(to)) => Some((to - length, to)),
(None, None) => None,
};
Ok(if let Some((left, right)) = const_dimensions {
if right - left != length {
return Err(ExpressionError::array_invalid_slice_length(span));
}
if right > array.len() {
return Err(ExpressionError::array_index_out_of_bounds(right, span));
}
ConstrainedValue::Array(array[left..right].to_owned())
} else {
{
let calc_len = enforce_sub::<F, G, _>(
cs,
ConstrainedValue::Integer(to_resolved.clone()),
ConstrainedValue::Integer(from_resolved.clone()),
span,
)?;
let calc_len = match calc_len {
ConstrainedValue::Integer(i) => i,
_ => unimplemented!("illegal non-Integer returned from sub"),
};
let namespace_string = format!(
"evaluate array range access length check {}:{}",
span.line_start, span.col_start
);
let mut unique_namespace = cs.ns(|| namespace_string);
calc_len
.enforce_equal(&mut unique_namespace, &Integer::new(&ConstInt::U32(length as u32)))
.map_err(|e| ExpressionError::cannot_enforce("array length check".to_string(), e, span))?;
}
{
let bounds_check = evaluate_le::<F, G, _>(
cs,
ConstrainedValue::Integer(to_resolved),
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(array.len() as u32))),
span,
)?;
let bounds_check = match bounds_check {
ConstrainedValue::Boolean(b) => b,
_ => unimplemented!("illegal non-Integer returned from le"),
};
let namespace_string = format!(
"evaluate array range access bounds {}:{}",
span.line_start, span.col_start
);
let mut unique_namespace = cs.ns(|| namespace_string);
bounds_check
.enforce_equal(&mut unique_namespace, &Boolean::Constant(true))
.map_err(|e| ExpressionError::cannot_enforce("array bounds check".to_string(), e, span))?;
}
let mut windows = array.windows(length);
let mut result = ConstrainedValue::Array(vec![]);
for i in 0..length {
let window = if let Some(window) = windows.next() {
window
} else {
break;
};
let array_value = ConstrainedValue::Array(window.to_vec());
let mut unique_namespace =
cs.ns(|| format!("array index eq-check {} {}:{}", i, span.line_start, span.col_start));
let equality = evaluate_eq::<F, G, _>(
&mut unique_namespace,
ConstrainedValue::Integer(from_resolved.clone()),
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(i as u32))),
span,
)?;
let equality = match equality {
ConstrainedValue::Boolean(b) => b,
_ => unimplemented!("unexpected non-Boolean for evaluate_eq"),
};
let unique_namespace =
unique_namespace.ns(|| format!("array index {} {}:{}", i, span.line_start, span.col_start));
result = ConstrainedValue::conditionally_select(unique_namespace, &equality, &array_value, &result)
.map_err(|e| ExpressionError::cannot_enforce("conditional select".to_string(), e, span))?;
}
result
})
} }
} }

View File

@ -16,7 +16,7 @@
//! Enforces an array index expression in a compiled Leo program. //! Enforces an array index expression in a compiled Leo program.
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType, Integer};
use leo_asg::{Expression, Span}; use leo_asg::{Expression, Span};
use snarkvm_fields::PrimeField; use snarkvm_fields::PrimeField;
@ -28,9 +28,9 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
cs: &mut CS, cs: &mut CS,
index: &'a Expression<'a>, index: &'a Expression<'a>,
span: &Span, span: &Span,
) -> Result<usize, ExpressionError> { ) -> Result<Integer, ExpressionError> {
match self.enforce_expression(cs, index)? { match self.enforce_expression(cs, index)? {
ConstrainedValue::Integer(number) => Ok(number.to_usize(span)?), ConstrainedValue::Integer(number) => Ok(number),
value => Err(ExpressionError::invalid_index(value.to_string(), span)), value => Err(ExpressionError::invalid_index(value.to_string(), span)),
} }
} }

View File

@ -32,8 +32,15 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
left: &'a Expression<'a>, left: &'a Expression<'a>,
right: &'a Expression<'a>, right: &'a Expression<'a>,
) -> Result<ConstrainedValuePair<'a, F, G>, ExpressionError> { ) -> Result<ConstrainedValuePair<'a, F, G>, ExpressionError> {
let resolved_left = self.enforce_expression(cs, left)?; let resolved_left = {
let resolved_right = self.enforce_expression(cs, right)?; let mut left_namespace = cs.ns(|| "left".to_string());
self.enforce_expression(&mut left_namespace, left)?
};
let resolved_right = {
let mut right_namespace = cs.ns(|| "right".to_string());
self.enforce_expression(&mut right_namespace, right)?
};
Ok((resolved_left, resolved_right)) Ok((resolved_left, resolved_right))
} }

View File

@ -133,9 +133,13 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => { Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
self.enforce_array_access(cs, array.get(), index.get(), span) self.enforce_array_access(cs, array.get(), index.get(), span)
} }
Expression::ArrayRangeAccess(ArrayRangeAccessExpression { array, left, right, .. }) => { Expression::ArrayRangeAccess(ArrayRangeAccessExpression {
self.enforce_array_range_access(cs, array.get(), left.get(), right.get(), span) array,
} left,
right,
length,
..
}) => self.enforce_array_range_access(cs, array.get(), left.get(), right.get(), *length, span),
// Tuples // Tuples
Expression::TupleInit(TupleInitExpression { elements, .. }) => self.enforce_tuple(cs, &elements[..]), Expression::TupleInit(TupleInitExpression { elements, .. }) => self.enforce_tuple(cs, &elements[..]),

View File

@ -40,7 +40,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
// Check for out of bounds access. // Check for out of bounds access.
if index > tuple.len() - 1 { if index > tuple.len() - 1 {
// probably safe to be a panic here // probably safe to be a panic here
return Err(ExpressionError::index_out_of_bounds(index, span)); return Err(ExpressionError::tuple_index_out_of_bounds(index, span));
} }
Ok(tuple[index].to_owned()) Ok(tuple[index].to_owned())

View File

@ -51,15 +51,31 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
let start_index = left let start_index = left
.get() .get()
.map(|start| self.enforce_index(cs, start, span)) .map(|start| self.enforce_index(cs, start, span))
.transpose()?
.map(|x| {
x.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(span))
})
.transpose()?;
let stop_index = right
.get()
.map(|stop| self.enforce_index(cs, stop, span))
.transpose()?
.map(|x| {
x.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(span))
})
.transpose()?; .transpose()?;
let stop_index = right.get().map(|stop| self.enforce_index(cs, stop, span)).transpose()?;
output.push(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index)); output.push(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index));
Ok(inner) Ok(inner)
} }
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => { Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
let inner = self.prepare_mut_access(cs, array.get(), span, output)?; let inner = self.prepare_mut_access(cs, array.get(), span, output)?;
let index = self.enforce_index(cs, index.get(), span)?; let index = self
.enforce_index(cs, index.get(), span)?
.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(span))?;
output.push(ResolvedAssigneeAccess::ArrayIndex(index)); output.push(ResolvedAssigneeAccess::ArrayIndex(index));
Ok(inner) Ok(inner)

View File

@ -45,19 +45,35 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
let start_index = start let start_index = start
.get() .get()
.map(|start| self.enforce_index(cs, start, &span)) .map(|start| self.enforce_index(cs, start, &span))
.transpose()?
.map(|x| {
x.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(&span))
})
.transpose()?;
let stop_index = stop
.get()
.map(|stop| self.enforce_index(cs, stop, &span))
.transpose()?
.map(|x| {
x.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(&span))
})
.transpose()?; .transpose()?;
let stop_index = stop.get().map(|stop| self.enforce_index(cs, stop, &span)).transpose()?;
Ok(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index)) Ok(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index))
} }
AssignAccess::ArrayIndex(index) => { AssignAccess::ArrayIndex(index) => {
let index = self.enforce_index(cs, index.get(), &span)?; let index = self
.enforce_index(cs, index.get(), &span)?
.to_usize()
.ok_or_else(|| StatementError::array_assign_index_const(&span))?;
Ok(ResolvedAssigneeAccess::ArrayIndex(index)) Ok(ResolvedAssigneeAccess::ArrayIndex(index))
} }
AssignAccess::Tuple(index) => Ok(ResolvedAssigneeAccess::Tuple(*index, span.clone())), AssignAccess::Tuple(index) => Ok(ResolvedAssigneeAccess::Tuple(*index, span.clone())),
AssignAccess::Member(identifier) => Ok(ResolvedAssigneeAccess::Member(identifier.clone())), AssignAccess::Member(identifier) => Ok(ResolvedAssigneeAccess::Member(identifier.clone())),
}) })
.collect::<Result<Vec<_>, crate::errors::ExpressionError>>()?; .collect::<Result<Vec<_>, StatementError>>()?;
let variable = assignee.target_variable.get().borrow(); let variable = assignee.target_variable.get().borrow();

View File

@ -17,6 +17,7 @@
//! Enforces an iteration statement in a compiled Leo program. //! Enforces an iteration statement in a compiled Leo program.
use crate::{ use crate::{
errors::StatementError,
program::ConstrainedProgram, program::ConstrainedProgram,
value::ConstrainedValue, value::ConstrainedValue,
GroupType, GroupType,
@ -42,8 +43,14 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
let span = statement.span.clone().unwrap_or_default(); let span = statement.span.clone().unwrap_or_default();
let from = self.enforce_index(cs, statement.start.get(), &span)?; let from = self
let to = self.enforce_index(cs, statement.stop.get(), &span)?; .enforce_index(cs, statement.start.get(), &span)?
.to_usize()
.ok_or_else(|| StatementError::loop_index_const(&span))?;
let to = self
.enforce_index(cs, statement.stop.get(), &span)?
.to_usize()
.ok_or_else(|| StatementError::loop_index_const(&span))?;
for i in from..to { for i in from..to {
// Store index in current function scope. // Store index in current function scope.

View File

@ -17,7 +17,7 @@
//! Enforces a statement in a compiled Leo program. //! Enforces a statement in a compiled Leo program.
use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
use leo_asg::Statement; use leo_asg::{Node, Statement};
use snarkvm_fields::PrimeField; use snarkvm_fields::PrimeField;
use snarkvm_gadgets::traits::utilities::boolean::Boolean; use snarkvm_gadgets::traits::utilities::boolean::Boolean;
@ -42,6 +42,9 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
statement: &'a Statement<'a>, statement: &'a Statement<'a>,
) -> StatementResult<Vec<IndicatorAndConstrainedValue<'a, F, G>>> { ) -> StatementResult<Vec<IndicatorAndConstrainedValue<'a, F, G>>> {
let mut results = vec![]; let mut results = vec![];
let span = statement.span().cloned().unwrap_or_default();
let mut cs = cs.ns(|| format!("statement {}:{}", span.line_start, span.col_start));
let cs = &mut cs;
match statement { match statement {
Statement::Return(statement) => { Statement::Return(statement) => {

View File

@ -111,15 +111,9 @@ impl Integer {
match_integer!(integer => integer.get_value()) match_integer!(integer => integer.get_value())
} }
pub fn to_usize(&self, span: &Span) -> Result<usize, IntegerError> { pub fn to_usize(&self) -> Option<usize> {
let unsigned_integer = self; let unsigned_integer = self;
let value_option: Option<String> = match_unsigned_integer!(unsigned_integer => unsigned_integer.get_value()); match_unsigned_integer!(unsigned_integer => unsigned_integer.get_index())
let value = value_option.ok_or_else(|| IntegerError::invalid_index(span))?;
let value_usize = value
.parse::<usize>()
.map_err(|_| IntegerError::invalid_integer(value, span))?;
Ok(value_usize)
} }
pub fn get_type(&self) -> IntegerType { pub fn get_type(&self) -> IntegerType {

View File

@ -539,3 +539,153 @@ fn test_variable_slice_fail() {
expect_asg_error(error); expect_asg_error(error);
} }
#[test]
fn test_array_index() {
let program_string = r#"
function main(i: u32) {
let b = [1u8, 2, 3, 4];
console.assert(2 == b[i]);
console.assert(3 == b[2]);
}
"#;
let input_string = r#"
[main]
i: u32 = 1;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
assert_satisfied(program);
}
#[test]
fn test_array_index_bounds_fail() {
let program_string = r#"
function main(i: u32) {
let b = [1u8, 2, 3, 4];
console.assert(2 == b[i]);
}
"#;
let input_string = r#"
[main]
i: u32 = 4;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_array_range_index() {
let program_string = r#"
function main(i: u32) {
let b = [1u8, 2, 3, 4];
console.assert([1u8, 2] == b[0..i]);
console.assert([3u8, 4] == b[i..4]);
}
"#;
let input_string = r#"
[main]
i: u32 = 2;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
assert_satisfied(program);
}
#[test]
fn test_array_range_index_dyn() {
let program_string = r#"
function main(i: u32) {
let b = [1u8, 2, 3, 4];
console.assert([1u8, 2] == b[..i]);
console.assert([3u8, 4] == b[i..]);
}
"#;
let input_string = r#"
[main]
i: u32 = 2;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
assert_satisfied(program);
}
#[test]
fn test_array_range_index_full_dyn() {
let program_string = r#"
function main(i: u32, y: u32) {
let b = [1u8, 2, 3, 4];
console.assert([3u8, 4] == b[i..y]);
}
"#;
let input_string = r#"
[main]
i: u32 = 2;
y: u32 = 4;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
assert_satisfied(program);
}
#[test]
fn test_array_range_index_fail_bounds() {
let program_string = r#"
function main(i: u32, y: u32) {
let b = [1u8, 2, 3, 4];
console.assert([1, 2] == b[3..5]);
}
"#;
let input_string = r#"
[main]
i: u32 = 2;
"#;
let err = parse_program_with_input(program_string, input_string).is_err();
assert!(err);
}
#[test]
fn test_array_range_index_full_dyn_resized_fail() {
let program_string = r#"
function main(i: u32, y: u32) {
let b = [1u8, 2, 3, 4];
console.assert([3u8, 4] == b[i..y]);
}
"#;
let input_string = r#"
[main]
i: u32 = 1;
y: u32 = 4;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_array_range_index_full_dyn_bounds_fail() {
let program_string = r#"
function main(i: u32, y: u32) {
let b = [1u8, 2, 3, 4];
console.assert([3u8, 4] == b[i..y]);
}
"#;
let input_string = r#"
[main]
i: u32 = 3;
y: u32 = 5;
"#;
let program = parse_program_with_input(program_string, input_string).unwrap();
expect_compiler_error(program);
}

View File

@ -5,5 +5,5 @@ function main() {
do_nothing(arr); do_nothing(arr);
do_nothing([...arr]); do_nothing([...arr]);
do_nothing(arr[1u32..]); do_nothing(arr[0u32..]);
} }