Refactor execution errors (#198)

This commit is contained in:
Mike Voronov 2021-12-21 11:37:35 +03:00 committed by GitHub
parent 54e383cdaf
commit f69b5aa728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 695 additions and 405 deletions

View File

@ -44,13 +44,13 @@ fn match_position_variable(
generation: Option<u32>, generation: Option<u32>,
ap_result: &MergerApResult, ap_result: &MergerApResult,
) -> ExecutionResult<()> { ) -> ExecutionResult<()> {
use crate::execution_step::ExecutionError::ApResultNotCorrespondToInstr; use crate::execution_step::UncatchableError::ApResultNotCorrespondToInstr;
use ast::Variable::*; use ast::Variable::*;
match (variable, generation) { match (variable, generation) {
(Stream(_), Some(_)) => Ok(()), (Stream(_), Some(_)) => Ok(()),
(Scalar(_), None) => Ok(()), (Scalar(_), None) => Ok(()),
_ => return crate::exec_err!(ApResultNotCorrespondToInstr(ap_result.clone())), _ => Err(ApResultNotCorrespondToInstr(ap_result.clone()).into()),
} }
} }

View File

@ -40,25 +40,26 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
log_instruction!(call, exec_ctx, trace_ctx); log_instruction!(call, exec_ctx, trace_ctx);
exec_ctx.tracker.meet_call(); exec_ctx.tracker.meet_call();
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| { let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx)
set_last_error(self, exec_ctx, e.clone(), None); .map_err(|e| set_last_error(self, exec_ctx, e, None))?;
e
})?;
let tetraplet = resolved_call.as_tetraplet(); let tetraplet = resolved_call.as_tetraplet();
joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx).map_err(|e| { joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx)
set_last_error(self, exec_ctx, e.clone(), Some(tetraplet)); .map_err(|e| set_last_error(self, exec_ctx, e, Some(tetraplet)))
e
})
} }
} }
fn set_last_error<'i>( fn set_last_error<'i>(
call: &Call<'i>, call: &Call<'i>,
exec_ctx: &mut ExecutionCtx<'i>, exec_ctx: &mut ExecutionCtx<'i>,
e: Rc<ExecutionError>, execution_error: ExecutionError,
tetraplet: Option<RSecurityTetraplet>, tetraplet: Option<RSecurityTetraplet>,
) { ) -> ExecutionError {
let catchable_error = match execution_error {
ExecutionError::Catchable(catchable) => catchable,
ExecutionError::Uncatchable(_) => return execution_error,
};
let current_peer_id = match &tetraplet { let current_peer_id = match &tetraplet {
// use tetraplet if they set, because an error could be propagated from data // 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 // (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(), 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 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 = Some(last_error);
exec_ctx.last_error_could_be_set = false; exec_ctx.last_error_could_be_set = false;
ExecutionError::Catchable(catchable_error)
} }

View File

@ -15,8 +15,8 @@
*/ */
use super::*; use super::*;
use crate::exec_err;
use crate::execution_step::air::call::call_result_setter::set_result_from_value; use crate::execution_step::air::call::call_result_setter::set_result_from_value;
use crate::execution_step::CatchableError;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use air_interpreter_data::CallResult; use air_interpreter_data::CallResult;
@ -51,7 +51,7 @@ pub(super) fn handle_prev_state<'i>(
let ret_code = *ret_code; let ret_code = *ret_code;
let err_msg = err_msg.clone(); let err_msg = err_msg.clone();
trace_ctx.meet_call_end(prev_result); 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 }) RequestSentBy(Sender::PeerIdWithCallId { peer_id, call_id })
if peer_id.as_str() == exec_ctx.current_peer_id.as_str() => 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_message = Rc::new(service_result.result);
let error = ExecutionError::LocalServiceError(service_result.ret_code, error_message.clone()); let error = CatchableError::LocalServiceError(service_result.ret_code, error_message.clone());
let error = Rc::new(error);
trace_ctx.meet_call_end(CallServiceFailed(service_result.ret_code, error_message)); trace_ctx.meet_call_end(CallServiceFailed(service_result.ret_code, error_message));
Err(error) Err(error.into())
} }
fn try_to_service_result( fn try_to_service_result(
@ -152,7 +151,7 @@ fn try_to_service_result(
let error = CallServiceFailed(i32::MAX, error_msg.clone()); let error = CallServiceFailed(i32::MAX, error_msg.clone());
trace_ctx.meet_call_end(error); trace_ctx.meet_call_end(error);
Err(Rc::new(ExecutionError::LocalServiceError(i32::MAX, error_msg))) Err(CatchableError::LocalServiceError(i32::MAX, error_msg).into())
} }
} }
} }

View File

@ -22,6 +22,7 @@ use super::triplet::resolve;
use super::*; use super::*;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use crate::execution_step::SecurityTetraplets; use crate::execution_step::SecurityTetraplets;
use crate::execution_step::UncatchableError;
use crate::trace_to_exec_err; use crate::trace_to_exec_err;
use crate::JValue; use crate::JValue;
use crate::SecurityTetraplet; use crate::SecurityTetraplet;
@ -187,10 +188,10 @@ fn check_output_name(output: &ast::CallOutputValue<'_>, exec_ctx: &ExecutionCtx<
if exec_ctx.scalars.shadowing_allowed() { if exec_ctx.scalars.shadowing_allowed() {
Ok(()) Ok(())
} else { } 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(()), Err(_) => Ok(()),
} }
} }

View File

@ -15,9 +15,8 @@
*/ */
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use crate::exec_err; use crate::execution_step::CatchableError;
use crate::JValue; use crate::JValue;
use air_parser::ast; 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<String> { fn try_jvalue_to_string(jvalue: JValue, variable: &ast::VariableWithLambda<'_>) -> ExecutionResult<String> {
match jvalue { match jvalue {
JValue::String(s) => Ok(s), JValue::String(s) => Ok(s),
_ => exec_err!(ExecutionError::IncompatibleJValueType { _ => Err(CatchableError::IncompatibleJValueType {
variable_name: variable.name().to_string(), variable_name: variable.name().to_string(),
actual_value: jvalue, actual_value: jvalue,
expected_value_type: "string", expected_value_type: "string",
}), }
.into()),
} }
} }

View File

