mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 20:40:50 +00:00
feat(execution-engine)!: this patch prohibits error code = 0 (#702)
This commit is contained in:
parent
b713e447fc
commit
45035ccff5
@ -17,7 +17,7 @@
|
|||||||
use super::ErrorAffectable;
|
use super::ErrorAffectable;
|
||||||
use super::Joinable;
|
use super::Joinable;
|
||||||
use crate::execution_step::execution_context::errors::StreamMapError;
|
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::execution_step::lambda_applier::LambdaError;
|
||||||
use crate::JValue;
|
use crate::JValue;
|
||||||
use crate::ToErrorCode;
|
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.
|
/// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidLastErrorObjectError(#[from] LastErrorObjectError),
|
InvalidErrorObjectError(#[from] ErrorObjectError),
|
||||||
|
|
||||||
/// A new with this variable name was met and right after that it was accessed
|
/// A new with this variable name was met and right after that it was accessed
|
||||||
/// that is prohibited.
|
/// that is prohibited.
|
||||||
|
@ -20,7 +20,7 @@ use thiserror::Error as ThisError;
|
|||||||
|
|
||||||
/// Describes errors related to converting a scalar into error object.
|
/// Describes errors related to converting a scalar into error object.
|
||||||
#[derive(Debug, Clone, ThisError)]
|
#[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")]
|
#[error("scalar should have an object type to be converted into error object, but '{0}' doesn't have")]
|
||||||
ScalarMustBeObject(JValue),
|
ScalarMustBeObject(JValue),
|
||||||
|
|
||||||
@ -33,4 +33,7 @@ pub enum LastErrorObjectError {
|
|||||||
field_name: &'static str,
|
field_name: &'static str,
|
||||||
expected_type: &'static str,
|
expected_type: &'static str,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("error code must be non-zero, but it is zero")]
|
||||||
|
ErrorCodeMustBeNonZero,
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::LastErrorObjectError;
|
use super::ErrorObjectError;
|
||||||
use crate::execution_step::RcSecurityTetraplet;
|
use crate::execution_step::RcSecurityTetraplet;
|
||||||
use crate::JValue;
|
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:
|
/// Checks that a scalar is a value of an object types that contains at least two fields:
|
||||||
/// - error_code
|
/// - error_code != 0
|
||||||
/// - message
|
/// - 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 {
|
let fields = match scalar {
|
||||||
JValue::Object(fields) => fields,
|
JValue::Object(fields) => fields,
|
||||||
_ => return Err(LastErrorObjectError::ScalarMustBeObject(scalar.clone())),
|
_ => return Err(ErrorObjectError::ScalarMustBeObject(scalar.clone())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let check_field = |field_name| {
|
let check_field = |field_name| {
|
||||||
fields
|
fields
|
||||||
.get(field_name)
|
.get(field_name)
|
||||||
.ok_or_else(|| LastErrorObjectError::ScalarMustContainField {
|
.ok_or_else(|| ErrorObjectError::ScalarMustContainField {
|
||||||
scalar: scalar.clone(),
|
scalar: scalar.clone(),
|
||||||
field_name,
|
field_name,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let error_code = check_field(ERROR_CODE_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)?;
|
let message = check_field(MESSAGE_FIELD_NAME)?;
|
||||||
ensure_jvalue_is_string(scalar, message, 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_jvalue_is_integer(
|
fn ensure_error_code_correct(
|
||||||
scalar: &JValue,
|
scalar: &JValue,
|
||||||
value: &JValue,
|
value: &JValue,
|
||||||
field_name: &'static str,
|
field_name: &'static str,
|
||||||
) -> Result<(), LastErrorObjectError> {
|
) -> Result<(), ErrorObjectError> {
|
||||||
match value {
|
match value {
|
||||||
JValue::Number(number) if number.is_i64() || number.is_u64() => Ok(()),
|
JValue::Number(number) if number.is_i64() | number.is_u64() => {
|
||||||
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
|
ensure_error_code_is_error(number.as_i64().unwrap())
|
||||||
|
}
|
||||||
|
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
|
||||||
scalar: scalar.clone(),
|
scalar: scalar.clone(),
|
||||||
field_name,
|
field_name,
|
||||||
expected_type: "integer",
|
expected_type: "integer",
|
||||||
@ -113,14 +115,18 @@ fn ensure_jvalue_is_integer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_jvalue_is_string(
|
fn ensure_error_code_is_error(number: i64) -> Result<(), ErrorObjectError> {
|
||||||
scalar: &JValue,
|
if number == NO_ERROR_ERROR_CODE {
|
||||||
value: &JValue,
|
Err(ErrorObjectError::ErrorCodeMustBeNonZero)
|
||||||
field_name: &'static str,
|
} else {
|
||||||
) -> Result<(), LastErrorObjectError> {
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_jvalue_is_string(scalar: &JValue, value: &JValue, field_name: &'static str) -> Result<(), ErrorObjectError> {
|
||||||
match value {
|
match value {
|
||||||
JValue::String(_) => Ok(()),
|
JValue::String(_) => Ok(()),
|
||||||
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
|
_ => Err(ErrorObjectError::ScalarFieldIsWrongType {
|
||||||
scalar: scalar.clone(),
|
scalar: scalar.clone(),
|
||||||
field_name,
|
field_name,
|
||||||
expected_type: "string",
|
expected_type: "string",
|
||||||
|
@ -20,7 +20,7 @@ mod errors_utils;
|
|||||||
mod instruction_error_definition;
|
mod instruction_error_definition;
|
||||||
mod last_error_descriptor;
|
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;
|
||||||
pub use instruction_error_definition::no_error_object;
|
pub use instruction_error_definition::no_error_object;
|
||||||
pub use instruction_error_definition::InstructionError;
|
pub use instruction_error_definition::InstructionError;
|
||||||
|
@ -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)?;
|
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
|
||||||
// tetraplets always have one element here and it'll be refactored after boxed value
|
// tetraplets always have one element here and it'll be refactored after boxed value
|
||||||
let tetraplet = tetraplet.remove(0);
|
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)
|
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)?;
|
let (value, mut tetraplet, provenance) = scalar.resolve(exec_ctx)?;
|
||||||
// tetraplets always have one element here and it'll be refactored after boxed value
|
// tetraplets always have one element here and it'll be refactored after boxed value
|
||||||
let tetraplet = tetraplet.remove(0);
|
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)
|
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)?;
|
let (value, mut tetraplets, provenance) = ast_canon.resolve(exec_ctx)?;
|
||||||
|
|
||||||
// tetraplets always have one element here and it'll be refactored after boxed value
|
// 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)
|
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,
|
provenance,
|
||||||
} = exec_ctx.last_error_descriptor.error();
|
} = 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
|
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159
|
||||||
let error = error.clone();
|
let error = error.clone();
|
||||||
let tetraplet = tetraplet.clone();
|
let tetraplet = tetraplet.clone();
|
||||||
@ -127,6 +129,8 @@ fn fail_with_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
|
|||||||
provenance,
|
provenance,
|
||||||
} = exec_ctx.error_descriptor.error();
|
} = 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())
|
fail_with_error_object(exec_ctx, error.clone(), tetraplet.clone(), provenance.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const TETRAPLET_IDX_CORRECT: &str = "selects always return a correct index insid
|
|||||||
pub use errors::CatchableError;
|
pub use errors::CatchableError;
|
||||||
pub use errors::ExecutionError;
|
pub use errors::ExecutionError;
|
||||||
pub use errors::UncatchableError;
|
pub use errors::UncatchableError;
|
||||||
pub use execution_context::LastErrorObjectError;
|
pub use execution_context::ErrorObjectError;
|
||||||
pub use lambda_applier::LambdaError;
|
pub use lambda_applier::LambdaError;
|
||||||
|
|
||||||
pub mod errors_prelude {
|
pub mod errors_prelude {
|
||||||
|
@ -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_ERROR_CODE;
|
||||||
pub use execution_step::execution_context::NO_ERROR_MESSAGE;
|
pub use execution_step::execution_context::NO_ERROR_MESSAGE;
|
||||||
pub use execution_step::CatchableError;
|
pub use execution_step::CatchableError;
|
||||||
|
pub use execution_step::ErrorObjectError;
|
||||||
pub use execution_step::ExecutionError;
|
pub use execution_step::ExecutionError;
|
||||||
pub use execution_step::LambdaError;
|
pub use execution_step::LambdaError;
|
||||||
pub use execution_step::LastErrorObjectError;
|
|
||||||
pub use execution_step::UncatchableError;
|
pub use execution_step::UncatchableError;
|
||||||
pub use farewell_step::FarewellError;
|
pub use farewell_step::FarewellError;
|
||||||
pub use polyplets::ResolvedTriplet;
|
pub use polyplets::ResolvedTriplet;
|
||||||
|
73
air/tests/test_module/features/errors/error.rs
Normal file
73
air/tests/test_module/features/errors/error.rs
Normal 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,);
|
||||||
|
}
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
use air::no_error_object;
|
use air::no_error_object;
|
||||||
use air::CatchableError;
|
use air::CatchableError;
|
||||||
|
use air::ErrorObjectError;
|
||||||
use air::ExecutionCidState;
|
use air::ExecutionCidState;
|
||||||
use air::ExecutionError;
|
use air::ExecutionError;
|
||||||
use air::LambdaError;
|
use air::LambdaError;
|
||||||
use air::LastErrorObjectError;
|
|
||||||
use air::SecurityTetraplet;
|
use air::SecurityTetraplet;
|
||||||
use air::NO_ERROR_ERROR_CODE;
|
use air::NO_ERROR_ERROR_CODE;
|
||||||
use air::NO_ERROR_MESSAGE;
|
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 result = call_vm!(vm, <_>::default(), &script, "", "");
|
||||||
|
|
||||||
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustContainField {
|
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustContainField {
|
||||||
scalar: service_result,
|
scalar: service_result,
|
||||||
field_name: "message",
|
field_name: "message",
|
||||||
});
|
});
|
||||||
@ -446,8 +446,7 @@ fn fail_with_scalar_from_call_not_right_type() {
|
|||||||
|
|
||||||
let result = call_vm!(vm, <_>::default(), &script, "", "");
|
let result = call_vm!(vm, <_>::default(), &script, "", "");
|
||||||
|
|
||||||
let expected_error =
|
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarMustBeObject(service_result));
|
||||||
CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustBeObject(service_result));
|
|
||||||
assert!(check_error(&result, expected_error));
|
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 result = call_vm!(vm, <_>::default(), &script, "", "");
|
||||||
|
|
||||||
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarFieldIsWrongType {
|
let expected_error = CatchableError::InvalidErrorObjectError(ErrorObjectError::ScalarFieldIsWrongType {
|
||||||
scalar: service_result,
|
scalar: service_result,
|
||||||
field_name: "error_code",
|
field_name: "error_code",
|
||||||
expected_type: "integer",
|
expected_type: "integer",
|
||||||
|
@ -14,5 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mod error;
|
||||||
mod invalid_air;
|
mod invalid_air;
|
||||||
mod last_error;
|
mod last_error;
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use air::CatchableError;
|
use air::CatchableError;
|
||||||
|
use air::ErrorObjectError;
|
||||||
|
use air::ExecutionError;
|
||||||
|
use air_test_framework::AirScriptExecutor;
|
||||||
use air_test_utils::prelude::*;
|
use air_test_utils::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -209,3 +212,58 @@ fn fail_with_canon_stream() {
|
|||||||
};
|
};
|
||||||
assert!(check_error(&result, expected_error));
|
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);
|
||||||
|
}
|
||||||
|
@ -86,7 +86,10 @@ Instr: Box<Instruction<'input>> = {
|
|||||||
Box::new(Instruction::New(new))
|
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))
|
Box::new(Instruction::Fail(fail_body))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,9 @@ pub enum ParserError {
|
|||||||
ap_key_type: String,
|
ap_key_type: String,
|
||||||
ap_result_name: String,
|
ap_result_name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("error code 0 with fail is unsupported")]
|
||||||
|
UnsupportedLiteralErrCodes { span: Span },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParserError {
|
impl ParserError {
|
||||||
@ -67,6 +70,7 @@ impl ParserError {
|
|||||||
Self::MultipleIterableValuesForOneIterator { span, .. } => *span,
|
Self::MultipleIterableValuesForOneIterator { span, .. } => *span,
|
||||||
Self::MultipleNextInFold { span, .. } => *span,
|
Self::MultipleNextInFold { span, .. } => *span,
|
||||||
Self::UnsupportedMapKeyType { span, .. } => *span,
|
Self::UnsupportedMapKeyType { span, .. } => *span,
|
||||||
|
Self::UnsupportedLiteralErrCodes { span } => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +120,10 @@ impl ParserError {
|
|||||||
ap_result_name: ap_result_name.into(),
|
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 {
|
impl From<std::convert::Infallible> for ParserError {
|
||||||
|
@ -92,6 +92,10 @@ pub(super) fn fail_last_error() -> Instruction<'static> {
|
|||||||
Instruction::Fail(Fail::LastError)
|
Instruction::Fail(Fail::LastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn fail_error() -> Instruction<'static> {
|
||||||
|
Instruction::Fail(Fail::Error)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn fold_scalar_variable<'i>(
|
pub(super) fn fold_scalar_variable<'i>(
|
||||||
scalar: Scalar<'i>,
|
scalar: Scalar<'i>,
|
||||||
iterator: Scalar<'i>,
|
iterator: Scalar<'i>,
|
||||||
|
@ -68,3 +68,47 @@ fn parse_fail_scalar_with_lambda() {
|
|||||||
));
|
));
|
||||||
assert_eq!(instruction, expected)
|
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 { .. }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
@ -59,9 +59,12 @@ pub struct VariableValidator<'i> {
|
|||||||
/// Contains all names that should be checked that they are not iterators.
|
/// Contains all names that should be checked that they are not iterators.
|
||||||
not_iterators_candidates: Vec<(&'i str, Span)>,
|
not_iterators_candidates: Vec<(&'i str, Span)>,
|
||||||
|
|
||||||
// This contains info about unssuported map key arguments used with ap instruction,
|
/// This contains info about unssuported map key arguments used with ap instruction,
|
||||||
// namely (key map ApArgument)
|
/// namely (key map ApArgument).
|
||||||
unsupported_map_keys: Vec<(String, &'i str, Span)>,
|
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> {
|
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>> {
|
pub(super) fn finalize(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
|
||||||
ValidatorErrorBuilder::new(self)
|
ValidatorErrorBuilder::new(self)
|
||||||
.check_undefined_variables()
|
.check_undefined_variables()
|
||||||
@ -205,6 +217,7 @@ impl<'i> VariableValidator<'i> {
|
|||||||
.check_new_on_iterators()
|
.check_new_on_iterators()
|
||||||
.check_iterator_for_multiple_definitions()
|
.check_iterator_for_multiple_definitions()
|
||||||
.check_for_unsupported_map_keys()
|
.check_for_unsupported_map_keys()
|
||||||
|
.check_for_unsupported_literal_errcodes()
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,6 +509,14 @@ impl<'i> ValidatorErrorBuilder<'i> {
|
|||||||
self
|
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>> {
|
fn build(self) -> Vec<ErrorRecovery<AirPos, Token<'i>, ParserError>> {
|
||||||
self.errors
|
self.errors
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user