feat(execution-engine)!: this patch prohibits error code = 0 (#702)

This commit is contained in:
raftedproc 2023-09-21 17:33:57 +03:00 committed by GitHub
parent b713e447fc
commit 45035ccff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 591 additions and 284 deletions

View File

@ -17,7 +17,7 @@
use super::ErrorAffectable;
use super::Joinable;
use crate::execution_step::execution_context::errors::StreamMapError;
use crate::execution_step::execution_context::LastErrorObjectError;
use crate::execution_step::execution_context::ErrorObjectError;
use crate::execution_step::lambda_applier::LambdaError;
use crate::JValue;
use crate::ToErrorCode;
@ -77,7 +77,7 @@ pub enum CatchableError {
/// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type.
#[error(transparent)]
InvalidLastErrorObjectError(#[from] LastErrorObjectError),
InvalidErrorObjectError(#[from] ErrorObjectError),
/// A new with this variable name was met and right after that it was accessed
/// that is prohibited.

View File

@ -20,7 +20,7 @@ use thiserror::Error as ThisError;
/// Describes errors related to converting a scalar into error object.
#[derive(Debug, Clone, ThisError)]
pub enum LastErrorObjectError {
pub enum ErrorObjectError {
#[error("scalar should have an object type to be converted into error object, but '{0}' doesn't have")]
ScalarMustBeObject(JValue),
@ -33,4 +33,7 @@ pub enum LastErrorObjectError {
field_name: &'static str,
expected_type: &'static str,
},
#[error("error code must be non-zero, but it is zero")]
ErrorCodeMustBeNonZero,
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use super::LastErrorObjectError;
use super::ErrorObjectError;
use crate::execution_step::RcSecurityTetraplet;
use crate::JValue;
@ -72,25 +72,25 @@ pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instru
}
/// Checks that a scalar is a value of an object types that contains at least two fields:
/// - error_code
/// - error_code != 0
/// - message
pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectError> {
pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), ErrorObjectError> {
let fields = match scalar {
JValue::Object(fields) => fields,
_ => return Err(LastErrorObjectError::ScalarMustBeObject(scalar.clone())),
_ => return Err(ErrorObjectError::ScalarMustBeObject(scalar.clone())),
};
let check_field = |field_name| {
fields
.get(field_name)
.ok_or_else(|| LastErrorObjectError::ScalarMustContainField {
.ok_or_else(|| ErrorObjectError::ScalarMustContainField {
scalar: scalar.clone(),
field_name,
})
};
let error_code = check_field(ERROR_CODE_FIELD_NAME)?;
ensure_jvalue_is_integer(scalar, error_code, ERROR_CODE_FIELD_NAME)?;
ensure_error_code_correct(scalar, error_code, ERROR_CODE_FIELD_NAME)?;
let message = check_field(MESSAGE_FIELD_NAME)?;
ensure_jvalue_is_string(scalar, message, MESSAGE_FIELD_NAME)?;
@ -98,14 +98,16 @@ pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectE
Ok(())
}
fn ensure_jvalue_is_integer(
fn ensure_error_code_correct(
scalar: &JValue,
value: &JValue,
field_name: &'static str,
) -> Result<(), LastErrorObjectError> {
) -> Result<(), ErrorObjectError> {
match value {
JValue::Number(number) if number.is_i64() || number.is_u64() => Ok(()),
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
JValue::Number(number) if number.is_i64() | number.is_u64() => {
ensure_error_code_is_error(number.as_i64().unwrap())
}
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(),
field_name,
expected_type: "integer",
@ -113,14 +115,18 @@ fn ensure_jvalue_is_integer(
}
}
fn ensure_jvalue_is_string(
scalar: &JValue,
value: &JValue,
field_name: &'static str,
) -> Result<(), LastErrorObjectError> {
fn ensure_error_code_is_error(number: i64) -> Result<(), ErrorObjectError> {
if number == NO_ERROR_ERROR_CODE {
Err(ErrorObjectError::ErrorCodeMustBeNonZero)
} else {
Ok(())
}
}
fn ensure_jvalue_is_string(scalar: &JValue, value: &JValue, field_name: &'static str) -> Result<(), ErrorObjectError> {
match value {
JValue::String(_) => Ok(()),
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(),
field_name,
expected_type: "string",

View File

@ -20,7 +20,7 @@ mod errors_utils;
mod instruction_error_definition;
mod last_error_descriptor;
pub use errors::LastErrorObjectError;
pub use errors::ErrorObjectError;
pub use instruction_error_definition::no_error;
pub use instruction_error_definition::no_error_object;
pub use instruction_error_definition::InstructionError;

View File

@ -55,7 +55,7 @@ fn fail_with_scalar<'i>(scalar: &ast::Scalar<'i>, exec_ctx: &mut ExecutionCtx<'i
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value
let tetraplet = tetraplet.remove(0);
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?;
check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance)
}
@ -64,7 +64,7 @@ fn fail_with_scalar_wl<'i>(scalar: &ast::ScalarWithLambda<'i>, exec_ctx: &mut Ex
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value
let tetraplet = tetraplet.remove(0);
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?;
check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet), provenance)
}
@ -97,7 +97,7 @@ fn fail_with_canon_stream(
let (value, mut tetraplets, provenance) = ast_canon.resolve(exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?;
check_error_object(&value).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplets.remove(0)), provenance)
}
@ -111,6 +111,8 @@ fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()>
provenance,
} = exec_ctx.last_error_descriptor.error();
check_error_object(error).map_err(CatchableError::InvalidErrorObjectError)?;
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159
let error = error.clone();
let tetraplet = tetraplet.clone();
@ -127,6 +129,8 @@ fn fail_with_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
provenance,
} = exec_ctx.error_descriptor.error();
check_error_object(error).map_err(CatchableError::InvalidErrorObjectError)?;
fail_with_error_object(exec_ctx, error.clone(), tetraplet.clone(), provenance.clone())
}

View File

@ -29,7 +29,7 @@ const TETRAPLET_IDX_CORRECT: &str = "selects always return a correct index insid
pub use errors::CatchableError;
pub use errors::ExecutionError;
pub use errors::UncatchableError;
pub use execution_context::LastErrorObjectError;
pub use execution_context::ErrorObjectError;
pub use lambda_applier::LambdaError;
pub mod errors_prelude {

View File

@ -46,9 +46,9 @@ pub use execution_step::execution_context::InstructionError;
pub use execution_step::execution_context::NO_ERROR_ERROR_CODE;
pub use execution_step::execution_context::NO_ERROR_MESSAGE;
pub use execution_step::CatchableError;
pub use execution_step::ErrorObjectError;
pub use execution_step::ExecutionError;
pub use execution_step::LambdaError;
pub use execution_step::LastErrorObjectError;
pub use execution_step::UncatchableError;
pub use farewell_step::FarewellError;
pub use polyplets::ResolvedTriplet;

View File

@ -0,0 +1,73 @@
/*
* Copyright 2023 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 air::no_error_object;
use air::ExecutionCidState;
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
#[test]
fn fail_with_rebubble_error() {
let peer_id = "peer_id";
let script = r#"
(seq
(xor
(xor
(match 1 2 (null) )
(fail :error:)
)
(call "peer_id" ("m" "f1") [:error:] scalar1) ; behaviour = echo
)
(call "peer_id" ("m" "f2") [:error:] scalar2) ; behaviour = echo
)
"#
.to_string();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(peer_id).unwrap();
let actual_trace = trace_from_result(&result.last().unwrap());
let mut cid_tracker: ExecutionCidState = ExecutionCidState::new();
let expected_error_json = {
json!({
"error_code": 10006,
"instruction": "xor",
"message": "fail with '{\"error_code\":10001,\"instruction\":\"match 1 2\",\"message\":\"compared values do not match\"}' is used without corresponding xor"
})
};
let expected_trace: Vec<ExecutedState> = vec![
scalar_tracked!(
expected_error_json.clone(),
cid_tracker,
peer = peer_id,
service = "m..0",
function = "f1",
args = [expected_error_json]
),
scalar_tracked!(
no_error_object(),
cid_tracker,
peer = peer_id,
service = "m..1",
function = "f2",
args = [no_error_object()]
),
];
assert_eq!(actual_trace, expected_trace,);
}

View File

@ -16,10 +16,10 @@
use air::no_error_object;
use air::CatchableError;
use air::ErrorObjectError;
use air::ExecutionCidState;
use air::ExecutionError;
use air::LambdaError;
use air::LastErrorObjectError;
use air::SecurityTetraplet;
use air::NO_ERROR_ERROR_CODE;
use air::NO_ERROR_MESSAGE;
@ -422,7 +422,7 @@ fn fail_with_scalar_from_call_not_enough_fields() {
let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustContainField {
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustContainField {
scalar: service_result,
field_name: "message",
});
@ -446,8 +446,7 @@ fn fail_with_scalar_from_call_not_right_type() {
let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error =
CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustBeObject(service_result));
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustBeObject(service_result));
assert!(check_error(&result, expected_error));
}
@ -468,7 +467,7 @@ fn fail_with_scalar_from_call_field_not_right_type() {
let result = call_vm!(vm, <_>::default(), &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarFieldIsWrongType {
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarFieldIsWrongType {
scalar: service_result,
field_name: "error_code",
expected_type: "integer",

View File

@ -14,5 +14,6 @@
* limitations under the License.
*/
mod error;
mod invalid_air;
mod last_error;

View File

@ -15,6 +15,9 @@
*/
use air::CatchableError;
use air::ErrorObjectError;
use air::ExecutionError;
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
#[test]
@ -209,3 +212,58 @@ fn fail_with_canon_stream() {
};
assert!(check_error(&result, expected_error));
}
fn fail_to_fail_with_unsupported_errorcode(script: &str) {
let local_peer_id = "local_peer_id";
let script = script.to_string();
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
.expect("invalid test AIR script");
let results = executor.execute_all(local_peer_id).unwrap();
let expected_error = ExecutionError::Catchable(rc!(CatchableError::InvalidErrorObjectError(
ErrorObjectError::ErrorCodeMustBeNonZero
)));
assert!(check_error(&results.last().unwrap(), expected_error));
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_scalar() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = {"error_code": 0, "message": "some message"}
(fail scalar)
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_scalar_wl() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = {"key": {"error_code": 0, "message": "some message"} }
(fail scalar.$.key)
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_canon() {
let script = r#"
(seq
(call "local_peer_id" ("m" "f1") [] scalar) ; ok = [{"error_code": 0, "message": "some message"}]
(fail scalar.$.[0])
)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}
#[test]
fn fail_to_fail_with_unsupported_errorcode_in_error() {
let script = r#"
(fail :error:)
"#;
fail_to_fail_with_unsupported_errorcode(script);
}

View File

@ -86,7 +86,10 @@ Instr: Box<Instruction<'input>> = {
Box::new(Instruction::New(new))
},
"(" fail <fail_body: FailBody> ")" => {
<left: @L> "(" fail <fail_body: FailBody> ")" <right: @R> => {
let span = Span::new(left, right);
validator.met_fail_literal(&fail_body, span);
Box::new(Instruction::Fail(fail_body))
},

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,9 @@ pub enum ParserError {
ap_key_type: String,
ap_result_name: String,
},
#[error("error code 0 with fail is unsupported")]
UnsupportedLiteralErrCodes { span: Span },
}
impl ParserError {
@ -67,6 +70,7 @@ impl ParserError {
Self::MultipleIterableValuesForOneIterator { span, .. } => *span,
Self::MultipleNextInFold { span, .. } => *span,
Self::UnsupportedMapKeyType { span, .. } => *span,
Self::UnsupportedLiteralErrCodes { span } => *span,
}
}
@ -116,6 +120,10 @@ impl ParserError {
ap_result_name: ap_result_name.into(),
}
}
pub fn unsupported_literal_errcodes(span: Span) -> Self {
Self::UnsupportedLiteralErrCodes { span }
}
}
impl From<std::convert::Infallible> for ParserError {

View File

@ -92,6 +92,10 @@ pub(super) fn fail_last_error() -> Instruction<'static> {
Instruction::Fail(Fail::LastError)
}
pub(super) fn fail_error() -> Instruction<'static> {
Instruction::Fail(Fail::Error)
}
pub(super) fn fold_scalar_variable<'i>(
scalar: Scalar<'i>,
iterator: Scalar<'i>,