@ -15,10 +15,10 @@
*/ */
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::LastErrorDescriptor; use super::LastErrorDescriptor;
use super::TraceHandler; use super::TraceHandler;
use crate::execution_step::CatchableError;
use crate::log_instruction; use crate::log_instruction;
use air_parser::ast::Fail; use air_parser::ast::Fail;
@ -48,7 +48,7 @@ fn fail_with_literals<'i>(
fail: &Fail<'_>, fail: &Fail<'_>,
exec_ctx: &mut ExecutionCtx<'i>, exec_ctx: &mut ExecutionCtx<'i>,
) -> ExecutionResult<()> { ) -> ExecutionResult<()> {
let fail_error = ExecutionError::FailWithoutXorError { let fail_error = CatchableError::FailWithoutXorError {
ret_code, ret_code,
error_message: error_message.to_string(), error_message: error_message.to_string(),
}; };
@ -71,7 +71,7 @@ fn fail_with_literals<'i>(
update_context_state(exec_ctx); update_context_state(exec_ctx);
Err(fail_error) Err(fail_error.into())
} }
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> { 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); update_context_state(exec_ctx);
Err(last_error) Err(last_error.into())
} }
fn update_context_state(exec_ctx: &mut ExecutionCtx<'_>) { fn update_context_state(exec_ctx: &mut ExecutionCtx<'_>) {

View File

@ -22,7 +22,6 @@ pub(crate) use fold_state::IterableType;
pub(super) use utils::*; pub(super) use utils::*;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::Instruction; use super::Instruction;
use super::ScalarRef; use super::ScalarRef;

View File

@ -15,7 +15,7 @@
*/ */
use super::*; use super::*;
use crate::exec_err; use crate::execution_step::CatchableError;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use crate::JValue; use crate::JValue;
use crate::LambdaAST; use crate::LambdaAST;
@ -107,11 +107,12 @@ fn from_call_result(call_result: ValueAggregate, variable_name: &str) -> Executi
array.len() array.len()
} }
v => { v => {
return exec_err!(ExecutionError::IncompatibleJValueType { return Err(CatchableError::IncompatibleJValueType {
variable_name: variable_name.to_string(), variable_name: variable_name.to_string(),
actual_value: (*v).clone(), actual_value: (*v).clone(),
expected_value_type: "array", expected_value_type: "array",
}) }
.into());
} }
}; };
@ -156,10 +157,7 @@ fn from_jvalue(
let iterable = match jvalue { let iterable = match jvalue {
JValue::Array(array) => array, JValue::Array(array) => array,
_ => { _ => {
return exec_err!(ExecutionError::FoldIteratesOverNonArray( return Err(CatchableError::FoldIteratesOverNonArray(jvalue.clone(), formatted_lambda_ast).into());
jvalue.clone(),
formatted_lambda_ast
))
} }
}; };

View File

@ -16,9 +16,9 @@
use super::compare_matchable::are_matchable_eq; use super::compare_matchable::are_matchable_eq;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::TraceHandler; use super::TraceHandler;
use crate::execution_step::CatchableError;
use crate::execution_step::Joinable; use crate::execution_step::Joinable;
use crate::joinable; use crate::joinable;
use crate::log_instruction; use crate::log_instruction;
@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for Match<'i> {
)?; )?;
if !are_values_equal { if !are_values_equal {
return crate::exec_err!(ExecutionError::MatchWithoutXorError); return Err(CatchableError::MatchWithoutXorError.into());
} }
self.instruction.execute(exec_ctx, trace_ctx) self.instruction.execute(exec_ctx, trace_ctx)

View File

@ -16,9 +16,9 @@
use super::compare_matchable::are_matchable_eq; use super::compare_matchable::are_matchable_eq;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::TraceHandler; use super::TraceHandler;
use crate::execution_step::CatchableError;
use crate::execution_step::Joinable; use crate::execution_step::Joinable;
use crate::joinable; use crate::joinable;
use crate::log_instruction; use crate::log_instruction;
@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for MisMatch<'i> {
)?; )?;
if are_values_equal { if are_values_equal {
return crate::exec_err!(ExecutionError::MismatchWithoutXorError); return Err(CatchableError::MismatchWithoutXorError.into());
} }
self.instruction.execute(exec_ctx, trace_ctx) self.instruction.execute(exec_ctx, trace_ctx)

View File

