diff --git a/air/src/execution_step/air/ap/utils.rs b/air/src/execution_step/air/ap/utils.rs index 977628f6..9893afda 100644 --- a/air/src/execution_step/air/ap/utils.rs +++ b/air/src/execution_step/air/ap/utils.rs @@ -44,13 +44,13 @@ fn match_position_variable( generation: Option, ap_result: &MergerApResult, ) -> ExecutionResult<()> { - use crate::execution_step::ExecutionError::ApResultNotCorrespondToInstr; + use crate::execution_step::UncatchableError::ApResultNotCorrespondToInstr; use ast::Variable::*; match (variable, generation) { (Stream(_), Some(_)) => Ok(()), (Scalar(_), None) => Ok(()), - _ => return crate::exec_err!(ApResultNotCorrespondToInstr(ap_result.clone())), + _ => Err(ApResultNotCorrespondToInstr(ap_result.clone()).into()), } } diff --git a/air/src/execution_step/air/call.rs b/air/src/execution_step/air/call.rs index c04d4c73..f2c21493 100644 --- a/air/src/execution_step/air/call.rs +++ b/air/src/execution_step/air/call.rs @@ -40,25 +40,26 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> { log_instruction!(call, exec_ctx, trace_ctx); exec_ctx.tracker.meet_call(); - let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| { - set_last_error(self, exec_ctx, e.clone(), None); - e - })?; + let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx) + .map_err(|e| set_last_error(self, exec_ctx, e, None))?; let tetraplet = resolved_call.as_tetraplet(); - joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx).map_err(|e| { - set_last_error(self, exec_ctx, e.clone(), Some(tetraplet)); - e - }) + joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx) + .map_err(|e| set_last_error(self, exec_ctx, e, Some(tetraplet))) } } fn set_last_error<'i>( call: &Call<'i>, exec_ctx: &mut ExecutionCtx<'i>, - e: Rc, + execution_error: ExecutionError, tetraplet: Option, -) { +) -> ExecutionError { + let catchable_error = match execution_error { + ExecutionError::Catchable(catchable) => catchable, + ExecutionError::Uncatchable(_) => return execution_error, + }; + let current_peer_id = match &tetraplet { // use tetraplet if they set, because an error could be propagated from data // (from CallServiceFailed state) and exec_ctx.current_peer_id won't mean @@ -67,10 +68,16 @@ fn set_last_error<'i>( None => exec_ctx.current_peer_id.to_string(), }; - log::warn!("call failed with an error `{}`, peerId `{}`", e, current_peer_id); + log::warn!( + "call failed with an error `{}`, peerId `{}`", + catchable_error, + current_peer_id + ); let instruction = call.to_string(); - let last_error = LastErrorDescriptor::new(e, instruction, current_peer_id, tetraplet); + let last_error = LastErrorDescriptor::new(catchable_error.clone(), instruction, current_peer_id, tetraplet); exec_ctx.last_error = Some(last_error); exec_ctx.last_error_could_be_set = false; + + ExecutionError::Catchable(catchable_error) } diff --git a/air/src/execution_step/air/call/prev_result_handler.rs b/air/src/execution_step/air/call/prev_result_handler.rs index 140a0da2..56acc86b 100644 --- a/air/src/execution_step/air/call/prev_result_handler.rs +++ b/air/src/execution_step/air/call/prev_result_handler.rs @@ -15,8 +15,8 @@ */ use super::*; -use crate::exec_err; use crate::execution_step::air::call::call_result_setter::set_result_from_value; +use crate::execution_step::CatchableError; use crate::execution_step::RSecurityTetraplet; use air_interpreter_data::CallResult; @@ -51,7 +51,7 @@ pub(super) fn handle_prev_state<'i>( let ret_code = *ret_code; let err_msg = err_msg.clone(); trace_ctx.meet_call_end(prev_result); - exec_err!(ExecutionError::LocalServiceError(ret_code, err_msg)) + Err(CatchableError::LocalServiceError(ret_code, err_msg).into()) } RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id }) if peer_id.as_str() == exec_ctx.current_peer_id.as_str() => @@ -126,12 +126,11 @@ fn handle_service_error( } let error_message = Rc::new(service_result.result); - let error = ExecutionError::LocalServiceError(service_result.ret_code, error_message.clone()); - let error = Rc::new(error); + let error = CatchableError::LocalServiceError(service_result.ret_code, error_message.clone()); trace_ctx.meet_call_end(CallServiceFailed(service_result.ret_code, error_message)); - Err(error) + Err(error.into()) } fn try_to_service_result( @@ -152,7 +151,7 @@ fn try_to_service_result( let error = CallServiceFailed(i32::MAX, error_msg.clone()); trace_ctx.meet_call_end(error); - Err(Rc::new(ExecutionError::LocalServiceError(i32::MAX, error_msg))) + Err(CatchableError::LocalServiceError(i32::MAX, error_msg).into()) } } } diff --git a/air/src/execution_step/air/call/resolved_call.rs b/air/src/execution_step/air/call/resolved_call.rs index c5ee26b7..eed77c0f 100644 --- a/air/src/execution_step/air/call/resolved_call.rs +++ b/air/src/execution_step/air/call/resolved_call.rs @@ -22,6 +22,7 @@ use super::triplet::resolve; use super::*; use crate::execution_step::RSecurityTetraplet; use crate::execution_step::SecurityTetraplets; +use crate::execution_step::UncatchableError; use crate::trace_to_exec_err; use crate::JValue; use crate::SecurityTetraplet; @@ -187,10 +188,10 @@ fn check_output_name(output: &ast::CallOutputValue<'_>, exec_ctx: &ExecutionCtx< if exec_ctx.scalars.shadowing_allowed() { Ok(()) } else { - crate::exec_err!(ExecutionError::MultipleVariablesFound(scalar_name.to_string())) + Err(UncatchableError::MultipleVariablesFound(scalar_name.to_string()).into()) } } - Ok(ScalarRef::IterableValue(_)) => crate::exec_err!(ExecutionError::IterableShadowing(scalar_name.to_string())), + Ok(ScalarRef::IterableValue(_)) => Err(UncatchableError::IterableShadowing(scalar_name.to_string()).into()), Err(_) => Ok(()), } } diff --git a/air/src/execution_step/air/call/triplet.rs b/air/src/execution_step/air/call/triplet.rs index 996c44f7..d7cdb010 100644 --- a/air/src/execution_step/air/call/triplet.rs +++ b/air/src/execution_step/air/call/triplet.rs @@ -15,9 +15,8 @@ */ use super::ExecutionCtx; -use super::ExecutionError; use super::ExecutionResult; -use crate::exec_err; +use crate::execution_step::CatchableError; use crate::JValue; use air_parser::ast; @@ -63,10 +62,11 @@ fn resolve_to_string<'i>(value: &ast::CallInstrValue<'i>, ctx: &ExecutionCtx<'i> fn try_jvalue_to_string(jvalue: JValue, variable: &ast::VariableWithLambda<'_>) -> ExecutionResult { match jvalue { JValue::String(s) => Ok(s), - _ => exec_err!(ExecutionError::IncompatibleJValueType { + _ => Err(CatchableError::IncompatibleJValueType { variable_name: variable.name().to_string(), actual_value: jvalue, expected_value_type: "string", - }), + } + .into()), } } diff --git a/air/src/execution_step/air/fail.rs b/air/src/execution_step/air/fail.rs index 5b191612..5bdecc75 100644 --- a/air/src/execution_step/air/fail.rs +++ b/air/src/execution_step/air/fail.rs @@ -15,10 +15,10 @@ */ use super::ExecutionCtx; -use super::ExecutionError; use super::ExecutionResult; use super::LastErrorDescriptor; use super::TraceHandler; +use crate::execution_step::CatchableError; use crate::log_instruction; use air_parser::ast::Fail; @@ -48,7 +48,7 @@ fn fail_with_literals<'i>( fail: &Fail<'_>, exec_ctx: &mut ExecutionCtx<'i>, ) -> ExecutionResult<()> { - let fail_error = ExecutionError::FailWithoutXorError { + let fail_error = CatchableError::FailWithoutXorError { ret_code, error_message: error_message.to_string(), }; @@ -71,7 +71,7 @@ fn fail_with_literals<'i>( update_context_state(exec_ctx); - Err(fail_error) + Err(fail_error.into()) } fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> { @@ -81,7 +81,7 @@ fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> }; update_context_state(exec_ctx); - Err(last_error) + Err(last_error.into()) } fn update_context_state(exec_ctx: &mut ExecutionCtx<'_>) { diff --git a/air/src/execution_step/air/fold/mod.rs b/air/src/execution_step/air/fold/mod.rs index 457ae848..3dca8ad5 100644 --- a/air/src/execution_step/air/fold/mod.rs +++ b/air/src/execution_step/air/fold/mod.rs @@ -22,7 +22,6 @@ pub(crate) use fold_state::IterableType; pub(super) use utils::*; use super::ExecutionCtx; -use super::ExecutionError; use super::ExecutionResult; use super::Instruction; use super::ScalarRef; diff --git a/air/src/execution_step/air/fold/utils.rs b/air/src/execution_step/air/fold/utils.rs index d5105d7d..0e109388 100644 --- a/air/src/execution_step/air/fold/utils.rs +++ b/air/src/execution_step/air/fold/utils.rs @@ -15,7 +15,7 @@ */ use super::*; -use crate::exec_err; +use crate::execution_step::CatchableError; use crate::execution_step::RSecurityTetraplet; use crate::JValue; use crate::LambdaAST; @@ -107,11 +107,12 @@ fn from_call_result(call_result: ValueAggregate, variable_name: &str) -> Executi array.len() } v => { - return exec_err!(ExecutionError::IncompatibleJValueType { + return Err(CatchableError::IncompatibleJValueType { variable_name: variable_name.to_string(), actual_value: (*v).clone(), expected_value_type: "array", - }) + } + .into()); } }; @@ -156,10 +157,7 @@ fn from_jvalue( let iterable = match jvalue { JValue::Array(array) => array, _ => { - return exec_err!(ExecutionError::FoldIteratesOverNonArray( - jvalue.clone(), - formatted_lambda_ast - )) + return Err(CatchableError::FoldIteratesOverNonArray(jvalue.clone(), formatted_lambda_ast).into()); } }; diff --git a/air/src/execution_step/air/match_.rs b/air/src/execution_step/air/match_.rs index 22482ebe..aadb8738 100644 --- a/air/src/execution_step/air/match_.rs +++ b/air/src/execution_step/air/match_.rs @@ -16,9 +16,9 @@ use super::compare_matchable::are_matchable_eq; use super::ExecutionCtx; -use super::ExecutionError; use super::ExecutionResult; use super::TraceHandler; +use crate::execution_step::CatchableError; use crate::execution_step::Joinable; use crate::joinable; use crate::log_instruction; @@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for Match<'i> { )?; if !are_values_equal { - return crate::exec_err!(ExecutionError::MatchWithoutXorError); + return Err(CatchableError::MatchWithoutXorError.into()); } self.instruction.execute(exec_ctx, trace_ctx) diff --git a/air/src/execution_step/air/mismatch.rs b/air/src/execution_step/air/mismatch.rs index 5e72db3f..1bc2b203 100644 --- a/air/src/execution_step/air/mismatch.rs +++ b/air/src/execution_step/air/mismatch.rs @@ -16,9 +16,9 @@ use super::compare_matchable::are_matchable_eq; use super::ExecutionCtx; -use super::ExecutionError; use super::ExecutionResult; use super::TraceHandler; +use crate::execution_step::CatchableError; use crate::execution_step::Joinable; use crate::joinable; use crate::log_instruction; @@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for MisMatch<'i> { )?; if are_values_equal { - return crate::exec_err!(ExecutionError::MismatchWithoutXorError); + return Err(CatchableError::MismatchWithoutXorError.into()); } self.instruction.execute(exec_ctx, trace_ctx) diff --git a/air/src/execution_step/air/mod.rs b/air/src/execution_step/air/mod.rs index a60358a6..d7ad1880 100644 --- a/air/src/execution_step/air/mod.rs +++ b/air/src/execution_step/air/mod.rs @@ -35,7 +35,6 @@ pub(crate) use fold::FoldState; use super::boxed_value::ScalarRef; use super::boxed_value::ValueAggregate; use super::execution_context::*; -use super::Catchable; use super::ExecutionCtx; use super::ExecutionError; use super::ExecutionResult; @@ -47,48 +46,68 @@ use air_parser::ast::Instruction; /// Executes instruction and updates last error if needed. macro_rules! execute { - ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => { - match $instr.execute($exec_ctx, $trace_ctx) { - Err(e) => { - if !$exec_ctx.last_error_could_be_set { - return Err(e); - } + ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{ + let result = $instr.execute($exec_ctx, $trace_ctx); - let instruction = format!("{}", $self); - let last_error = - LastErrorDescriptor::new(e.clone(), instruction, $exec_ctx.current_peer_id.to_string(), None); - $exec_ctx.last_error = Some(last_error); - Err(e) - } - v => v, + if !$exec_ctx.last_error_could_be_set { + return result; } - }; + + let execution_error = match result { + Err(e) => e, + v => return v, + }; + + let catchable_error = match execution_error { + // handle only catchable errors + crate::execution_step::ExecutionError::Catchable(e) => e, + e => return Err(e), + }; + + let instruction = $self.to_string(); + let last_error = LastErrorDescriptor::new( + catchable_error.clone(), + instruction, + $exec_ctx.current_peer_id.to_string(), + None, + ); + $exec_ctx.last_error = Some(last_error); + + Err(catchable_error.into()) + }}; } /// Executes match/mismatch instructions and updates last error if error type wasn't /// MatchWithoutXorError or MismatchWithoutXorError. -macro_rules! execute_match_mismatch { - ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => { - match $instr.execute($exec_ctx, $trace_ctx) { - Err(e) => { - use std::borrow::Borrow; +macro_rules! execute_match_or_mismatch { + ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{ + let result = $instr.execute($exec_ctx, $trace_ctx); + let execution_error = match result { + Err(e) => e, + v => return v, + }; - if !$exec_ctx.last_error_could_be_set - || matches!(&*e.borrow(), ExecutionError::MatchWithoutXorError) - || matches!(&*e.borrow(), ExecutionError::MismatchWithoutXorError) - { - return Err(e); - } - - let instruction = format!("{}", $self); - let last_error = - LastErrorDescriptor::new(e.clone(), instruction, $exec_ctx.current_peer_id.to_string(), None); - $exec_ctx.last_error = Some(last_error); - Err(e) - } - v => v, + if !$exec_ctx.last_error_could_be_set || execution_error.is_match_or_mismatch() { + return Err(execution_error); } - }; + + let catchable_error = match execution_error { + // handle only catchable errors + crate::execution_step::ExecutionError::Catchable(e) => e, + e => return Err(e), + }; + + let instruction = $self.to_string(); + let last_error = LastErrorDescriptor::new( + catchable_error.clone(), + instruction, + $exec_ctx.current_peer_id.to_string(), + None, + ); + $exec_ctx.last_error = Some(last_error); + + Err(catchable_error.into()) + }}; } pub(crate) trait ExecutableInstruction<'i> { @@ -114,8 +133,8 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> { Instruction::Xor(xor) => execute!(self, xor, exec_ctx, trace_ctx), // match/mismatch shouldn't rewrite last_error - Instruction::Match(match_) => execute_match_mismatch!(self, match_, exec_ctx, trace_ctx), - Instruction::MisMatch(mismatch) => execute_match_mismatch!(self, mismatch, exec_ctx, trace_ctx), + Instruction::Match(match_) => execute_match_or_mismatch!(self, match_, exec_ctx, trace_ctx), + Instruction::MisMatch(mismatch) => execute_match_or_mismatch!(self, mismatch, exec_ctx, trace_ctx), Instruction::Error => unreachable!("should not execute if parsing succeeded. QED."), } diff --git a/air/src/execution_step/air/par.rs b/air/src/execution_step/air/par.rs index 6a36f57d..52031b1a 100644 --- a/air/src/execution_step/air/par.rs +++ b/air/src/execution_step/air/par.rs @@ -16,7 +16,6 @@ mod completeness_updater; -use super::Catchable; use super::ExecutableInstruction; use super::ExecutionCtx; use super::ExecutionError; @@ -30,8 +29,6 @@ use completeness_updater::ParCompletenessUpdater; use air_parser::ast::Par; use air_trace_handler::SubtreeType; -use std::rc::Rc; - #[rustfmt::skip] impl<'i> ExecutableInstruction<'i> for Par<'i> { fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> { @@ -84,7 +81,7 @@ fn execute_subtree<'i>( enum SubtreeResult { Succeeded, - Failed(Rc), + Failed(ExecutionError), } fn prepare_par_result( diff --git a/air/src/execution_step/air/xor.rs b/air/src/execution_step/air/xor.rs index e370aab1..6a7dbbfb 100644 --- a/air/src/execution_step/air/xor.rs +++ b/air/src/execution_step/air/xor.rs @@ -14,7 +14,6 @@ * limitations under the License. */ -use super::Catchable; use super::ExecutionCtx; use super::ExecutionError; use super::ExecutionResult; @@ -42,12 +41,13 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> { } fn print_xor_log(e: &ExecutionError) { - match e { + if e.is_match_or_mismatch() { // These errors actually aren't real errors, but a way to bubble execution_step up from match // to a corresponding xor. They'll become errors iff there is no such xor and execution_step is // bubble up until the very beginning of current subtree. So the error message shouldn't // be print out in order not to confuse users. - ExecutionError::MatchWithoutXorError | ExecutionError::MismatchWithoutXorError => {} - e => log::warn!("xor caught an error: {}", e), + return; } + + log::warn!("xor caught an error: {}", e); } diff --git a/air/src/execution_step/boxed_value/jvaluable.rs b/air/src/execution_step/boxed_value/jvaluable.rs index 0b113c26..f8c3510d 100644 --- a/air/src/execution_step/boxed_value/jvaluable.rs +++ b/air/src/execution_step/boxed_value/jvaluable.rs @@ -21,7 +21,6 @@ mod resolved_call_result; mod stream; use super::iterable::IterableItem; -use super::ExecutionError; use super::ExecutionResult; use super::ValueAggregate; use crate::execution_step::lambda_applier::*; diff --git a/air/src/execution_step/boxed_value/jvaluable/empty_stream.rs b/air/src/execution_step/boxed_value/jvaluable/empty_stream.rs index 76271c8f..68f2f935 100644 --- a/air/src/execution_step/boxed_value/jvaluable/empty_stream.rs +++ b/air/src/execution_step/boxed_value/jvaluable/empty_stream.rs @@ -14,12 +14,11 @@ * limitations under the License. */ -use super::ExecutionError::LambdaApplierError; use super::ExecutionResult; use super::JValuable; use super::LambdaAST; use super::LambdaError::EmptyStream; -use crate::exec_err; +use crate::execution_step::CatchableError::LambdaApplierError; use crate::execution_step::ExecutionCtx; use crate::execution_step::RSecurityTetraplet; use crate::execution_step::SecurityTetraplets; @@ -30,7 +29,7 @@ use std::borrow::Cow; impl JValuable for () { fn apply_lambda<'i>(&self, _lambda: &LambdaAST<'_>, _exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> { // applying lambda to an empty stream will produce a join behaviour - exec_err!(LambdaApplierError(EmptyStream)) + Err(LambdaApplierError(EmptyStream).into()) } fn apply_lambda_with_tetraplets<'i>( @@ -39,7 +38,7 @@ impl JValuable for () { _exec_ctx: &ExecutionCtx<'i>, ) -> ExecutionResult<(&JValue, RSecurityTetraplet)> { // applying lambda to an empty stream will produce a join behaviour - exec_err!(LambdaApplierError(EmptyStream)) + Err(LambdaApplierError(EmptyStream).into()) } fn as_jvalue(&self) -> Cow<'_, JValue> { diff --git a/air/src/execution_step/boxed_value/jvaluable/stream.rs b/air/src/execution_step/boxed_value/jvaluable/stream.rs index 57bb3233..edb9c732 100644 --- a/air/src/execution_step/boxed_value/jvaluable/stream.rs +++ b/air/src/execution_step/boxed_value/jvaluable/stream.rs @@ -17,7 +17,6 @@ use super::select_from_stream; use super::ExecutionResult; use super::JValuable; -use crate::exec_err; use crate::execution_step::boxed_value::Generation; use crate::execution_step::boxed_value::Stream; use crate::execution_step::ExecutionCtx; @@ -89,7 +88,7 @@ impl<'stream> StreamJvaluableIngredients<'stream> { } pub(self) fn iter(&self) -> ExecutionResult> { - use super::ExecutionError::StreamDontHaveSuchGeneration; + use crate::execution_step::CatchableError::StreamDontHaveSuchGeneration; match self.stream.iter(self.generation) { Some(iter) => Ok(iter), @@ -99,10 +98,7 @@ impl<'stream> StreamJvaluableIngredients<'stream> { Generation::Last => unreachable!(), }; - exec_err!(StreamDontHaveSuchGeneration( - self.stream.deref().clone(), - generation as usize - )) + Err(StreamDontHaveSuchGeneration(self.stream.deref().clone(), generation as usize).into()) } } } diff --git a/air/src/execution_step/boxed_value/mod.rs b/air/src/execution_step/boxed_value/mod.rs index 5058cfbd..fabf23ba 100644 --- a/air/src/execution_step/boxed_value/mod.rs +++ b/air/src/execution_step/boxed_value/mod.rs @@ -20,7 +20,6 @@ mod scalar; mod stream; mod variable; -pub(crate) use super::ExecutionError; pub(crate) use iterable::*; pub(crate) use jvaluable::*; pub(crate) use scalar::ScalarRef; diff --git a/air/src/execution_step/boxed_value/stream.rs b/air/src/execution_step/boxed_value/stream.rs index cac18a49..9025cee1 100644 --- a/air/src/execution_step/boxed_value/stream.rs +++ b/air/src/execution_step/boxed_value/stream.rs @@ -14,10 +14,9 @@ * limitations under the License. */ -use super::ExecutionError; use super::ExecutionResult; use super::ValueAggregate; -use crate::exec_err; +use crate::execution_step::CatchableError; use crate::JValue; use std::fmt::Formatter; @@ -29,9 +28,8 @@ use std::fmt::Formatter; /// of values that interpreter obtained from one particle. It means that number of generation on /// a peer is equal to number of the interpreter runs in context of one particle. And each set of /// obtained values from a current_data that were not present in prev_data becomes a new generation. -// TODO: make it non-pub after boxed value refactoring. #[derive(Debug, Default, Clone)] -pub(crate) struct Stream(Vec>); +pub struct Stream(Vec>); impl Stream { pub(crate) fn from_generations_count(count: usize) -> Self { @@ -51,7 +49,7 @@ impl Stream { }; if generation >= self.0.len() { - return exec_err!(ExecutionError::StreamDontHaveSuchGeneration(self.clone(), generation)); + return Err(CatchableError::StreamDontHaveSuchGeneration(self.clone(), generation).into()); } self.0[generation].push(value); diff --git a/air/src/execution_step/errors.rs b/air/src/execution_step/errors/catchable_errors.rs similarity index 55% rename from air/src/execution_step/errors.rs rename to air/src/execution_step/errors/catchable_errors.rs index 4fe0e72e..1bcdf72f 100644 --- a/air/src/execution_step/errors.rs +++ b/air/src/execution_step/errors/catchable_errors.rs @@ -14,19 +14,13 @@ * limitations under the License. */ -mod catchable; -mod joinable; - -pub(crate) use catchable::Catchable; -pub(crate) use joinable::Joinable; +pub(crate) use super::Joinable; use super::Stream; use crate::execution_step::lambda_applier::LambdaError; use crate::JValue; use crate::ToErrorCode; -use air_trace_handler::MergerApResult; -use air_trace_handler::TraceHandlerError; use strum::IntoEnumIterator; use strum_macros::EnumDiscriminants; use strum_macros::EnumIter; @@ -34,26 +28,22 @@ use thiserror::Error as ThisError; use std::rc::Rc; -/// Errors arisen while executing AIR script. +/// Catchable errors arisen during AIR script execution. Catchable here means that these errors +/// could be handled by a xor instruction and their error_code could be used in a match +/// instruction. #[derive(ThisError, EnumDiscriminants, Debug)] #[strum_discriminants(derive(EnumIter))] -pub(crate) enum ExecutionError { +pub enum CatchableError { /// An error is occurred while calling local service via call_service. #[error("Local service error, ret_code is {0}, error message is '{1}'")] LocalServiceError(i32, Rc), /// Variable with such a name wasn't defined during AIR script execution. + /// This error type is used in order to support the join behaviour and + /// it's ok if some variable hasn't been defined yet, due to the par nature of AIR. #[error("variable with name '{0}' wasn't defined during script execution")] VariableNotFound(String), - /// Multiple values for such name found. - #[error("multiple variables found for name '{0}' in data")] - MultipleVariablesFound(String), - - /// An error occurred while trying to apply lambda to a value. - #[error(transparent)] - LambdaApplierError(#[from] LambdaError), - /// Provided JValue has incompatible type with a requested one. #[error( "expected JValue type '{expected_value_type}' for the variable `{variable_name}`, but got '{actual_value}'" @@ -64,22 +54,10 @@ pub(crate) enum ExecutionError { expected_value_type: &'static str, }, - /// Fold state wasn't found for such iterator name. - #[error("fold state not found for this iterable '{0}'")] - FoldStateNotFound(String), - - /// Multiple fold states found for such iterator name. - #[error("multiple iterable values found for iterable name '{0}'")] - MultipleIterableValues(String), - /// A fold instruction must iterate over array value. #[error("lambda '{1}' returned non-array value '{0}' for fold instruction")] FoldIteratesOverNonArray(JValue, String), - /// Errors encountered while shadowing non-scalar values. - #[error("variable with name '{0}' can't be shadowed, shadowing isn't supported for iterables")] - IterableShadowing(String), - /// This error type is produced by a match to notify xor that compared values aren't equal. #[error("match is used without corresponding xor")] MatchWithoutXorError, @@ -92,61 +70,31 @@ pub(crate) enum ExecutionError { #[error("fail with ret_code '{ret_code}' and error_message '{error_message}' is used without corresponding xor")] FailWithoutXorError { ret_code: i64, error_message: String }, - /// Errors bubbled from a trace handler. + /// An error occurred while trying to apply lambda to a value. #[error(transparent)] - TraceError(#[from] TraceHandlerError), + LambdaApplierError(#[from] LambdaError), /// Errors occurred while insertion of a value inside stream that doesn't have corresponding generation. #[error("stream {0:?} doesn't have generation with number {1}, probably a supplied to the interpreter data is corrupted")] StreamDontHaveSuchGeneration(Stream, usize), - - /// Errors occurred when result from data doesn't match to a instruction, f.e. an instruction - /// could be applied to a stream, but result doesn't contain generation in a source position. - #[error("ap result {0:?} doesn't match corresponding instruction")] - ApResultNotCorrespondToInstr(MergerApResult), } -impl From for Rc { +impl From for Rc { fn from(e: LambdaError) -> Self { - Rc::new(ExecutionError::LambdaApplierError(e)) + Rc::new(CatchableError::LambdaApplierError(e)) } } -/// This macro is needed because it's impossible to implement -/// From for Rc due to the orphan rule. -#[macro_export] -macro_rules! trace_to_exec_err { - ($trace_expr: expr) => { - $trace_expr.map_err(|e| std::rc::Rc::new(crate::execution_step::ExecutionError::TraceError(e))) - }; -} - -/* -impl ToErrorCode for ExecutionError { +impl ToErrorCode for Rc { fn to_error_code(&self) -> i64 { - const EXECUTION_ERRORS_START_ID: i64 = 1000; - - let mut errors = ExecutionErrorDiscriminants::iter(); - let actual_error_type = ExecutionErrorDiscriminants::from(self); - - // unwrap is safe here because errors are guaranteed to contain all errors variants - let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; - EXECUTION_ERRORS_START_ID + enum_variant_position + self.as_ref().to_error_code() } } - */ - -impl ToErrorCode for Rc { +impl ToErrorCode for CatchableError { fn to_error_code(&self) -> i64 { - const EXECUTION_ERRORS_START_ID: i64 = 1000; - - let mut errors = ExecutionErrorDiscriminants::iter(); - let actual_error_type = ExecutionErrorDiscriminants::from(self.as_ref()); - - // unwrap is safe here because errors are guaranteed to contain all errors variants - let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; - EXECUTION_ERRORS_START_ID + enum_variant_position + use crate::utils::CATCHABLE_ERRORS_START_ID; + crate::generate_to_error_code!(self, CatchableError, CATCHABLE_ERRORS_START_ID) } } @@ -157,11 +105,11 @@ macro_rules! log_join { } #[rustfmt::skip::macros(log_join)] -impl Joinable for ExecutionError { +impl Joinable for CatchableError { /// Returns true, if supplied error is related to variable not found errors type. /// Print log if this is joinable error type. fn is_joinable(&self) -> bool { - use ExecutionError::*; + use CatchableError::*; match self { VariableNotFound(var_name) => { @@ -181,16 +129,3 @@ impl Joinable for ExecutionError { } } } - -impl Catchable for ExecutionError { - fn is_catchable(&self) -> bool { - // this kind is related to an invalid data and should treat as a non-catchable error - !matches!(self, ExecutionError::TraceError(_)) - } -} - -impl From for ExecutionError { - fn from(_: std::convert::Infallible) -> Self { - unreachable!() - } -} diff --git a/air/src/execution_step/errors/execution_errors.rs b/air/src/execution_step/errors/execution_errors.rs new file mode 100644 index 00000000..1856da13 --- /dev/null +++ b/air/src/execution_step/errors/execution_errors.rs @@ -0,0 +1,87 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::CatchableError; +use super::Joinable; +use super::UncatchableError; +use crate::ToErrorCode; + +use strum_macros::EnumDiscriminants; +use strum_macros::EnumIter; +use thiserror::Error as ThisError; + +use std::rc::Rc; + +// TODO: add tests for all execution errors +/// Errors arisen while executing AIR script. +/// This enum is pub since it's used in tests. +#[derive(ThisError, EnumDiscriminants, Debug)] +#[strum_discriminants(derive(EnumIter))] +pub enum ExecutionError { + #[error(transparent)] + Catchable(#[from] Rc), + + #[error(transparent)] + Uncatchable(#[from] UncatchableError), +} + +impl ExecutionError { + pub fn is_catchable(&self) -> bool { + matches!(self, ExecutionError::Catchable(_)) + } + + pub fn is_match_or_mismatch(&self) -> bool { + match self { + ExecutionError::Catchable(catchable) => matches!( + catchable.as_ref(), + CatchableError::MatchWithoutXorError | CatchableError::MismatchWithoutXorError + ), + _ => false, + } + } +} + +impl From for ExecutionError { + fn from(catchable: CatchableError) -> Self { + Self::Catchable(std::rc::Rc::new(catchable)) + } +} + +#[macro_export] +macro_rules! trace_to_exec_err { + ($trace_expr: expr) => { + $trace_expr.map_err(|e| { + crate::execution_step::ExecutionError::Uncatchable(crate::execution_step::UncatchableError::TraceError(e)) + }) + }; +} +impl ToErrorCode for ExecutionError { + fn to_error_code(&self) -> i64 { + match self { + ExecutionError::Catchable(err) => err.to_error_code(), + ExecutionError::Uncatchable(err) => err.to_error_code(), + } + } +} + +impl Joinable for ExecutionError { + fn is_joinable(&self) -> bool { + match self { + ExecutionError::Catchable(err) => err.is_joinable(), + _ => false, + } + } +} diff --git a/air/src/execution_step/errors/catchable.rs b/air/src/execution_step/errors/mod.rs similarity index 59% rename from air/src/execution_step/errors/catchable.rs rename to air/src/execution_step/errors/mod.rs index 9d4e7b68..02259360 100644 --- a/air/src/execution_step/errors/catchable.rs +++ b/air/src/execution_step/errors/mod.rs @@ -14,11 +14,15 @@ * limitations under the License. */ -/// This trait is intended to differentiate between catchable and non-catchable error types. -/// Errors of the first type could be caught by xor, the second couldn't and should stop -/// AIR execution. This is needed to prevent some malicious data merging and manage -/// prev_data always in a valid state. -pub(crate) trait Catchable { - /// Return true, if error is catchable. - fn is_catchable(&self) -> bool; -} +mod catchable_errors; +mod execution_errors; +mod joinable; +mod uncatchable_errors; + +pub use catchable_errors::CatchableError; +pub use execution_errors::ExecutionError; +pub use uncatchable_errors::UncatchableError; + +pub(crate) use joinable::Joinable; + +use super::Stream; diff --git a/air/src/execution_step/errors/uncatchable_errors.rs b/air/src/execution_step/errors/uncatchable_errors.rs new file mode 100644 index 00000000..baff7457 --- /dev/null +++ b/air/src/execution_step/errors/uncatchable_errors.rs @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::ToErrorCode; + +use air_trace_handler::MergerApResult; +use air_trace_handler::TraceHandlerError; +use strum::IntoEnumIterator; +use strum_macros::EnumDiscriminants; +use strum_macros::EnumIter; +use thiserror::Error as ThisError; + +/// Uncatchable errors arisen during AIR script execution. Uncatchable here means that these errors +/// couldn't be handled by a xor instruction and their error_code couldn't be used in a match +/// instruction. They are similar to JVM runtime errors and some of them could be caught only +/// while execution of AIR script, others (FoldStateNotFound and MultipleVariablesFound) are +/// checked additionally on the validation step, and presence here for convenience. +#[derive(ThisError, EnumDiscriminants, Debug)] +#[strum_discriminants(derive(EnumIter))] +pub enum UncatchableError { + /// Errors bubbled from a trace handler. + #[error(transparent)] + TraceError(#[from] TraceHandlerError), + + /// Fold state wasn't found for such iterator name. + #[error("fold state not found for this iterable '{0}'")] + FoldStateNotFound(String), + + /// Errors encountered while shadowing non-scalar values. + #[error("variable with name '{0}' can't be shadowed, shadowing isn't supported for iterables")] + IterableShadowing(String), + + /// Multiple fold states found for such iterator name. + #[error("multiple iterable values found for iterable name '{0}'")] + MultipleIterableValues(String), + + /// Errors occurred when result from data doesn't match to a instruction, f.e. an instruction + /// could be applied to a stream, but result doesn't contain generation in a source position. + #[error("ap result {0:?} doesn't match corresponding instruction")] + ApResultNotCorrespondToInstr(MergerApResult), + + /// Multiple values for such name found. + #[error("multiple variables found for name '{0}' in data")] + MultipleVariablesFound(String), +} + +impl ToErrorCode for UncatchableError { + fn to_error_code(&self) -> i64 { + use crate::utils::UNCATCHABLE_ERRORS_START_ID; + crate::generate_to_error_code!(self, UncatchableError, UNCATCHABLE_ERRORS_START_ID) + } +} diff --git a/air/src/execution_step/execution_context/error_descriptor.rs b/air/src/execution_step/execution_context/error_descriptor.rs index a20f3485..aac67b99 100644 --- a/air/src/execution_step/execution_context/error_descriptor.rs +++ b/air/src/execution_step/execution_context/error_descriptor.rs @@ -15,7 +15,7 @@ */ use super::ExecutionCtx; -use crate::execution_step::ExecutionError; +use crate::execution_step::CatchableError; use crate::execution_step::RSecurityTetraplet; use crate::SecurityTetraplet; @@ -28,7 +28,7 @@ use std::rc::Rc; /// This struct is intended to track the last arisen error. #[derive(Debug)] pub(crate) struct LastErrorDescriptor { - pub(crate) error: Rc, + pub(crate) error: Rc, pub(crate) instruction: String, pub(crate) peer_id: String, pub(crate) tetraplet: Option, @@ -70,7 +70,7 @@ impl<'s> LastErrorWithTetraplet { impl LastErrorDescriptor { pub(crate) fn new( - error: Rc, + error: Rc, instruction: String, peer_id: String, tetraplet: Option, diff --git a/air/src/execution_step/execution_context/scalar_variables.rs b/air/src/execution_step/execution_context/scalar_variables.rs index cbb846a6..43412115 100644 --- a/air/src/execution_step/execution_context/scalar_variables.rs +++ b/air/src/execution_step/execution_context/scalar_variables.rs @@ -14,9 +14,8 @@ * limitations under the License. */ -use crate::exec_err; use crate::execution_step::boxed_value::ScalarRef; -use crate::execution_step::ExecutionError; +use crate::execution_step::errors_prelude::*; use crate::execution_step::ExecutionResult; use crate::execution_step::FoldState; use crate::execution_step::ValueAggregate; @@ -87,7 +86,7 @@ impl<'i> Scalars<'i> { } Occupied(entry) => { if !shadowing_allowed { - return exec_err!(ExecutionError::MultipleVariablesFound(entry.key().clone())); + return Err(UncatchableError::MultipleVariablesFound(entry.key().clone()).into()); } let values = entry.into_mut(); @@ -115,9 +114,7 @@ impl<'i> Scalars<'i> { entry.insert(fold_state); Ok(()) } - Occupied(entry) => { - exec_err!(ExecutionError::MultipleIterableValues(entry.key().clone())) - } + Occupied(entry) => Err(UncatchableError::MultipleIterableValues(entry.key().clone()).into()), } } @@ -135,13 +132,13 @@ impl<'i> Scalars<'i> { .rev() .find_map(|scalar| scalar.as_ref()) }) - .ok_or_else(|| Rc::new(ExecutionError::VariableNotFound(name.to_string()))) + .ok_or_else(|| Rc::new(CatchableError::VariableNotFound(name.to_string())).into()) } pub(crate) fn get_iterable_mut(&mut self, name: &str) -> ExecutionResult<&mut FoldState<'i>> { self.iterable_values .get_mut(name) - .ok_or_else(|| Rc::new(ExecutionError::FoldStateNotFound(name.to_string()))) + .ok_or_else(|| UncatchableError::FoldStateNotFound(name.to_string()).into()) } pub(crate) fn get(&'i self, name: &str) -> ExecutionResult> { @@ -149,7 +146,7 @@ impl<'i> Scalars<'i> { let iterable_value = self.iterable_values.get(name); match (value, iterable_value) { - (Err(_), None) => exec_err!(ExecutionError::VariableNotFound(name.to_string())), + (Err(_), None) => Err(CatchableError::VariableNotFound(name.to_string()).into()), (Ok(value), None) => Ok(ScalarRef::Value(value)), (Err(_), Some(iterable_value)) => Ok(ScalarRef::IterableValue(iterable_value)), (Ok(_), Some(_)) => unreachable!("this is checked on the parsing stage"), diff --git a/air/src/execution_step/execution_context/streams_variables.rs b/air/src/execution_step/execution_context/streams_variables.rs index b130e815..ff33e59d 100644 --- a/air/src/execution_step/execution_context/streams_variables.rs +++ b/air/src/execution_step/execution_context/streams_variables.rs @@ -189,7 +189,7 @@ fn find_closest<'d>( ) -> Option<&'d RefCell> { // descriptors are placed in a order of decreasing scopes, so it's enough to get the latest suitable for descriptor in descriptors.rev() { - if descriptor.span.contains(position) { + if descriptor.span.contains_position(position) { return Some(&descriptor.stream); } } diff --git a/air/src/execution_step/lambda_applier/applier.rs b/air/src/execution_step/lambda_applier/applier.rs index 4adb7c0a..dd07ab17 100644 --- a/air/src/execution_step/lambda_applier/applier.rs +++ b/air/src/execution_step/lambda_applier/applier.rs @@ -18,6 +18,7 @@ use super::utils::*; use super::LambdaError; use crate::execution_step::ExecutionCtx; use crate::execution_step::ExecutionResult; +use crate::lambda_to_execution_error; use crate::JValue; use crate::LambdaAST; @@ -39,19 +40,18 @@ pub(crate) fn select_from_stream<'value, 'i>( let idx = match prefix { ArrayAccess { idx } => *idx, FieldAccessByName { field_name } => { - return Err(LambdaError::FieldAccessorAppliedToStream { + return lambda_to_execution_error!(Err(LambdaError::FieldAccessorAppliedToStream { field_name: field_name.to_string(), - } - .into()); + })); } _ => unreachable!("should not execute if parsing succeeded. QED."), }; let stream_size = stream.len(); - let value = stream + let value = lambda_to_execution_error!(stream .peekable() .nth(idx as usize) - .ok_or(LambdaError::StreamNotHaveEnoughValues { stream_size, idx })?; + .ok_or(LambdaError::StreamNotHaveEnoughValues { stream_size, idx }))?; let result = select(value, body.iter(), exec_ctx)?; let select_result = StreamSelectResult::new(result, idx); @@ -66,14 +66,14 @@ pub(crate) fn select<'value, 'accessor, 'i>( for accessor in lambda { match accessor { ValueAccessor::ArrayAccess { idx } => { - value = try_jvalue_with_idx(value, *idx)?; + value = lambda_to_execution_error!(try_jvalue_with_idx(value, *idx))?; } ValueAccessor::FieldAccessByName { field_name } => { - value = try_jvalue_with_field_name(value, *field_name)?; + value = lambda_to_execution_error!(try_jvalue_with_field_name(value, *field_name))?; } ValueAccessor::FieldAccessByScalar { scalar_name } => { let scalar = exec_ctx.scalars.get(scalar_name)?; - value = select_by_scalar(value, scalar)?; + value = lambda_to_execution_error!(select_by_scalar(value, scalar))?; } ValueAccessor::Error => unreachable!("should not execute if parsing succeeded. QED."), } diff --git a/air/src/execution_step/lambda_applier/errors.rs b/air/src/execution_step/lambda_applier/errors.rs index 62842494..6c74f314 100644 --- a/air/src/execution_step/lambda_applier/errors.rs +++ b/air/src/execution_step/lambda_applier/errors.rs @@ -18,8 +18,9 @@ use crate::JValue; use thiserror::Error as ThisError; +/// Describes errors related to applying lambdas to values. #[derive(Debug, Clone, ThisError)] -pub(crate) enum LambdaError { +pub enum LambdaError { #[error("lambda is applied to a stream that have only '{stream_size}' elements, but '{idx}' requested")] StreamNotHaveEnoughValues { stream_size: usize, idx: u32 }, @@ -36,12 +37,12 @@ pub(crate) enum LambdaError { #[error("value '{value}' does not contain element for idx = '{idx}'")] ValueNotContainSuchArrayIdx { value: JValue, idx: u32 }, + #[error("value '{value}' does not contain element with field name = '{field_name}'")] + ValueNotContainSuchField { value: JValue, field_name: String }, + #[error("value '{value}' is not an map-type to match field accessor with field_name = '{field_name}'")] FieldAccessorNotMatchValue { value: JValue, field_name: String }, - #[error("value '{value}' does not contain element with field name = '{field_name}'")] - JValueNotContainSuchField { value: JValue, field_name: String }, - #[error("index accessor `{accessor} can't be converted to u32`")] IndexAccessNotU32 { accessor: serde_json::Number }, diff --git a/air/src/execution_step/lambda_applier/mod.rs b/air/src/execution_step/lambda_applier/mod.rs index 3315803c..3b5281db 100644 --- a/air/src/execution_step/lambda_applier/mod.rs +++ b/air/src/execution_step/lambda_applier/mod.rs @@ -18,8 +18,20 @@ mod applier; mod errors; mod utils; +pub use errors::LambdaError; + pub(crate) type LambdaResult = std::result::Result; pub(crate) use applier::select; pub(crate) use applier::select_from_stream; -pub(crate) use errors::LambdaError; + +#[macro_export] +macro_rules! lambda_to_execution_error { + ($lambda_expr: expr) => { + $lambda_expr.map_err(|lambda_error| { + crate::execution_step::ExecutionError::Catchable(std::rc::Rc::new( + crate::execution_step::CatchableError::LambdaApplierError(lambda_error), + )) + }) + }; +} diff --git a/air/src/execution_step/lambda_applier/utils.rs b/air/src/execution_step/lambda_applier/utils.rs index 8a3d6333..b20a8e89 100644 --- a/air/src/execution_step/lambda_applier/utils.rs +++ b/air/src/execution_step/lambda_applier/utils.rs @@ -39,14 +39,12 @@ pub(super) fn try_jvalue_with_field_name<'value>( field_name: &str, ) -> LambdaResult<&'value JValue> { match jvalue { - JValue::Object(values_map) => { - values_map - .get(field_name) - .ok_or_else(|| LambdaError::JValueNotContainSuchField { - value: jvalue.clone(), - field_name: field_name.to_string(), - }) - } + JValue::Object(values_map) => values_map + .get(field_name) + .ok_or_else(|| LambdaError::ValueNotContainSuchField { + value: jvalue.clone(), + field_name: field_name.to_string(), + }), _ => Err(LambdaError::FieldAccessorNotMatchValue { value: jvalue.clone(), field_name: field_name.to_string(), diff --git a/air/src/execution_step/mod.rs b/air/src/execution_step/mod.rs index 21df2f83..d7b9aa17 100644 --- a/air/src/execution_step/mod.rs +++ b/air/src/execution_step/mod.rs @@ -21,14 +21,23 @@ pub(crate) mod execution_context; mod lambda_applier; mod resolver; +pub use errors::CatchableError; +pub use errors::ExecutionError; +pub use errors::UncatchableError; +pub use lambda_applier::LambdaError; + +pub mod errors_prelude { + pub use super::CatchableError; + pub use super::ExecutionError; + pub use super::UncatchableError; +} + pub(super) use self::air::ExecutableInstruction; pub(super) use self::air::FoldState; pub(super) use boxed_value::Generation; pub(super) use boxed_value::ScalarRef; pub(super) use boxed_value::Stream; pub(super) use boxed_value::ValueAggregate; -pub(crate) use errors::Catchable; -pub(super) use errors::ExecutionError; pub(crate) use errors::Joinable; pub(crate) use execution_context::ExecutionCtx; @@ -37,13 +46,6 @@ pub(crate) use air_trace_handler::TraceHandler; use std::cell::RefCell; use std::rc::Rc; -type ExecutionResult = std::result::Result>; +type ExecutionResult = std::result::Result; type RSecurityTetraplet = Rc>; type SecurityTetraplets = Vec; - -#[macro_export] -macro_rules! exec_err { - ($err:expr) => { - Err(std::rc::Rc::new($err)) - }; -} diff --git a/air/src/farewell_step/errors.rs b/air/src/farewell_step/errors.rs index cc034d75..83736f7c 100644 --- a/air/src/farewell_step/errors.rs +++ b/air/src/farewell_step/errors.rs @@ -35,13 +35,7 @@ pub enum FarewellError { impl ToErrorCode for FarewellError { fn to_error_code(&self) -> i64 { - const FAREWELL_ERRORS_START_ID: i64 = 20000; - - let mut errors = FarewellErrorDiscriminants::iter(); - let actual_error_type = FarewellErrorDiscriminants::from(self); - - // unwrap is safe here because errors are guaranteed to contain all errors variants - let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; - FAREWELL_ERRORS_START_ID + enum_variant_position + use crate::utils::FAREWELL_ERRORS_START_ID; + crate::generate_to_error_code!(self, FarewellError, FAREWELL_ERRORS_START_ID) } } diff --git a/air/src/farewell_step/mod.rs b/air/src/farewell_step/mod.rs index 10fef167..c4311803 100644 --- a/air/src/farewell_step/mod.rs +++ b/air/src/farewell_step/mod.rs @@ -17,7 +17,8 @@ mod errors; mod outcome; -pub(crate) use errors::FarewellError; +pub use errors::FarewellError; + pub(crate) use outcome::from_execution_error; pub(crate) use outcome::from_success_result; pub(crate) use outcome::from_uncatchable_error; diff --git a/air/src/lib.rs b/air/src/lib.rs index 5a56499d..7167b5e7 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -14,7 +14,6 @@ * limitations under the License. */ -#![allow(improper_ctypes)] #![warn(rust_2018_idioms)] #![deny( dead_code, @@ -36,8 +35,14 @@ pub use air_interpreter_interface::InterpreterOutcome; pub use air_interpreter_interface::RunParameters; pub use air_interpreter_interface::INTERPRETER_SUCCESS; pub use execution_step::execution_context::LastError; +pub use execution_step::CatchableError; +pub use execution_step::ExecutionError; +pub use execution_step::LambdaError; +pub use execution_step::UncatchableError; pub use polyplets::ResolvedTriplet; pub use polyplets::SecurityTetraplet; +pub use preparation_step::PreparationError; +pub use utils::ToErrorCode; pub use crate::runner::execute_air; @@ -55,6 +60,5 @@ pub mod parser { } pub(crate) type JValue = serde_json::Value; -pub(crate) use utils::ToErrorCode; use air_lambda_parser::LambdaAST; diff --git a/air/src/preparation_step/errors.rs b/air/src/preparation_step/errors.rs index 36e13ccd..52231ac1 100644 --- a/air/src/preparation_step/errors.rs +++ b/air/src/preparation_step/errors.rs @@ -43,13 +43,7 @@ pub enum PreparationError { impl ToErrorCode for PreparationError { fn to_error_code(&self) -> i64 { - const PREPARATION_ERRORS_START_ID: i64 = 1; - - let mut errors = PreparationErrorDiscriminants::iter(); - let actual_error_type = PreparationErrorDiscriminants::from(self); - - // unwrap is safe here because errors are guaranteed to contain all errors variants - let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; - PREPARATION_ERRORS_START_ID + enum_variant_position + use crate::utils::PREPARATION_ERROR_START_ID; + crate::generate_to_error_code!(self, PreparationError, PREPARATION_ERROR_START_ID) } } diff --git a/air/src/preparation_step/mod.rs b/air/src/preparation_step/mod.rs index c7dd5a85..60430927 100644 --- a/air/src/preparation_step/mod.rs +++ b/air/src/preparation_step/mod.rs @@ -17,6 +17,7 @@ mod errors; mod preparation; -pub(crate) use errors::PreparationError; +pub use errors::PreparationError; + pub(crate) use preparation::prepare; pub(crate) use preparation::PreparationDescriptor; diff --git a/air/src/runner.rs b/air/src/runner.rs index b97749e3..260717b7 100644 --- a/air/src/runner.rs +++ b/air/src/runner.rs @@ -14,7 +14,6 @@ * limitations under the License. */ -use crate::execution_step::Catchable; use crate::execution_step::ExecutableInstruction; use crate::farewell_step as farewell; use crate::preparation_step::prepare; @@ -58,8 +57,8 @@ fn execute_air_impl( mut trace_handler, air, } = match prepare(&prev_data, &data, air.as_str(), &call_results, params) { - Ok(desc) => desc, - // return the initial data in case of errors + Ok(descriptor) => descriptor, + // return the prev data in case of errors Err(error) => return Err(farewell::from_uncatchable_error(prev_data, error)), }; @@ -69,7 +68,7 @@ fn execute_air_impl( Ok(_) => farewell::from_success_result(exec_ctx, trace_handler), // return new collected trace in case of errors Err(error) if error.is_catchable() => Err(farewell::from_execution_error(exec_ctx, trace_handler, error)), - // return the old data in case of any trace errors + // return the prev data in case of any trace errors Err(error) => Err(farewell::from_uncatchable_error(prev_data, error)), } } diff --git a/air/src/utils/error_codes.rs b/air/src/utils/error_codes.rs new file mode 100644 index 00000000..0bf70bd6 --- /dev/null +++ b/air/src/utils/error_codes.rs @@ -0,0 +1,21 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// This consts are used as start ids of corresponding errors. +pub(crate) const PREPARATION_ERROR_START_ID: i64 = 1; +pub(crate) const CATCHABLE_ERRORS_START_ID: i64 = 10000; +pub(crate) const UNCATCHABLE_ERRORS_START_ID: i64 = 20000; +pub(crate) const FAREWELL_ERRORS_START_ID: i64 = 30000; diff --git a/air/src/utils/mod.rs b/air/src/utils/mod.rs index af80c745..273b7600 100644 --- a/air/src/utils/mod.rs +++ b/air/src/utils/mod.rs @@ -14,6 +14,8 @@ * limitations under the License. */ +mod error_codes; mod to_error_code; -pub(crate) use to_error_code::ToErrorCode; +pub(crate) use error_codes::*; +pub use to_error_code::ToErrorCode; diff --git a/air/src/utils/to_error_code.rs b/air/src/utils/to_error_code.rs index 3f02c146..0faf49ee 100644 --- a/air/src/utils/to_error_code.rs +++ b/air/src/utils/to_error_code.rs @@ -14,24 +14,26 @@ * limitations under the License. */ -pub(crate) trait ToErrorCode { +pub trait ToErrorCode { fn to_error_code(&self) -> i64; } -/* -use concat_idents::concat_idents; - #[macro_export] macro_rules! generate_to_error_code { - ($error_type:ident, $start_id: ident) => { - const PREPARATION_ERRORS_START_ID: u32 = $start_id; + ($self: expr, $error_type:ident, $start_id: expr) => { + concat_idents::concat_idents!(error_start_id = $error_type, _, START_ID { + concat_idents::concat_idents!(error_discriminant = $error_type, Discriminants { { + #[allow(non_upper_case_globals)] + const error_start_id: i64 = $start_id; - let mut errors = PreparationErrorDiscriminants::iter(); - let actual_error_type = PreparationErrorDiscriminants::from(self); + let mut errors = error_discriminant::iter(); + let actual_error_type = error_discriminant::from($self); - // unwrap is safe here because errors are guaranteed to contain all errors variants - let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; - PREPARATION_ERRORS_START_ID + enum_variant_position + // unwrap is safe here because errors are guaranteed to contain all errors variants + let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64; + error_start_id + enum_variant_position + } + }) + }) } } - */ diff --git a/air/tests/test_module/instructions/call.rs b/air/tests/test_module/instructions/call.rs index 2e8ff818..e3360a74 100644 --- a/air/tests/test_module/instructions/call.rs +++ b/air/tests/test_module/instructions/call.rs @@ -14,8 +14,12 @@ * limitations under the License. */ +use air::UncatchableError; use air_test_utils::prelude::*; +use fstrings::f; +use fstrings::format_args_f; + // Check that %init_peer_id% alias works correctly (by comparing result with it and explicit peer id). // Additionally, check that empty string for data does the same as empty call path. #[test] @@ -98,18 +102,21 @@ fn variables() { // Check that duplicate variables are impossible. #[test] fn duplicate_variables() { - let mut vm = create_avm(unit_call_service(), "some_peer_id"); + let peer_id = "peer_id"; + let mut vm = create_avm(unit_call_service(), peer_id); - let script = r#" + let variable_name = "modules"; + let script = f!(r#" (seq - (call "some_peer_id" ("some_service_id" "local_fn_name") [] modules) - (call "some_peer_id" ("some_service_id" "local_fn_name") [] modules) + (call "{peer_id}" ("some_service_id" "local_fn_name") [] {variable_name}) + (call "{peer_id}" ("some_service_id" "local_fn_name") [] {variable_name}) ) - "#; + "#); let result = call_vm!(vm, "asd", script, "", ""); - assert_eq!(result.ret_code, 1002); + let expected_error = UncatchableError::MultipleVariablesFound(variable_name.to_string()); + assert!(check_error(&result, expected_error)); assert!(result.next_peer_pks.is_empty()); } diff --git a/air/tests/test_module/instructions/fail.rs b/air/tests/test_module/instructions/fail.rs index 7c36301b..2ea16849 100644 --- a/air/tests/test_module/instructions/fail.rs +++ b/air/tests/test_module/instructions/fail.rs @@ -14,11 +14,14 @@ * limitations under the License. */ +use air::CatchableError; use air_test_utils::prelude::*; use fstrings::f; use fstrings::format_args_f; +use std::rc::Rc; + #[test] fn fail_with_last_error() { let local_peer_id = "local_peer_id"; @@ -32,11 +35,10 @@ fn fail_with_last_error() { )"#); let result = call_vm!(vm, "", script, "", ""); - assert_eq!(result.ret_code, 1000); - assert_eq!( - result.error_message, - r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"# - ); + + let expected_error = + CatchableError::LocalServiceError(1, Rc::new(r#""failed result from fallible_call_service""#.to_string())); + assert!(check_error(&result, expected_error)); } #[test] @@ -53,11 +55,12 @@ fn fail_with_literals() { ); let result = call_vm!(vm, "", script, "", ""); - assert_eq!(result.ret_code, 1012); - assert_eq!( - result.error_message, - "fail with ret_code '1337' and error_message 'error message' is used without corresponding xor" - ); + + let expected_error = CatchableError::FailWithoutXorError { + ret_code: 1337, + error_message: "error message".to_string(), + }; + assert!(check_error(&result, expected_error)); } #[test] @@ -78,7 +81,7 @@ fn fail_with_last_error_tetraplets() { ) "#); - let result = checked_call_vm!(vm, local_peer_id, script, "", ""); + let _ = checked_call_vm!(vm, local_peer_id, script, "", ""); assert_eq!( tetraplet_anchor.borrow()[0][0], SecurityTetraplet::new(local_peer_id, fallible_service_id, local_fn_name, "") diff --git a/air/tests/test_module/instructions/fold.rs b/air/tests/test_module/instructions/fold.rs index 2212da20..d7203c32 100644 --- a/air/tests/test_module/instructions/fold.rs +++ b/air/tests/test_module/instructions/fold.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use air::{PreparationError, ToErrorCode}; use air_test_utils::prelude::*; #[test] @@ -157,7 +158,8 @@ fn inner_fold_with_same_iterator() { let result = call_vm!(vm, "", script, "", ""); - assert_eq!(result.ret_code, 1007); + let expected_error = PreparationError::AIRParseError("".to_string()); + assert_eq!(result.ret_code, expected_error.to_error_code()); } #[test] diff --git a/air/tests/test_module/instructions/match_.rs b/air/tests/test_module/instructions/match_.rs index f3077d6e..1e79e366 100644 --- a/air/tests/test_module/instructions/match_.rs +++ b/air/tests/test_module/instructions/match_.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use air::CatchableError; use air_test_utils::prelude::*; #[test] @@ -163,8 +164,9 @@ fn match_with_equal_numbers() { (null) )"; - let result = checked_call_vm!(vm, "asd", script, "", ""); - assert_eq!(result.ret_code, 0); + let result = call_vm!(vm, "asd", script, "", ""); + + assert!(is_interpreter_succeded(&result)); } #[test] @@ -192,11 +194,13 @@ fn match_without_xor() { let result = call_vm!(set_variable_vm, "", &script, "", ""); let result = call_vm!(vm, "", &script, "", result.data); - assert_eq!(result.ret_code, 1010); + let expected_error = CatchableError::MatchWithoutXorError; + assert!(check_error(&result, expected_error)); let result = call_vm!(vm, "", script, "", result.data); - assert_eq!(result.ret_code, 1010); + let expected_error = CatchableError::MatchWithoutXorError; + assert!(check_error(&result, expected_error)); } #[test] diff --git a/air/tests/test_module/instructions/mismatch.rs b/air/tests/test_module/instructions/mismatch.rs index a42564ee..50cf6a20 100644 --- a/air/tests/test_module/instructions/mismatch.rs +++ b/air/tests/test_module/instructions/mismatch.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use air::CatchableError; use air_test_utils::prelude::*; #[test] @@ -143,11 +144,13 @@ fn mismatch_without_xor() { let result = call_vm!(set_variable_vm, "asd", &script, "", ""); let result = call_vm!(vm, "asd", &script, "", result.data); - assert_eq!(result.ret_code, 1011); + let expected_error = CatchableError::MismatchWithoutXorError; + assert!(check_error(&result, expected_error)); let result = call_vm!(vm, "asd", script, "", result.data); - assert_eq!(result.ret_code, 1011); + let expected_error = CatchableError::MismatchWithoutXorError; + assert!(check_error(&result, expected_error)); } #[test] @@ -183,6 +186,5 @@ fn mismatch_with_two_xors() { let mut actual_trace = trace_from_result(&result); let expected_executed_call_result = executed_state::request_sent_by(local_peer_id); - assert_eq!(result.ret_code, 0); assert_eq!(actual_trace.pop().unwrap(), expected_executed_call_result); } diff --git a/air/tests/test_module/instructions/xor.rs b/air/tests/test_module/instructions/xor.rs index 72ffff16..f5bc01ed 100644 --- a/air/tests/test_module/instructions/xor.rs +++ b/air/tests/test_module/instructions/xor.rs @@ -14,8 +14,12 @@ * limitations under the License. */ +use air::UncatchableError; use air_test_utils::prelude::*; +use fstrings::f; +use fstrings::format_args_f; + #[test] fn xor() { let local_peer_id = "local_peer_id"; @@ -89,32 +93,22 @@ fn xor_multiple_variables_found() { let mut set_variables_vm = create_avm(echo_call_service(), set_variables_peer_id); let local_peer_id = "local_peer_id"; - let mut vm = create_avm(echo_call_service(), local_peer_id); - - let some_string = String::from("some_string"); - let expected_string = String::from("expected_string"); - let script = format!( - r#" + let some_string = "some_string"; + let expected_string = "expected_string"; + let variable_name = "result_1"; + let script = f!(r#" (seq - (call "{0}" ("service_id_1" "local_fn_name") ["{2}"] result_1) + (call "{set_variables_peer_id}" ("service_id_1" "local_fn_name") ["{some_string}"] {variable_name}) (xor - (call "{1}" ("service_id_1" "local_fn_name") [""] result_1) - (call "{1}" ("service_id_2" "local_fn_name") ["{3}"] result_2) + (call "{local_peer_id}" ("service_id_1" "local_fn_name") [""] {variable_name}) + (call "{local_peer_id}" ("service_id_2" "local_fn_name") ["{expected_string}"] result_2) ) - )"#, - set_variables_peer_id, local_peer_id, some_string, expected_string - ); + )"#); - let result = checked_call_vm!(set_variables_vm, "asd", &script, "", ""); - let result = checked_call_vm!(vm, "asd", script, "", result.data); + let result = call_vm!(set_variables_vm, "asd", &script, "", ""); - let actual_trace = trace_from_result(&result); - let expected_trace = vec![ - executed_state::scalar_string(some_string), - executed_state::scalar_string(expected_string), - ]; - - assert_eq!(actual_trace, expected_trace); + let expected_error = UncatchableError::MultipleVariablesFound(variable_name.to_string()); + assert!(check_error(&result, expected_error)); } #[test] diff --git a/air/tests/test_module/integration/air_basic.rs b/air/tests/test_module/integration/air_basic.rs index a071170b..609ac68d 100644 --- a/air/tests/test_module/integration/air_basic.rs +++ b/air/tests/test_module/integration/air_basic.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use air::PreparationError; use air_test_utils::prelude::*; #[test] @@ -160,5 +161,8 @@ fn invalid_air() { let script = r#"(seq )"#; let result = call_vm!(vm, "", script, "", ""); - assert_eq!(result.ret_code, 1); + + let error_message = air_parser::parse(script).expect_err("air parser should fail on this script"); + let expected_error = PreparationError::AIRParseError(error_message); + assert!(check_error(&result, expected_error)); } diff --git a/air/tests/test_module/integration/flattening.rs b/air/tests/test_module/integration/flattening.rs index 54d8479a..2f67ca46 100644 --- a/air/tests/test_module/integration/flattening.rs +++ b/air/tests/test_module/integration/flattening.rs @@ -74,9 +74,9 @@ fn flattening_scalar_arrays() { ); let result = checked_call_vm!(set_variable_vm, "asd", script.clone(), "", ""); - let result = checked_call_vm!(local_vm, "asd", script.clone(), "", result.data); + let result = call_vm!(local_vm, "asd", script.clone(), "", result.data); - assert_eq!(result.ret_code, 0); + assert!(is_interpreter_succeded(&result)); assert_eq!( closure_call_args.service_id_var, Rc::new(RefCell::new("local_service_id".to_string())) @@ -124,9 +124,9 @@ fn flattening_streams() { ); let result = checked_call_vm!(set_variable_vm, "asd", script.clone(), "", ""); - let result = checked_call_vm!(local_vm, "asd", script.clone(), "", result.data); + let result = call_vm!(local_vm, "asd", script.clone(), "", result.data); - assert_eq!(result.ret_code, 0); + assert!(is_interpreter_succeded(&result)); assert_eq!( closure_call_args.service_id_var, Rc::new(RefCell::new("local_service_id".to_string())) @@ -164,7 +164,7 @@ fn flattening_empty_values() { let result = checked_call_vm!(set_variable_vm, "asd", script.clone(), "", ""); let result = checked_call_vm!(local_vm, "asd", script.clone(), "", result.data); - assert_eq!(result.ret_code, 0); + assert!(is_interpreter_succeded(&result)); assert_eq!(closure_call_args.args_var, Rc::new(RefCell::new(vec![]))); } diff --git a/air/tests/test_module/integration/join_behaviour.rs b/air/tests/test_module/integration/join_behaviour.rs index d12555e4..518abcca 100644 --- a/air/tests/test_module/integration/join_behaviour.rs +++ b/air/tests/test_module/integration/join_behaviour.rs @@ -14,6 +14,8 @@ * limitations under the License. */ +use air::CatchableError; +use air::LambdaError; use air_test_utils::prelude::*; use fstrings::f; @@ -85,7 +87,6 @@ fn wait_on_stream_json_path_by_id() { let result = checked_call_vm!(local_vm, "", non_join_stream_script, "", ""); let actual_trace = trace_from_result(&result); - assert_eq!(result.ret_code, 0); assert_eq!(actual_trace.len(), 3); let join_stream_script = format!( @@ -100,7 +101,6 @@ fn wait_on_stream_json_path_by_id() { let result = checked_call_vm!(local_vm, "", join_stream_script, "", ""); let actual_trace = trace_from_result(&result); - assert_eq!(result.ret_code, 0); assert_eq!(actual_trace.len(), 2); // par and the first call emit traces, second call doesn't } @@ -135,17 +135,17 @@ fn wait_on_empty_stream_json_path() { #[test] fn dont_wait_on_json_path_on_scalars() { - let array = json!([1, 2, 3, 4, 5]); + let array = json!([1u32, 2u32, 3u32, 4u32, 5u32]); let object = json!({ "err_msg": "", - "is_authenticated": 1, - "ret_code": 0, + "is_authenticated": 1i32, + "ret_code": 0i32, }); let variables = maplit::hashmap!( - "array".to_string() => array, - "object".to_string() => object, + "array".to_string() => array.clone(), + "object".to_string() => object.clone(), ); let set_variables_call_service = set_variables_call_service(variables, VariableOptionSource::Argument(0)); @@ -172,11 +172,10 @@ fn dont_wait_on_json_path_on_scalars() { let init_peer_id = "asd"; let result = call_vm!(set_variable_vm, init_peer_id, &script, "", ""); let array_result = call_vm!(array_consumer, init_peer_id, &script, "", result.data.clone()); - assert_eq!(array_result.ret_code, 1003); - assert_eq!( - array_result.error_message, - r#"value '[1,2,3,4,5]' does not contain element for idx = '5'"# - ); + + let expected_error = + CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchArrayIdx { value: array, idx: 5 }); + assert!(check_error(&array_result, expected_error)); let script = format!( r#" @@ -191,11 +190,13 @@ fn dont_wait_on_json_path_on_scalars() { let init_peer_id = "asd"; let result = call_vm!(set_variable_vm, init_peer_id, &script, "", ""); let object_result = call_vm!(object_consumer, init_peer_id, script, "", result.data); - assert_eq!(object_result.ret_code, 1003); - assert_eq!( - object_result.error_message, - r#"value '{"err_msg":"","is_authenticated":1,"ret_code":0}' does not contain element with field name = 'non_exist_path'"# - ); + + let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField { + value: object, + field_name: "non_exist_path".to_string(), + }); + + assert!(check_error(&object_result, expected_error)); } #[test] diff --git a/air/tests/test_module/integration/lambda.rs b/air/tests/test_module/integration/lambda.rs index 824dc7ea..97bb23f1 100644 --- a/air/tests/test_module/integration/lambda.rs +++ b/air/tests/test_module/integration/lambda.rs @@ -14,6 +14,8 @@ * limitations under the License. */ +use air::CatchableError; +use air::LambdaError; use air_test_utils::prelude::*; use fstrings::f; @@ -27,20 +29,22 @@ fn lambda_not_allowed_for_non_objects_and_arrays() { let local_peer_id = "local_peer_id"; let mut local_vm = create_avm(echo_call_service(), local_peer_id); - let script = format!( - r#" + let some_string = "some_string"; + let script = f!(r#" (seq - (call "{0}" ("" "") ["some_string"] string_variable) - (call "{1}" ("" "") [string_variable.$.some_lambda]) + (call "{set_variable_peer_id}" ("" "") ["{some_string}"] string_variable) + (call "{local_peer_id}" ("" "") [string_variable.$.some_lambda]) ) - "#, - set_variable_peer_id, local_peer_id - ); + "#); let result = checked_call_vm!(set_variable_vm, "asd", &script, "", ""); let result = call_vm!(local_vm, "asd", script, "", result.data); - assert_eq!(result.ret_code, 1003); + let expected_error = CatchableError::LambdaApplierError(LambdaError::FieldAccessorNotMatchValue { + value: json!(some_string), + field_name: "some_lambda".to_string(), + }); + assert!(check_error(&result, expected_error)); } #[test] diff --git a/air/tests/test_module/integration/streams_early_exit.rs b/air/tests/test_module/integration/streams_early_exit.rs index 2d5b516d..1b095525 100644 --- a/air/tests/test_module/integration/streams_early_exit.rs +++ b/air/tests/test_module/integration/streams_early_exit.rs @@ -14,8 +14,10 @@ * limitations under the License. */ +use air::UncatchableError; use air_test_utils::prelude::*; -use pretty_assertions::assert_eq; +use air_trace_handler::TraceHandlerError; +use air_trace_handler::{CallResultError, MergeError}; #[test] fn par_early_exit() { @@ -141,7 +143,20 @@ fn par_early_exit() { ]; let setter_3_malicious_data = raw_data_from_trace(setter_3_malicious_trace); let init_result_3 = call_vm!(init, "", &script, init_result_2.data.clone(), setter_3_malicious_data); - assert_eq!(init_result_3.ret_code, 1013); + + let expected_error = UncatchableError::TraceError(TraceHandlerError::MergeError(MergeError::IncorrectCallResult( + CallResultError::ValuesNotEqual { + prev_value: Value::Stream { + value: rc!(json!("1")), + generation: 0, + }, + current_value: Value::Stream { + value: rc!(json!("non_exist_value")), + generation: 0, + }, + }, + ))); + assert!(check_error(&init_result_3, expected_error)); let actual_trace = trace_from_result(&init_result_3); let expected_trace = trace_from_result(&init_result_2); diff --git a/air/tests/test_module/issues/issue_137.rs b/air/tests/test_module/issues/issue_137.rs index e173f22d..036f5b8c 100644 --- a/air/tests/test_module/issues/issue_137.rs +++ b/air/tests/test_module/issues/issue_137.rs @@ -62,5 +62,6 @@ fn issue_137() { let node_2_result = checked_call_vm!(node_2, "", &script, "", initiator_result.data); let node_4_result_1 = checked_call_vm!(node_4, "", &script, "", node_1_result.data); let result = call_vm!(node_4, "", script, node_4_result_1.data, node_2_result.data); - assert_eq!(result.ret_code, 0); + + assert!(is_interpreter_succeded(&result)); } diff --git a/crates/air-lib/air-parser/src/ast/values/impls.rs b/crates/air-lib/air-parser/src/ast/values/impls.rs index 31bedc48..91452b33 100644 --- a/crates/air-lib/air-parser/src/ast/values/impls.rs +++ b/crates/air-lib/air-parser/src/ast/values/impls.rs @@ -88,7 +88,7 @@ impl<'i> Variable<'i> { Self::Stream(Stream::new(name, position)) } - pub fn name(&self) -> &str { + pub fn name(&self) -> &'i str { match self { Variable::Scalar(scalar) => scalar.name, Variable::Stream(stream) => stream.name, @@ -113,7 +113,7 @@ impl<'i> VariableWithLambda<'i> { Self::Stream(StreamWithLambda::new(name, Some(lambda), position)) } - pub fn name(&self) -> &str { + pub fn name(&self) -> &'i str { match self { VariableWithLambda::Scalar(scalar) => scalar.name, VariableWithLambda::Stream(stream) => stream.name, diff --git a/crates/air-lib/air-parser/src/parser/air_parser.rs b/crates/air-lib/air-parser/src/parser/air_parser.rs index a1e69b1d..33cce10f 100644 --- a/crates/air-lib/air-parser/src/parser/air_parser.rs +++ b/crates/air-lib/air-parser/src/parser/air_parser.rs @@ -142,6 +142,9 @@ fn parser_error_to_label(file_id: usize, error: ParserError) -> Label { IteratorRestrictionNotAllowed(start, end, _) => { Label::primary(file_id, start..end).with_message(error.to_string()) } + MultipleIterableValues(start, end, _) => { + Label::primary(file_id, start..end).with_message(error.to_string()) + } } } diff --git a/crates/air-lib/air-parser/src/parser/errors.rs b/crates/air-lib/air-parser/src/parser/errors.rs index 50fdacb5..17eaea3d 100644 --- a/crates/air-lib/air-parser/src/parser/errors.rs +++ b/crates/air-lib/air-parser/src/parser/errors.rs @@ -40,6 +40,9 @@ pub enum ParserError { #[error("new can't be applied to a '{2}' because it's an iterator")] IteratorRestrictionNotAllowed(usize, usize, String), + + #[error("multiple iterable values found for iterable name '{2}'")] + MultipleIterableValues(usize, usize, String), } impl From for ParserError { diff --git a/crates/air-lib/air-parser/src/parser/span.rs b/crates/air-lib/air-parser/src/parser/span.rs index 2b61ebd8..7662300b 100644 --- a/crates/air-lib/air-parser/src/parser/span.rs +++ b/crates/air-lib/air-parser/src/parser/span.rs @@ -17,7 +17,7 @@ use serde::Deserialize; use serde::Serialize; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Span { pub left: usize, pub right: usize, @@ -28,7 +28,35 @@ impl Span { Self { left, right } } - pub fn contains(&self, position: usize) -> bool { + pub fn contains_position(&self, position: usize) -> bool { self.left < position && position < self.right } + + pub fn contains_span(&self, span: Self) -> bool { + self.contains_position(span.left) && self.contains_position(span.right) + } +} + +use std::cmp::Ordering; + +impl PartialOrd for Span { + fn partial_cmp(&self, other: &Self) -> Option { + let self_min = std::cmp::min(self.left, self.right); + let other_min = std::cmp::min(other.left, other.right); + + if self_min < other_min { + Some(Ordering::Less) + } else if self == other { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } +} + +impl Ord for Span { + fn cmp(&self, other: &Self) -> Ordering { + // it's safe since partial_cmp always returns Some + self.partial_cmp(other).unwrap() + } } diff --git a/crates/air-lib/air-parser/src/parser/tests/fold.rs b/crates/air-lib/air-parser/src/parser/tests/fold.rs index 58f531a3..2f5e0bb1 100644 --- a/crates/air-lib/air-parser/src/parser/tests/fold.rs +++ b/crates/air-lib/air-parser/src/parser/tests/fold.rs @@ -59,6 +59,89 @@ fn parse_undefined_iterable() { assert!(matches!(parser_error, ParserError::UndefinedIterable(..))); } +#[test] +fn parse_fold_with_undefined_iterable() { + let source_code = r#" + (seq + (null) + (fold iterable i + (seq + (call "" ("" "") ["hello" ""] $void) + (next i) + ) + ) + ) + "#; + + let lexer = crate::AIRLexer::new(source_code); + + let parser = crate::AIRParser::new(); + let mut errors = Vec::new(); + let mut validator = crate::parser::VariableValidator::new(); + parser + .parse(source_code, &mut errors, &mut validator, lexer) + .expect("parser shouldn't fail"); + + let errors = validator.finalize(); + + assert_eq!(errors.len(), 1); + + let error = &errors[0].error; + let parser_error = match error { + ParseError::User { error } => error, + _ => panic!("unexpected error type"), + }; + + assert!(matches!(parser_error, ParserError::UndefinedVariable(..))); +} + +#[test] +fn parse_fold_with_multiple_iterator() { + let source_code = r#" + (seq + (seq + (call "" ("" "") [] iterable_1) + (call "" ("" "") [] iterable_2) + ) + (fold iterable_1 i + (seq + (fold iterable_2 i + (seq + (call "" ("" "") ["hello" ""] $void) + (next i) + ) + ) + (next i) + ) + ) + ) + "#; + + let lexer = crate::AIRLexer::new(source_code); + + let parser = crate::AIRParser::new(); + let mut errors = Vec::new(); + let mut validator = crate::parser::VariableValidator::new(); + parser + .parse(source_code, &mut errors, &mut validator, lexer) + .expect("parser shouldn't fail"); + + let errors = validator.finalize(); + + assert_eq!(errors.len(), 1); + + let error = &errors[0].error; + let parser_error = match error { + ParseError::User { error } => error, + _ => panic!("unexpected error type"), + }; + + assert!(matches!( + parser_error, + ParserError::MultipleIterableValues(..) + )); +} + #[test] fn parse_fold() { let source_code = r#" diff --git a/crates/air-lib/air-parser/src/parser/validator.rs b/crates/air-lib/air-parser/src/parser/validator.rs index 1864f0cb..1d36e43c 100644 --- a/crates/air-lib/air-parser/src/parser/validator.rs +++ b/crates/air-lib/air-parser/src/parser/validator.rs @@ -25,6 +25,7 @@ use lalrpop_util::ParseError; use multimap::MultiMap; use std::collections::HashMap; +use std::ops::Deref; /// Intermediate implementation of variable validator. /// @@ -36,10 +37,10 @@ use std::collections::HashMap; #[derive(Debug, Default, Clone)] pub struct VariableValidator<'i> { /// Contains the most left definition of a variables met in call outputs. - met_variables: HashMap<&'i str, Span>, + met_variable_definitions: HashMap<&'i str, Span>, /// Contains iterables met in fold iterables. - met_iterators: MultiMap<&'i str, Span>, + met_iterator_definitions: MultiMap<&'i str, Span>, /// These variables from calls and folds haven't been resolved at the first meet. unresolved_variables: MultiMap<&'i str, Span>, @@ -92,7 +93,7 @@ impl<'i> VariableValidator<'i> { pub(super) fn met_new(&mut self, new: &New<'i>, span: Span) { self.check_for_non_iterators - .push((variable_name(&new.variable), span)); + .push((new.variable.name(), span)); // new defines a new variable self.met_variable_definition(&new.variable, span); } @@ -120,7 +121,7 @@ impl<'i> VariableValidator<'i> { self.met_variable_definition(&ap.result, span); } - pub(super) fn finalize(&self) -> Vec, ParserError>> { + pub(super) fn finalize(self) -> Vec, ParserError>> { let mut errors = Vec::new(); for (name, span) in self.unresolved_variables.iter() { if !self.contains_variable(name, *span) { @@ -140,6 +141,19 @@ impl<'i> VariableValidator<'i> { } } + for (name, mut spans) in self.met_iterator_definitions.into_iter() { + spans.sort(); + let mut prev_span: Option = None; + for span in spans { + match prev_span { + Some(prev_span) if prev_span.contains_span(span) => { + add_to_errors(name, &mut errors, span, Token::Fold) + } + Some(_) | None => prev_span = Some(span), + } + } + } + errors } @@ -169,8 +183,7 @@ impl<'i> VariableValidator<'i> { } fn met_variable_wl(&mut self, variable: &VariableWithLambda<'i>, span: Span) { - let name = variable_wl_name(variable); - self.met_variable_name(name, span); + self.met_variable_name(variable.name(), span); } fn met_variable_name(&mut self, name: &'i str, span: Span) { @@ -180,13 +193,13 @@ impl<'i> VariableValidator<'i> { } fn contains_variable(&self, key: &str, key_span: Span) -> bool { - if let Some(found_span) = self.met_variables.get(key) { + if let Some(found_span) = self.met_variable_definitions.get(key) { if found_span < &key_span { return true; } } - let found_spans = match self.met_iterators.get_vec(key) { + let found_spans = match self.met_iterator_definitions.get_vec(key) { Some(found_spans) => found_spans, None => return false, }; @@ -195,14 +208,13 @@ impl<'i> VariableValidator<'i> { } fn met_variable_definition(&mut self, variable: &Variable<'i>, span: Span) { - let name = variable_name(variable); - self.met_variable_name_definition(name, span); + self.met_variable_name_definition(variable.name(), span); } fn met_variable_name_definition(&mut self, name: &'i str, span: Span) { use std::collections::hash_map::Entry; - match self.met_variables.entry(name) { + match self.met_variable_definitions.entry(name) { Entry::Occupied(occupied) => { if occupied.get() > &span { *occupied.into_mut() = span; @@ -228,7 +240,7 @@ impl<'i> VariableValidator<'i> { /// Checks that multimap contains a span for given key such that provided span lies inside it. fn contains_iterable(&self, key: &str, key_span: Span) -> bool { - let found_spans = match self.met_iterators.get_vec(key) { + let found_spans = match self.met_iterator_definitions.get_vec(key) { Some(found_spans) => found_spans, None => return false, }; @@ -239,25 +251,7 @@ impl<'i> VariableValidator<'i> { } fn met_iterator_definition(&mut self, iterator: &Scalar<'i>, span: Span) { - self.met_iterators.insert(iterator.name, span); - } -} - -use std::cmp::Ordering; -use std::ops::Deref; - -impl PartialOrd for Span { - fn partial_cmp(&self, other: &Self) -> Option { - let self_min = std::cmp::min(self.left, self.right); - let other_min = std::cmp::min(other.left, other.right); - - if self_min < other_min { - Some(Ordering::Less) - } else if self == other { - Some(Ordering::Equal) - } else { - Some(Ordering::Greater) - } + self.met_iterator_definitions.insert(iterator.name, span); } } @@ -273,6 +267,7 @@ fn add_to_errors<'err, 'i>( Token::New => { ParserError::IteratorRestrictionNotAllowed(span.left, span.right, variable_name) } + Token::Fold => ParserError::MultipleIterableValues(span.left, span.right, variable_name), _ => ParserError::UndefinedVariable(span.left, span.right, variable_name), }; let error = ParseError::User { error }; @@ -286,17 +281,3 @@ fn add_to_errors<'err, 'i>( errors.push(error); } - -fn variable_name<'v>(variable: &Variable<'v>) -> &'v str { - match variable { - Variable::Scalar(scalar) => scalar.name, - Variable::Stream(stream) => stream.name, - } -} - -fn variable_wl_name<'v>(variable: &VariableWithLambda<'v>) -> &'v str { - match variable { - VariableWithLambda::Scalar(scalar) => scalar.name, - VariableWithLambda::Stream(stream) => stream.name, - } -} diff --git a/crates/air-lib/test-utils/src/lib.rs b/crates/air-lib/test-utils/src/lib.rs index 4b3602a3..dfb71a0d 100644 --- a/crates/air-lib/test-utils/src/lib.rs +++ b/crates/air-lib/test-utils/src/lib.rs @@ -106,3 +106,28 @@ pub fn print_trace(result: &RawAVMOutcome, trace_name: &str) { } println!("]"); } + +#[macro_export] +macro_rules! rc { + ($expr:expr) => { + std::rc::Rc::new($expr) + }; +} + +use air::ToErrorCode; +use air_interpreter_interface::INTERPRETER_SUCCESS; + +pub fn is_interpreter_succeded(result: &RawAVMOutcome) -> bool { + result.ret_code == INTERPRETER_SUCCESS +} + +pub fn check_error(result: &RawAVMOutcome, error: impl ToErrorCode + ToString) -> bool { + println!( + "{} == {} || {} == {}", + result.ret_code, + error.to_error_code(), + result.error_message, + error.to_string() + ); + result.ret_code == error.to_error_code() && result.error_message == error.to_string() +}