Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,9 +839,10 @@ impl<'a> Context<'a> {
self.handle_ssa_call_outputs(result_ids, outputs, dfg)?;
}
Value::ForeignFunction(_) => {
// TODO: Remove this once elaborator is default frontend. This is now caught by a lint inside the frontend.
return Err(RuntimeError::UnconstrainedOracleReturnToConstrained {
call_stack: self.acir_context.get_call_stack(),
})
});
}
_ => unreachable!("expected calling a function but got {function_value:?}"),
}
Expand Down
36 changes: 35 additions & 1 deletion compiler/noirc_frontend/src/elaborator/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
HirExpression, HirLiteral, NodeInterner, NoirFunction, UnaryOp, UnresolvedTypeData,
Visibility,
},
node_interner::{DefinitionKind, ExprId},
node_interner::{DefinitionKind, ExprId, FuncId},
Type,
};
use acvm::AcirField;
Expand Down Expand Up @@ -72,6 +72,40 @@ pub(super) fn low_level_function_outside_stdlib(
}
}

/// Oracle definitions (functions with the `#[oracle]` attribute) must be marked as unconstrained.
pub(super) fn oracle_not_marked_unconstrained(func: &NoirFunction) -> Option<ResolverError> {
let is_oracle_function =
func.attributes().function.as_ref().map_or(false, |func| func.is_oracle());
if is_oracle_function && !func.def.is_unconstrained {
Some(ResolverError::OracleMarkedAsConstrained { ident: func.name_ident().clone() })
} else {
None
}
}

/// Oracle functions may not be called by constrained functions directly.
///
/// In order for a constrained function to call an oracle it must first call through an unconstrained function.
pub(super) fn oracle_called_from_constrained_function(
interner: &NodeInterner,
called_func: &FuncId,
calling_from_constrained_runtime: bool,
span: Span,
) -> Option<ResolverError> {
if !calling_from_constrained_runtime {
return None;
}

let function_attributes = interner.function_attributes(called_func);
let is_oracle_call =
function_attributes.function.as_ref().map_or(false, |func| func.is_oracle());
if is_oracle_call {
Some(ResolverError::UnconstrainedOracleReturnToConstrained { span })
} else {
None
}
}

/// `pub` is required on return types for entry point functions
pub(super) fn missing_pub(func: &NoirFunction, is_entry_point: bool) -> Option<ResolverError> {
if is_entry_point
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ impl<'context> Elaborator<'context> {
self.run_lint(|elaborator| {
lints::unnecessary_pub_return(func, elaborator.pub_allowed(func)).map(Into::into)
});
self.run_lint(|_| lints::oracle_not_marked_unconstrained(func).map(Into::into));
self.run_lint(|elaborator| {
lints::low_level_function_outside_stdlib(func, elaborator.crate_id).map(Into::into)
});
Expand Down
28 changes: 19 additions & 9 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
},
hir_def::{
expr::{
HirBinaryOp, HirCallExpression, HirIdent, HirMemberAccess, HirMethodReference,
HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference,
HirPrefixExpression,
},
function::{FuncMeta, Parameters},
Expand Down Expand Up @@ -1150,6 +1150,19 @@ impl<'context> Elaborator<'context> {
let is_unconstrained_call = self.is_unconstrained_call(call.func);
let crossing_runtime_boundary = is_current_func_constrained && is_unconstrained_call;
if crossing_runtime_boundary {
let called_func_id = self
.interner
.lookup_function_from_expr(&call.func)
.expect("Called function should exist");
self.run_lint(|elaborator| {
lints::oracle_called_from_constrained_function(
elaborator.interner,
&called_func_id,
is_current_func_constrained,
span,
)
.map(Into::into)
});
let errors = lints::unconstrained_function_args(&args);
for error in errors {
self.push_err(error);
Expand All @@ -1168,15 +1181,12 @@ impl<'context> Elaborator<'context> {
}

fn is_unconstrained_call(&self, expr: ExprId) -> bool {
if let HirExpression::Ident(HirIdent { id, .. }, _) = self.interner.expression(&expr) {
if let Some(DefinitionKind::Function(func_id)) =
self.interner.try_definition(id).map(|def| &def.kind)
{
let modifiers = self.interner.function_modifiers(func_id);
return modifiers.is_unconstrained;
}
if let Some(func_id) = self.interner.lookup_function_from_expr(&expr) {
let modifiers = self.interner.function_modifiers(&func_id);
modifiers.is_unconstrained
} else {
false
}
false
}

/// Check if the given method type requires a mutable reference to the object type, and check
Expand Down
16 changes: 16 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ pub enum ResolverError {
AbiAttributeOutsideContract { span: Span },
#[error("Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library")]
LowLevelFunctionOutsideOfStdlib { ident: Ident },
#[error(
"Usage of the `#[oracle]` function attribute is only valid on unconstrained functions"
)]
OracleMarkedAsConstrained { ident: Ident },
#[error("Oracle functions cannot be called directly from constrained functions")]
UnconstrainedOracleReturnToConstrained { span: Span },
#[error("Dependency cycle found, '{item}' recursively depends on itself: {cycle} ")]
DependencyCycle { span: Span, item: String, cycle: String },
#[error("break/continue are only allowed in unconstrained functions")]
Expand Down Expand Up @@ -327,6 +333,16 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
"Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library".into(),
ident.span(),
),
ResolverError::OracleMarkedAsConstrained { ident } => Diagnostic::simple_error(
error.to_string(),
"Oracle functions must have the `unconstrained` keyword applied".into(),
ident.span(),
),
ResolverError::UnconstrainedOracleReturnToConstrained { span } => Diagnostic::simple_error(
error.to_string(),
"This oracle call must be wrapped in a call to another unconstrained function before being returned to a constrained runtime".into(),
*span,
),
ResolverError::DependencyCycle { span, item, cycle } => {
Diagnostic::simple_error(
"Dependency cycle found".into(),
Expand Down
16 changes: 16 additions & 0 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::hir::def_map::{LocalModuleId, ModuleId};

use crate::ast::{BinaryOpKind, FunctionDefinition, ItemVisibility};
use crate::hir::resolution::errors::ResolverError;
use crate::hir_def::expr::HirIdent;
use crate::hir_def::stmt::HirLetStatement;
use crate::hir_def::traits::TraitImpl;
use crate::hir_def::traits::{Trait, TraitConstraint};
Expand Down Expand Up @@ -824,6 +825,21 @@ impl NodeInterner {
self.function_modules[&func]
}

/// Returns the [`FuncId`] corresponding to the function referred to by `expr_id`
pub fn lookup_function_from_expr(&self, expr: &ExprId) -> Option<FuncId> {
if let HirExpression::Ident(HirIdent { id, .. }, _) = self.expression(expr) {
if let Some(DefinitionKind::Function(func_id)) =
self.try_definition(id).map(|def| &def.kind)
{
Some(*func_id)
} else {
None
}
} else {
None
}
}

/// Returns the interned HIR function corresponding to `func_id`
//
// Cloning HIR structures is cheap, so we return owned structures
Expand Down
2 changes: 1 addition & 1 deletion test_programs/execution_success/schnorr/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn main(
let pub_key = embedded_curve_ops::EmbeddedCurvePoint { x: pub_key_x, y: pub_key_y, is_infinite: false };
let valid_signature = verify_signature_noir(pub_key, signature, message2);
assert(valid_signature);
assert_valid_signature(pub_key,signature,message2);
assert_valid_signature(pub_key, signature, message2);
}

// TODO: to put in the stdlib once we have numeric generics
Expand Down