@ -35,7 +35,6 @@ pub(crate) use fold::FoldState;
use super::boxed_value::ScalarRef; use super::boxed_value::ScalarRef;
use super::boxed_value::ValueAggregate; use super::boxed_value::ValueAggregate;
use super::execution_context::*; use super::execution_context::*;
use super::Catchable;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError; use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
@ -47,48 +46,68 @@ use air_parser::ast::Instruction;
/// Executes instruction and updates last error if needed. /// Executes instruction and updates last error if needed.
macro_rules! execute { macro_rules! execute {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => { ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
match $instr.execute($exec_ctx, $trace_ctx) { let result = $instr.execute($exec_ctx, $trace_ctx);
Err(e) => {
if !$exec_ctx.last_error_could_be_set {
return Err(e);
}
let instruction = format!("{}", $self); if !$exec_ctx.last_error_could_be_set {
let last_error = return result;
LastErrorDescriptor::new(e.clone(), instruction, $exec_ctx.current_peer_id.to_string(), None);
$exec_ctx.last_error = Some(last_error);
Err(e)
}
v => v,
} }
};
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 /// Executes match/mismatch instructions and updates last error if error type wasn't
/// MatchWithoutXorError or MismatchWithoutXorError. /// MatchWithoutXorError or MismatchWithoutXorError.
macro_rules! execute_match_mismatch { macro_rules! execute_match_or_mismatch {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => { ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
match $instr.execute($exec_ctx, $trace_ctx) { let result = $instr.execute($exec_ctx, $trace_ctx);
Err(e) => { let execution_error = match result {
use std::borrow::Borrow; Err(e) => e,
v => return v,
};
if !$exec_ctx.last_error_could_be_set if !$exec_ctx.last_error_could_be_set || execution_error.is_match_or_mismatch() {
|| matches!(&*e.borrow(), ExecutionError::MatchWithoutXorError) return Err(execution_error);
|| 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,
} }
};
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> { 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), Instruction::Xor(xor) => execute!(self, xor, exec_ctx, trace_ctx),
// match/mismatch shouldn't rewrite last_error // match/mismatch shouldn't rewrite last_error
Instruction::Match(match_) => execute_match_mismatch!(self, match_, exec_ctx, trace_ctx), Instruction::Match(match_) => execute_match_or_mismatch!(self, match_, exec_ctx, trace_ctx),
Instruction::MisMatch(mismatch) => execute_match_mismatch!(self, mismatch, 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."), Instruction::Error => unreachable!("should not execute if parsing succeeded. QED."),
} }

View File

@ -16,7 +16,6 @@
mod completeness_updater; mod completeness_updater;
use super::Catchable;
use super::ExecutableInstruction; use super::ExecutableInstruction;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError; use super::ExecutionError;
@ -30,8 +29,6 @@ use completeness_updater::ParCompletenessUpdater;
use air_parser::ast::Par; use air_parser::ast::Par;
use air_trace_handler::SubtreeType; use air_trace_handler::SubtreeType;
use std::rc::Rc;
#[rustfmt::skip] #[rustfmt::skip]
impl<'i> ExecutableInstruction<'i> for Par<'i> { impl<'i> ExecutableInstruction<'i> for Par<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> { fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
@ -84,7 +81,7 @@ fn execute_subtree<'i>(
enum SubtreeResult { enum SubtreeResult {
Succeeded, Succeeded,
Failed(Rc<ExecutionError>), Failed(ExecutionError),
} }
fn prepare_par_result( fn prepare_par_result(

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
use super::Catchable;
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionError; use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
@ -42,12 +41,13 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
} }
fn print_xor_log(e: &ExecutionError) { 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 // 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 // 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 // bubble up until the very beginning of current subtree. So the error message shouldn't
// be print out in order not to confuse users. // be print out in order not to confuse users.
ExecutionError::MatchWithoutXorError | ExecutionError::MismatchWithoutXorError => {} return;
e => log::warn!("xor caught an error: {}", e),
} }
log::warn!("xor caught an error: {}", e);
} }

View File

@ -21,7 +21,6 @@ mod resolved_call_result;
mod stream; mod stream;
use super::iterable::IterableItem; use super::iterable::IterableItem;
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::ValueAggregate; use super::ValueAggregate;
use crate::execution_step::lambda_applier::*; use crate::execution_step::lambda_applier::*;

View File

@ -14,12 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
use super::ExecutionError::LambdaApplierError;
use super::ExecutionResult; use super::ExecutionResult;
use super::JValuable; use super::JValuable;
use super::LambdaAST; use super::LambdaAST;
use super::LambdaError::EmptyStream; use super::LambdaError::EmptyStream;
use crate::exec_err; use crate::execution_step::CatchableError::LambdaApplierError;
use crate::execution_step::ExecutionCtx; use crate::execution_step::ExecutionCtx;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use crate::execution_step::SecurityTetraplets; use crate::execution_step::SecurityTetraplets;
@ -30,7 +29,7 @@ use std::borrow::Cow;
impl JValuable for () { impl JValuable for () {
fn apply_lambda<'i>(&self, _lambda: &LambdaAST<'_>, _exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> { fn apply_lambda<'i>(&self, _lambda: &LambdaAST<'_>, _exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
// applying lambda to an empty stream will produce a join behaviour // 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>( fn apply_lambda_with_tetraplets<'i>(
@ -39,7 +38,7 @@ impl JValuable for () {
_exec_ctx: &ExecutionCtx<'i>, _exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, RSecurityTetraplet)> { ) -> ExecutionResult<(&JValue, RSecurityTetraplet)> {
// applying lambda to an empty stream will produce a join behaviour // 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> { fn as_jvalue(&self) -> Cow<'_, JValue> {

View File

@ -17,7 +17,6 @@
use super::select_from_stream; use super::select_from_stream;
use super::ExecutionResult; use super::ExecutionResult;
use super::JValuable; use super::JValuable;
use crate::exec_err;
use crate::execution_step::boxed_value::Generation; use crate::execution_step::boxed_value::Generation;
use crate::execution_step::boxed_value::Stream; use crate::execution_step::boxed_value::Stream;
use crate::execution_step::ExecutionCtx; use crate::execution_step::ExecutionCtx;
@ -89,7 +88,7 @@ impl<'stream> StreamJvaluableIngredients<'stream> {
} }
pub(self) fn iter(&self) -> ExecutionResult<StreamIter<'_>> { pub(self) fn iter(&self) -> ExecutionResult<StreamIter<'_>> {
use super::ExecutionError::StreamDontHaveSuchGeneration; use crate::execution_step::CatchableError::StreamDontHaveSuchGeneration;
match self.stream.iter(self.generation) { match self.stream.iter(self.generation) {
Some(iter) => Ok(iter), Some(iter) => Ok(iter),
@ -99,10 +98,7 @@ impl<'stream> StreamJvaluableIngredients<'stream> {
Generation::Last => unreachable!(), Generation::Last => unreachable!(),
}; };
exec_err!(StreamDontHaveSuchGeneration( Err(StreamDontHaveSuchGeneration(self.stream.deref().clone(), generation as usize).into())
self.stream.deref().clone(),
generation as usize
))
} }
} }
} }

View File

@ -20,7 +20,6 @@ mod scalar;
mod stream; mod stream;
mod variable; mod variable;
pub(crate) use super::ExecutionError;
pub(crate) use iterable::*; pub(crate) use iterable::*;
pub(crate) use jvaluable::*; pub(crate) use jvaluable::*;
pub(crate) use scalar::ScalarRef; pub(crate) use scalar::ScalarRef;