View File

@ -68,3 +68,47 @@ fn parse_fail_scalar_with_lambda() {
));
assert_eq!(instruction, expected)
}
#[test]
fn parse_fail_scalar_with_error() {
let source_code = r#"
(fail :error:)
"#;
let instruction = parse(source_code);
let expected = fail_error();
assert_eq!(instruction, expected)
}
#[test]
fn parse_fail_literal_0() {
use crate::parser::errors::ParserError;
use lalrpop_util::ParseError;
let source_code = r#"
(fail 0 "some error")
"#;
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::UnsupportedLiteralErrCodes { .. }
));
}

View File

@ -59,9 +59,12 @@ pub struct VariableValidator<'i> {
/// Contains all names that should be checked that they are not iterators.
not_iterators_candidates: Vec<(&'i str, Span)>,
// This contains info about unssuported map key arguments used with ap instruction,
// namely (key map ApArgument)
/// This contains info about unssuported map key arguments used with ap instruction,
/// namely (key map ApArgument).
unsupported_map_keys: Vec<(String, &'i str, Span)>,
/// This vector contains all literal error codes used with fail.
unsupported_literal_errcodes: Vec<(i64, Span)>,
}
impl<'i> VariableValidator<'i> {
@ -197,6 +200,15 @@ impl<'i> VariableValidator<'i> {
}
}
pub(super) fn met_fail_literal(&mut self, fail: &Fail<'i>, span: Span) {
match fail {
Fail::Literal { ret_code, .. } if *ret_code == 0 => {
self.unsupported_literal_errcodes.push((*ret_code, span))
}
_ => {}
}
}
pub(super) fn finalize(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
ValidatorErrorBuilder::new(self)
.check_undefined_variables()
@ -205,6 +217,7 @@ impl<'i> VariableValidator<'i> {
.check_new_on_iterators()
.check_iterator_for_multiple_definitions()
.check_for_unsupported_map_keys()
.check_for_unsupported_literal_errcodes()
.build()
}
@ -496,6 +509,14 @@ impl<'i> ValidatorErrorBuilder<'i> {
self
}
fn check_for_unsupported_literal_errcodes(mut self) -> Self {
for (_, span) in self.validator.unsupported_literal_errcodes.iter_mut() {
let error = ParserError::unsupported_literal_errcodes(*span);
add_to_errors(&mut self.errors, *span, Token::New, error);
}
self
}
fn build(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
self.errors
}