View File

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
use super::ExecutionError;
use super::ExecutionResult; use super::ExecutionResult;
use super::ValueAggregate; use super::ValueAggregate;
use crate::exec_err; use crate::execution_step::CatchableError;
use crate::JValue; use crate::JValue;
use std::fmt::Formatter; 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 /// 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 /// 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. /// 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)] #[derive(Debug, Default, Clone)]
pub(crate) struct Stream(Vec<Vec<ValueAggregate>>); pub struct Stream(Vec<Vec<ValueAggregate>>);
impl Stream { impl Stream {
pub(crate) fn from_generations_count(count: usize) -> Self { pub(crate) fn from_generations_count(count: usize) -> Self {
@ -51,7 +49,7 @@ impl Stream {
}; };
if generation >= self.0.len() { 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); self.0[generation].push(value);

View File

@ -14,19 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
mod catchable; pub(crate) use super::Joinable;
mod joinable;
pub(crate) use catchable::Catchable;
pub(crate) use joinable::Joinable;
use super::Stream; use super::Stream;
use crate::execution_step::lambda_applier::LambdaError; use crate::execution_step::lambda_applier::LambdaError;
use crate::JValue; use crate::JValue;
use crate::ToErrorCode; use crate::ToErrorCode;
use air_trace_handler::MergerApResult;
use air_trace_handler::TraceHandlerError;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumDiscriminants; use strum_macros::EnumDiscriminants;
use strum_macros::EnumIter; use strum_macros::EnumIter;
@ -34,26 +28,22 @@ use thiserror::Error as ThisError;
use std::rc::Rc; 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)] #[derive(ThisError, EnumDiscriminants, Debug)]
#[strum_discriminants(derive(EnumIter))] #[strum_discriminants(derive(EnumIter))]
pub(crate) enum ExecutionError { pub enum CatchableError {
/// An error is occurred while calling local service via call_service. /// An error is occurred while calling local service via call_service.
#[error("Local service error, ret_code is {0}, error message is '{1}'")] #[error("Local service error, ret_code is {0}, error message is '{1}'")]
LocalServiceError(i32, Rc<String>), LocalServiceError(i32, Rc<String>),
/// Variable with such a name wasn't defined during AIR script execution. /// 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")] #[error("variable with name '{0}' wasn't defined during script execution")]
VariableNotFound(String), 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. /// Provided JValue has incompatible type with a requested one.
#[error( #[error(
"expected JValue type '{expected_value_type}' for the variable `{variable_name}`, but got '{actual_value}'" "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, 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. /// A fold instruction must iterate over array value.
#[error("lambda '{1}' returned non-array value '{0}' for fold instruction")] #[error("lambda '{1}' returned non-array value '{0}' for fold instruction")]
FoldIteratesOverNonArray(JValue, String), 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. /// This error type is produced by a match to notify xor that compared values aren't equal.
#[error("match is used without corresponding xor")] #[error("match is used without corresponding xor")]
MatchWithoutXorError, 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")] #[error("fail with ret_code '{ret_code}' and error_message '{error_message}' is used without corresponding xor")]
FailWithoutXorError { ret_code: i64, error_message: String }, 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)] #[error(transparent)]
TraceError(#[from] TraceHandlerError), LambdaApplierError(#[from] LambdaError),
/// Errors occurred while insertion of a value inside stream that doesn't have corresponding generation. /// 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")] #[error("stream {0:?} doesn't have generation with number {1}, probably a supplied to the interpreter data is corrupted")]
StreamDontHaveSuchGeneration(Stream, usize), 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<LambdaError> for Rc<ExecutionError> { impl From<LambdaError> for Rc<CatchableError> {
fn from(e: LambdaError) -> Self { 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 impl ToErrorCode for Rc<CatchableError> {
/// From<TraceHandlerError> for Rc<ExecutionError> 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 {
fn to_error_code(&self) -> i64 { fn to_error_code(&self) -> i64 {
const EXECUTION_ERRORS_START_ID: i64 = 1000; self.as_ref().to_error_code()
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
} }
} }
*/ impl ToErrorCode for CatchableError {
impl ToErrorCode for Rc<ExecutionError> {
fn to_error_code(&self) -> i64 { fn to_error_code(&self) -> i64 {
const EXECUTION_ERRORS_START_ID: i64 = 1000; use crate::utils::CATCHABLE_ERRORS_START_ID;
crate::generate_to_error_code!(self, CatchableError, CATCHABLE_ERRORS_START_ID)
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
} }
} }
@ -157,11 +105,11 @@ macro_rules! log_join {
} }
#[rustfmt::skip::macros(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. /// Returns true, if supplied error is related to variable not found errors type.
/// Print log if this is joinable error type. /// Print log if this is joinable error type.
fn is_joinable(&self) -> bool { fn is_joinable(&self) -> bool {
use ExecutionError::*; use CatchableError::*;
match self { match self {
VariableNotFound(var_name) => { 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<std::convert::Infallible> for ExecutionError {
fn from(_: std::convert::Infallible) -> Self {
unreachable!()
}
}

View File

@ -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<CatchableError>),
#[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<CatchableError> 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,
}
}
}

View File

@ -14,11 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
/// This trait is intended to differentiate between catchable and non-catchable error types. mod catchable_errors;
/// Errors of the first type could be caught by xor, the second couldn't and should stop mod execution_errors;
/// AIR execution. This is needed to prevent some malicious data merging and manage mod joinable;
/// prev_data always in a valid state. mod uncatchable_errors;
pub(crate) trait Catchable {
/// Return true, if error is catchable. pub use catchable_errors::CatchableError;
fn is_catchable(&self) -> bool; pub use execution_errors::ExecutionError;
} pub use uncatchable_errors::UncatchableError;
pub(crate) use joinable::Joinable;
use super::Stream;

View File

@ -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)
}
}

View File

@ -15,7 +15,7 @@
*/ */
use super::ExecutionCtx; use super::ExecutionCtx;
use crate::execution_step::ExecutionError; use crate::execution_step::CatchableError;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use crate::SecurityTetraplet; use crate::SecurityTetraplet;
@ -28,7 +28,7 @@ use std::rc::Rc;
/// This struct is intended to track the last arisen error. /// This struct is intended to track the last arisen error.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct LastErrorDescriptor { pub(crate) struct LastErrorDescriptor {
pub(crate) error: Rc<ExecutionError>, pub(crate) error: Rc<CatchableError>,
pub(crate) instruction: String, pub(crate) instruction: String,
pub(crate) peer_id: String, pub(crate) peer_id: String,
pub(crate) tetraplet: Option<RSecurityTetraplet>, pub(crate) tetraplet: Option<RSecurityTetraplet>,
@ -70,7 +70,7 @@ impl<'s> LastErrorWithTetraplet {
impl LastErrorDescriptor { impl LastErrorDescriptor {
pub(crate) fn new( pub(crate) fn new(
error: Rc<ExecutionError>, error: Rc<CatchableError>,
instruction: String, instruction: String,
peer_id: String, peer_id: String,
tetraplet: Option<RSecurityTetraplet>, tetraplet: Option<RSecurityTetraplet>,

View File

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::exec_err;
use crate::execution_step::boxed_value::ScalarRef; 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::ExecutionResult;
use crate::execution_step::FoldState; use crate::execution_step::FoldState;
use crate::execution_step::ValueAggregate; use crate::execution_step::ValueAggregate;
@ -87,7 +86,7 @@ impl<'i> Scalars<'i> {
} }
Occupied(entry) => { Occupied(entry) => {
if !shadowing_allowed { if !shadowing_allowed {
return exec_err!(ExecutionError::MultipleVariablesFound(entry.key().clone())); return Err(UncatchableError::MultipleVariablesFound(entry.key().clone()).into());
} }
let values = entry.into_mut(); let values = entry.into_mut();
@ -115,9 +114,7 @@ impl<'i> Scalars<'i> {
entry.insert(fold_state); entry.insert(fold_state);
Ok(()) Ok(())
} }
Occupied(entry) => { Occupied(entry) => Err(UncatchableError::MultipleIterableValues(entry.key().clone()).into()),
exec_err!(ExecutionError::MultipleIterableValues(entry.key().clone()))
}
} }
} }
@ -135,13 +132,13 @@ impl<'i> Scalars<'i> {
.rev() .rev()
.find_map(|scalar| scalar.as_ref()) .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>> { pub(crate) fn get_iterable_mut(&mut self, name: &str) -> ExecutionResult<&mut FoldState<'i>> {
self.iterable_values self.iterable_values
.get_mut(name) .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<ScalarRef<'i>> { pub(crate) fn get(&'i self, name: &str) -> ExecutionResult<ScalarRef<'i>> {
@ -149,7 +146,7 @@ impl<'i> Scalars<'i> {
let iterable_value = self.iterable_values.get(name); let iterable_value = self.iterable_values.get(name);
match (value, iterable_value) { 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)), (Ok(value), None) => Ok(ScalarRef::Value(value)),
(Err(_), Some(iterable_value)) => Ok(ScalarRef::IterableValue(iterable_value)), (Err(_), Some(iterable_value)) => Ok(ScalarRef::IterableValue(iterable_value)),
(Ok(_), Some(_)) => unreachable!("this is checked on the parsing stage"), (Ok(_), Some(_)) => unreachable!("this is checked on the parsing stage"),

View File

@ -189,7 +189,7 @@ fn find_closest<'d>(
) -> Option<&'d RefCell<Stream>> { ) -> Option<&'d RefCell<Stream>> {
// descriptors are placed in a order of decreasing scopes, so it's enough to get the latest suitable // descriptors are placed in a order of decreasing scopes, so it's enough to get the latest suitable
for descriptor in descriptors.rev() { for descriptor in descriptors.rev() {
if descriptor.span.contains(position) { if descriptor.span.contains_position(position) {
return Some(&descriptor.stream); return Some(&descriptor.stream);
} }
} }

View File

@ -18,6 +18,7 @@ use super::utils::*;
use super::LambdaError; use super::LambdaError;
use crate::execution_step::ExecutionCtx; use crate::execution_step::ExecutionCtx;
use crate::execution_step::ExecutionResult; use crate::execution_step::ExecutionResult;
use crate::lambda_to_execution_error;
use crate::JValue; use crate::JValue;
use crate::LambdaAST; use crate::LambdaAST;
@ -39,19 +40,18 @@ pub(crate) fn select_from_stream<'value, 'i>(
let idx = match prefix { let idx = match prefix {
ArrayAccess { idx } => *idx, ArrayAccess { idx } => *idx,
FieldAccessByName { field_name } => { FieldAccessByName { field_name } => {
return Err(LambdaError::FieldAccessorAppliedToStream { return lambda_to_execution_error!(Err(LambdaError::FieldAccessorAppliedToStream {
field_name: field_name.to_string(), field_name: field_name.to_string(),
} }));
.into());
} }
_ => unreachable!("should not execute if parsing succeeded. QED."), _ => unreachable!("should not execute if parsing succeeded. QED."),
}; };
let stream_size = stream.len(); let stream_size = stream.len();
let value = stream let value = lambda_to_execution_error!(stream
.peekable() .peekable()
.nth(idx as usize) .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 result = select(value, body.iter(), exec_ctx)?;
let select_result = StreamSelectResult::new(result, idx); let select_result = StreamSelectResult::new(result, idx);
@ -66,14 +66,14 @@ pub(crate) fn select<'value, 'accessor, 'i>(
for accessor in lambda { for accessor in lambda {
match accessor { match accessor {
ValueAccessor::ArrayAccess { idx } => { 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 } => { 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 } => { ValueAccessor::FieldAccessByScalar { scalar_name } => {
let scalar = exec_ctx.scalars.get(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."), ValueAccessor::Error => unreachable!("should not execute if parsing succeeded. QED."),
} }

View File

@ -18,8 +18,9 @@ use crate::JValue;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
/// Describes errors related to applying lambdas to values.
#[derive(Debug, Clone, ThisError)] #[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")] #[error("lambda is applied to a stream that have only '{stream_size}' elements, but '{idx}' requested")]
StreamNotHaveEnoughValues { stream_size: usize, idx: u32 }, StreamNotHaveEnoughValues { stream_size: usize, idx: u32 },
@ -36,12 +37,12 @@ pub(crate) enum LambdaError {
#[error("value '{value}' does not contain element for idx = '{idx}'")] #[error("value '{value}' does not contain element for idx = '{idx}'")]
ValueNotContainSuchArrayIdx { value: JValue, idx: u32 }, 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}'")] #[error("value '{value}' is not an map-type to match field accessor with field_name = '{field_name}'")]
FieldAccessorNotMatchValue { value: JValue, field_name: String }, 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`")] #[error("index accessor `{accessor} can't be converted to u32`")]
IndexAccessNotU32 { accessor: serde_json::Number }, IndexAccessNotU32 { accessor: serde_json::Number },

View File

@ -18,8 +18,20 @@ mod applier;
mod errors; mod errors;
mod utils; mod utils;
pub use errors::LambdaError;
pub(crate) type LambdaResult<T> = std::result::Result<T, LambdaError>; pub(crate) type LambdaResult<T> = std::result::Result<T, LambdaError>;
pub(crate) use applier::select; pub(crate) use applier::select;
pub(crate) use applier::select_from_stream; 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),
))
})
};
}

View File

@ -39,14 +39,12 @@ pub(super) fn try_jvalue_with_field_name<'value>(
field_name: &str, field_name: &str,
) -> LambdaResult<&'value JValue> { ) -> LambdaResult<&'value JValue> {
match jvalue { match jvalue {
JValue::Object(values_map) => { JValue::Object(values_map) => values_map
values_map .get(field_name)
.get(field_name) .ok_or_else(|| LambdaError::ValueNotContainSuchField {
.ok_or_else(|| LambdaError::JValueNotContainSuchField { value: jvalue.clone(),
value: jvalue.clone(), field_name: field_name.to_string(),
field_name: field_name.to_string(), }),
})
}
_ => Err(LambdaError::FieldAccessorNotMatchValue { _ => Err(LambdaError::FieldAccessorNotMatchValue {
value: jvalue.clone(), value: jvalue.clone(),
field_name: field_name.to_string(), field_name: field_name.to_string(),

View File

@ -21,14 +21,23 @@ pub(crate) mod execution_context;
mod lambda_applier; mod lambda_applier;
mod resolver; 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::ExecutableInstruction;
pub(super) use self::air::FoldState; pub(super) use self::air::FoldState;
pub(super) use boxed_value::Generation; pub(super) use boxed_value::Generation;
pub(super) use boxed_value::ScalarRef; pub(super) use boxed_value::ScalarRef;
pub(super) use boxed_value::Stream; pub(super) use boxed_value::Stream;
pub(super) use boxed_value::ValueAggregate; pub(super) use boxed_value::ValueAggregate;
pub(crate) use errors::Catchable;
pub(super) use errors::ExecutionError;
pub(crate) use errors::Joinable; pub(crate) use errors::Joinable;
pub(crate) use execution_context::ExecutionCtx; pub(crate) use execution_context::ExecutionCtx;
@ -37,13 +46,6 @@ pub(crate) use air_trace_handler::TraceHandler;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
type ExecutionResult<T> = std::result::Result<T, Rc<ExecutionError>>; type ExecutionResult<T> = std::result::Result<T, ExecutionError>;
type RSecurityTetraplet = Rc<RefCell<crate::SecurityTetraplet>>; type RSecurityTetraplet = Rc<RefCell<crate::SecurityTetraplet>>;
type SecurityTetraplets = Vec<RSecurityTetraplet>; type SecurityTetraplets = Vec<RSecurityTetraplet>;
#[macro_export]
macro_rules! exec_err {
($err:expr) => {
Err(std::rc::Rc::new($err))
};
}

View File

@ -35,13 +35,7 @@ pub enum FarewellError {
impl ToErrorCode for FarewellError { impl ToErrorCode for FarewellError {
fn to_error_code(&self) -> i64 { fn to_error_code(&self) -> i64 {
const FAREWELL_ERRORS_START_ID: i64 = 20000; use crate::utils::FAREWELL_ERRORS_START_ID;
crate::generate_to_error_code!(self, FarewellError, FAREWELL_ERRORS_START_ID)
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
} }
} }

View File

@ -17,7 +17,8 @@
mod errors; mod errors;
mod outcome; mod outcome;
pub(crate) use errors::FarewellError; pub use errors::FarewellError;
pub(crate) use outcome::from_execution_error; pub(crate) use outcome::from_execution_error;
pub(crate) use outcome::from_success_result; pub(crate) use outcome::from_success_result;
pub(crate) use outcome::from_uncatchable_error; pub(crate) use outcome::from_uncatchable_error;

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
#![allow(improper_ctypes)]
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
#![deny( #![deny(
dead_code, dead_code,
@ -36,8 +35,14 @@ pub use air_interpreter_interface::InterpreterOutcome;
pub use air_interpreter_interface::RunParameters; pub use air_interpreter_interface::RunParameters;
pub use air_interpreter_interface::INTERPRETER_SUCCESS; pub use air_interpreter_interface::INTERPRETER_SUCCESS;
pub use execution_step::execution_context::LastError; 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::ResolvedTriplet;
pub use polyplets::SecurityTetraplet; pub use polyplets::SecurityTetraplet;
pub use preparation_step::PreparationError;
pub use utils::ToErrorCode;
pub use crate::runner::execute_air; pub use crate::runner::execute_air;
@ -55,6 +60,5 @@ pub mod parser {
} }
pub(crate) type JValue = serde_json::Value; pub(crate) type JValue = serde_json::Value;
pub(crate) use utils::ToErrorCode;
use air_lambda_parser::LambdaAST; use air_lambda_parser::LambdaAST;

View File

@ -43,13 +43,7 @@ pub enum PreparationError {
impl ToErrorCode for PreparationError { impl ToErrorCode for PreparationError {
fn to_error_code(&self) -> i64 { fn to_error_code(&self) -> i64 {
const PREPARATION_ERRORS_START_ID: i64 = 1; use crate::utils::PREPARATION_ERROR_START_ID;
crate::generate_to_error_code!(self, PreparationError, PREPARATION_ERROR_START_ID)
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
} }
} }

View File

@ -17,6 +17,7 @@
mod errors; mod errors;
mod preparation; mod preparation;
pub(crate) use errors::PreparationError; pub use errors::PreparationError;
pub(crate) use preparation::prepare; pub(crate) use preparation::prepare;
pub(crate) use preparation::PreparationDescriptor; pub(crate) use preparation::PreparationDescriptor;

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::execution_step::Catchable;
use crate::execution_step::ExecutableInstruction; use crate::execution_step::ExecutableInstruction;
use crate::farewell_step as farewell; use crate::farewell_step as farewell;
use crate::preparation_step::prepare; use crate::preparation_step::prepare;
@ -58,8 +57,8 @@ fn execute_air_impl(
mut trace_handler, mut trace_handler,
air, air,
} = match prepare(&prev_data, &data, air.as_str(), &call_results, params) { } = match prepare(&prev_data, &data, air.as_str(), &call_results, params) {
Ok(desc) => desc, Ok(descriptor) => descriptor,
// return the initial data in case of errors // return the prev data in case of errors
Err(error) => return Err(farewell::from_uncatchable_error(prev_data, error)), 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), Ok(_) => farewell::from_success_result(exec_ctx, trace_handler),
// return new collected trace in case of errors // return new collected trace in case of errors
Err(error) if error.is_catchable() => Err(farewell::from_execution_error(exec_ctx, trace_handler, error)), 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)), Err(error) => Err(farewell::from_uncatchable_error(prev_data, error)),
} }
} }

View File

@ -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;

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
mod error_codes;
mod to_error_code; mod to_error_code;
pub(crate) use to_error_code::ToErrorCode; pub(crate) use error_codes::*;
pub use to_error_code::ToErrorCode;

View File

@ -14,24 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
pub(crate) trait ToErrorCode { pub trait ToErrorCode {
fn to_error_code(&self) -> i64; fn to_error_code(&self) -> i64;
} }
/*
use concat_idents::concat_idents;
#[macro_export] #[macro_export]
macro_rules! generate_to_error_code { macro_rules! generate_to_error_code {
($error_type:ident, $start_id: ident) => { ($self: expr, $error_type:ident, $start_id: expr) => {
const PREPARATION_ERRORS_START_ID: u32 = $start_id; 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 mut errors = error_discriminant::iter();
let actual_error_type = PreparationErrorDiscriminants::from(self); let actual_error_type = error_discriminant::from($self);
// unwrap is safe here because errors are guaranteed to contain all errors variants // 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; let enum_variant_position = errors.position(|et| et == actual_error_type).unwrap() as i64;
PREPARATION_ERRORS_START_ID + enum_variant_position error_start_id + enum_variant_position
}
})
})
} }
} }
*/

View File

@ -14,8 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
use air::UncatchableError;
use air_test_utils::prelude::*; 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). // 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. // Additionally, check that empty string for data does the same as empty call path.
#[test] #[test]
@ -98,18 +102,21 @@ fn variables() {
// Check that duplicate variables are impossible. // Check that duplicate variables are impossible.
#[test] #[test]
fn duplicate_variables() { 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 (seq
(call "some_peer_id" ("some_service_id" "local_fn_name") [] modules) (call "{peer_id}" ("some_service_id" "local_fn_name") [] {variable_name})
(call "some_peer_id" ("some_service_id" "local_fn_name") [] modules) (call "{peer_id}" ("some_service_id" "local_fn_name") [] {variable_name})
) )
"#; "#);
let result = call_vm!(vm, "asd", script, "", ""); 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()); assert!(result.next_peer_pks.is_empty());
} }

View File

@ -14,11 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
use air::CatchableError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use fstrings::f; use fstrings::f;
use fstrings::format_args_f; use fstrings::format_args_f;
use std::rc::Rc;
#[test] #[test]
fn fail_with_last_error() { fn fail_with_last_error() {
let local_peer_id = "local_peer_id"; let local_peer_id = "local_peer_id";
@ -32,11 +35,10 @@ fn fail_with_last_error() {
)"#); )"#);
let result = call_vm!(vm, "", script, "", ""); let result = call_vm!(vm, "", script, "", "");
assert_eq!(result.ret_code, 1000);
assert_eq!( let expected_error =
result.error_message, CatchableError::LocalServiceError(1, Rc::new(r#""failed result from fallible_call_service""#.to_string()));
r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"# assert!(check_error(&result, expected_error));
);
} }
#[test] #[test]
@ -53,11 +55,12 @@ fn fail_with_literals() {
); );
let result = call_vm!(vm, "", script, "", ""); let result = call_vm!(vm, "", script, "", "");
assert_eq!(result.ret_code, 1012);
assert_eq!( let expected_error = CatchableError::FailWithoutXorError {
result.error_message, ret_code: 1337,
"fail with ret_code '1337' and error_message 'error message' is used without corresponding xor" error_message: "error message".to_string(),
); };
assert!(check_error(&result, expected_error));
} }
#[test] #[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!( assert_eq!(
tetraplet_anchor.borrow()[0][0], tetraplet_anchor.borrow()[0][0],
SecurityTetraplet::new(local_peer_id, fallible_service_id, local_fn_name, "") SecurityTetraplet::new(local_peer_id, fallible_service_id, local_fn_name, "")

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air::{PreparationError, ToErrorCode};
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -157,7 +158,8 @@ fn inner_fold_with_same_iterator() {
let result = call_vm!(vm, "", script, "", ""); 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] #[test]

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air::CatchableError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -163,8 +164,9 @@ fn match_with_equal_numbers() {
(null) (null)
)"; )";
let result = checked_call_vm!(vm, "asd", script, "", ""); let result = call_vm!(vm, "asd", script, "", "");
assert_eq!(result.ret_code, 0);
assert!(is_interpreter_succeded(&result));
} }
#[test] #[test]
@ -192,11 +194,13 @@ fn match_without_xor() {
let result = call_vm!(set_variable_vm, "", &script, "", ""); let result = call_vm!(set_variable_vm, "", &script, "", "");
let result = call_vm!(vm, "", &script, "", result.data); 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); 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] #[test]

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air::CatchableError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -143,11 +144,13 @@ fn mismatch_without_xor() {
let result = call_vm!(set_variable_vm, "asd", &script, "", ""); let result = call_vm!(set_variable_vm, "asd", &script, "", "");
let result = call_vm!(vm, "asd", &script, "", result.data); 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); 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] #[test]
@ -183,6 +186,5 @@ fn mismatch_with_two_xors() {
let mut actual_trace = trace_from_result(&result); let mut actual_trace = trace_from_result(&result);
let expected_executed_call_result = executed_state::request_sent_by(local_peer_id); 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); assert_eq!(actual_trace.pop().unwrap(), expected_executed_call_result);
} }

View File

@ -14,8 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
use air::UncatchableError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use fstrings::f;
use fstrings::format_args_f;
#[test] #[test]
fn xor() { fn xor() {
let local_peer_id = "local_peer_id"; 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 mut set_variables_vm = create_avm(echo_call_service(), set_variables_peer_id);
let local_peer_id = "local_peer_id"; let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id); let some_string = "some_string";
let expected_string = "expected_string";
let some_string = String::from("some_string"); let variable_name = "result_1";
let expected_string = String::from("expected_string"); let script = f!(r#"
let script = format!(
r#"
(seq (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 (xor
(call "{1}" ("service_id_1" "local_fn_name") [""] result_1) (call "{local_peer_id}" ("service_id_1" "local_fn_name") [""] {variable_name})
(call "{1}" ("service_id_2" "local_fn_name") ["{3}"] result_2) (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 = call_vm!(set_variables_vm, "asd", &script, "", "");
let result = checked_call_vm!(vm, "asd", script, "", result.data);
let actual_trace = trace_from_result(&result); let expected_error = UncatchableError::MultipleVariablesFound(variable_name.to_string());
let expected_trace = vec![ assert!(check_error(&result, expected_error));
executed_state::scalar_string(some_string),
executed_state::scalar_string(expected_string),
];
assert_eq!(actual_trace, expected_trace);
} }
#[test] #[test]

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air::PreparationError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -160,5 +161,8 @@ fn invalid_air() {
let script = r#"(seq )"#; let script = r#"(seq )"#;
let result = call_vm!(vm, "", script, "", ""); 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));
} }

View File

@ -74,9 +74,9 @@ fn flattening_scalar_arrays() {
); );
let result = checked_call_vm!(set_variable_vm, "asd", script.clone(), "", ""); 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!( assert_eq!(
closure_call_args.service_id_var, closure_call_args.service_id_var,
Rc::new(RefCell::new("local_service_id".to_string())) 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!(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!( assert_eq!(
closure_call_args.service_id_var, closure_call_args.service_id_var,
Rc::new(RefCell::new("local_service_id".to_string())) 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!(set_variable_vm, "asd", script.clone(), "", "");
let result = checked_call_vm!(local_vm, "asd", script.clone(), "", result.data); 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![]))); assert_eq!(closure_call_args.args_var, Rc::new(RefCell::new(vec![])));
} }

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
use air::CatchableError;
use air::LambdaError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use fstrings::f; 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 result = checked_call_vm!(local_vm, "", non_join_stream_script, "", "");
let actual_trace = trace_from_result(&result); let actual_trace = trace_from_result(&result);
assert_eq!(result.ret_code, 0);
assert_eq!(actual_trace.len(), 3); assert_eq!(actual_trace.len(), 3);
let join_stream_script = format!( 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 result = checked_call_vm!(local_vm, "", join_stream_script, "", "");
let actual_trace = trace_from_result(&result); 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 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] #[test]
fn dont_wait_on_json_path_on_scalars() { 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!({ let object = json!({
"err_msg": "", "err_msg": "",
"is_authenticated": 1, "is_authenticated": 1i32,
"ret_code": 0, "ret_code": 0i32,
}); });
let variables = maplit::hashmap!( let variables = maplit::hashmap!(
"array".to_string() => array, "array".to_string() => array.clone(),
"object".to_string() => object, "object".to_string() => object.clone(),
); );
let set_variables_call_service = set_variables_call_service(variables, VariableOptionSource::Argument(0)); 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 init_peer_id = "asd";
let result = call_vm!(set_variable_vm, init_peer_id, &script, "", ""); 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()); let array_result = call_vm!(array_consumer, init_peer_id, &script, "", result.data.clone());
assert_eq!(array_result.ret_code, 1003);
assert_eq!( let expected_error =
array_result.error_message, CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchArrayIdx { value: array, idx: 5 });
r#"value '[1,2,3,4,5]' does not contain element for idx = '5'"# assert!(check_error(&array_result, expected_error));
);
let script = format!( let script = format!(
r#" r#"
@ -191,11 +190,13 @@ fn dont_wait_on_json_path_on_scalars() {
let init_peer_id = "asd"; let init_peer_id = "asd";
let result = call_vm!(set_variable_vm, init_peer_id, &script, "", ""); let result = call_vm!(set_variable_vm, init_peer_id, &script, "", "");
let object_result = call_vm!(object_consumer, init_peer_id, script, "", result.data); let object_result = call_vm!(object_consumer, init_peer_id, script, "", result.data);
assert_eq!(object_result.ret_code, 1003);
assert_eq!( let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField {
object_result.error_message, value: object,
r#"value '{"err_msg":"","is_authenticated":1,"ret_code":0}' does not contain element with field name = 'non_exist_path'"# field_name: "non_exist_path".to_string(),
); });
assert!(check_error(&object_result, expected_error));
} }
#[test] #[test]

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
use air::CatchableError;
use air::LambdaError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use fstrings::f; use fstrings::f;
@ -27,20 +29,22 @@ fn lambda_not_allowed_for_non_objects_and_arrays() {
let local_peer_id = "local_peer_id"; let local_peer_id = "local_peer_id";
let mut local_vm = create_avm(echo_call_service(), local_peer_id); let mut local_vm = create_avm(echo_call_service(), local_peer_id);
let script = format!( let some_string = "some_string";
r#" let script = f!(r#"
(seq (seq
(call "{0}" ("" "") ["some_string"] string_variable) (call "{set_variable_peer_id}" ("" "") ["{some_string}"] string_variable)
(call "{1}" ("" "") [string_variable.$.some_lambda]) (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 = checked_call_vm!(set_variable_vm, "asd", &script, "", "");
let result = call_vm!(local_vm, "asd", script, "", result.data); 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] #[test]

View File

@ -14,8 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
use air::UncatchableError;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use pretty_assertions::assert_eq; use air_trace_handler::TraceHandlerError;
use air_trace_handler::{CallResultError, MergeError};
#[test] #[test]
fn par_early_exit() { 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 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); 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 actual_trace = trace_from_result(&init_result_3);
let expected_trace = trace_from_result(&init_result_2); let expected_trace = trace_from_result(&init_result_2);

View File

@ -62,5 +62,6 @@ fn issue_137() {
let node_2_result = checked_call_vm!(node_2, "", &script, "", initiator_result.data); 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 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); 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));
} }

View File

@ -88,7 +88,7 @@ impl<'i> Variable<'i> {
Self::Stream(Stream::new(name, position)) Self::Stream(Stream::new(name, position))
} }
pub fn name(&self) -> &str { pub fn name(&self) -> &'i str {
match self { match self {
Variable::Scalar(scalar) => scalar.name, Variable::Scalar(scalar) => scalar.name,
Variable::Stream(stream) => stream.name, Variable::Stream(stream) => stream.name,
@ -113,7 +113,7 @@ impl<'i> VariableWithLambda<'i> {
Self::Stream(StreamWithLambda::new(name, Some(lambda), position)) Self::Stream(StreamWithLambda::new(name, Some(lambda), position))
} }
pub fn name(&self) -> &str { pub fn name(&self) -> &'i str {
match self { match self {
VariableWithLambda::Scalar(scalar) => scalar.name, VariableWithLambda::Scalar(scalar) => scalar.name,
VariableWithLambda::Stream(stream) => stream.name, VariableWithLambda::Stream(stream) => stream.name,

View File

@ -142,6 +142,9 @@ fn parser_error_to_label(file_id: usize, error: ParserError) -> Label<usize> {
IteratorRestrictionNotAllowed(start, end, _) => { IteratorRestrictionNotAllowed(start, end, _) => {
Label::primary(file_id, start..end).with_message(error.to_string()) 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())
}
} }
} }

View File

@ -40,6 +40,9 @@ pub enum ParserError {
#[error("new can't be applied to a '{2}' because it's an iterator")] #[error("new can't be applied to a '{2}' because it's an iterator")]
IteratorRestrictionNotAllowed(usize, usize, String), IteratorRestrictionNotAllowed(usize, usize, String),
#[error("multiple iterable values found for iterable name '{2}'")]
MultipleIterableValues(usize, usize, String),
} }
impl From<std::convert::Infallible> for ParserError { impl From<std::convert::Infallible> for ParserError {

View File

@ -17,7 +17,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Span { pub struct Span {
pub left: usize, pub left: usize,
pub right: usize, pub right: usize,
@ -28,7 +28,35 @@ impl Span {
Self { left, right } Self { left, right }
} }
pub fn contains(&self, position: usize) -> bool { pub fn contains_position(&self, position: usize) -> bool {
self.left < position && position < self.right 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<Ordering> {
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()
}
} }

View File

@ -59,6 +59,89 @@ fn parse_undefined_iterable() {
assert!(matches!(parser_error, ParserError::UndefinedIterable(..))); 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] #[test]
fn parse_fold() { fn parse_fold() {
let source_code = r#" let source_code = r#"

View File

@ -25,6 +25,7 @@ use lalrpop_util::ParseError;
use multimap::MultiMap; use multimap::MultiMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
/// Intermediate implementation of variable validator. /// Intermediate implementation of variable validator.
/// ///
@ -36,10 +37,10 @@ use std::collections::HashMap;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct VariableValidator<'i> { pub struct VariableValidator<'i> {
/// Contains the most left definition of a variables met in call outputs. /// 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. /// 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. /// These variables from calls and folds haven't been resolved at the first meet.
unresolved_variables: MultiMap<&'i str, Span>, 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) { pub(super) fn met_new(&mut self, new: &New<'i>, span: Span) {
self.check_for_non_iterators self.check_for_non_iterators
.push((variable_name(&new.variable), span)); .push((new.variable.name(), span));
// new defines a new variable // new defines a new variable
self.met_variable_definition(&new.variable, span); self.met_variable_definition(&new.variable, span);
} }
@ -120,7 +121,7 @@ impl<'i> VariableValidator<'i> {
self.met_variable_definition(&ap.result, span); self.met_variable_definition(&ap.result, span);
} }
pub(super) fn finalize(&self) -> Vec<ErrorRecovery<usize, Token<'i>, ParserError>> { pub(super) fn finalize(self) -> Vec<ErrorRecovery<usize, Token<'i>, ParserError>> {
let mut errors = Vec::new(); let mut errors = Vec::new();
for (name, span) in self.unresolved_variables.iter() { for (name, span) in self.unresolved_variables.iter() {
if !self.contains_variable(name, *span) { 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<Span> = 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 errors
} }
@ -169,8 +183,7 @@ impl<'i> VariableValidator<'i> {
} }
fn met_variable_wl(&mut self, variable: &VariableWithLambda<'i>, span: Span) { fn met_variable_wl(&mut self, variable: &VariableWithLambda<'i>, span: Span) {
let name = variable_wl_name(variable); self.met_variable_name(variable.name(), span);
self.met_variable_name(name, span);
} }
fn met_variable_name(&mut self, name: &'i str, span: 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 { 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 { if found_span < &key_span {
return true; 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, Some(found_spans) => found_spans,
None => return false, None => return false,
}; };
@ -195,14 +208,13 @@ impl<'i> VariableValidator<'i> {
} }
fn met_variable_definition(&mut self, variable: &Variable<'i>, span: Span) { fn met_variable_definition(&mut self, variable: &Variable<'i>, span: Span) {
let name = variable_name(variable); self.met_variable_name_definition(variable.name(), span);
self.met_variable_name_definition(name, span);
} }
fn met_variable_name_definition(&mut self, name: &'i str, span: Span) { fn met_variable_name_definition(&mut self, name: &'i str, span: Span) {
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
match self.met_variables.entry(name) { match self.met_variable_definitions.entry(name) {
Entry::Occupied(occupied) => { Entry::Occupied(occupied) => {
if occupied.get() > &span { if occupied.get() > &span {
*occupied.into_mut() = 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. /// 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 { 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, Some(found_spans) => found_spans,
None => return false, None => return false,
}; };
@ -239,25 +251,7 @@ impl<'i> VariableValidator<'i> {
} }
fn met_iterator_definition(&mut self, iterator: &Scalar<'i>, span: Span) { fn met_iterator_definition(&mut self, iterator: &Scalar<'i>, span: Span) {
self.met_iterators.insert(iterator.name, span); self.met_iterator_definitions.insert(iterator.name, span);
}
}
use std::cmp::Ordering;
use std::ops::Deref;
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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)
}
} }
} }
@ -273,6 +267,7 @@ fn add_to_errors<'err, 'i>(
Token::New => { Token::New => {
ParserError::IteratorRestrictionNotAllowed(span.left, span.right, variable_name) 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), _ => ParserError::UndefinedVariable(span.left, span.right, variable_name),
}; };
let error = ParseError::User { error }; let error = ParseError::User { error };
@ -286,17 +281,3 @@ fn add_to_errors<'err, 'i>(
errors.push(error); 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,
}
}

View File

@ -106,3 +106,28 @@ pub fn print_trace(result: &RawAVMOutcome, trace_name: &str) {
} }
println!("]"); 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()
}