From 5fabaf53d992379e456257b4f407e3acc91e3591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ron=C4=8Devi=C4=87?= Date: Thu, 15 Aug 2024 12:32:50 +0200 Subject: [PATCH 1/2] Fix `scwq` instruction and phantom error in purity checks --- .../book/src/blockchain-development/purity.md | 13 + sway-ast/src/assignable.rs | 1 + sway-core/src/ir_generation.rs | 1 + sway-core/src/ir_generation/compile.rs | 13 +- sway-core/src/ir_generation/function.rs | 15 +- sway-core/src/ir_generation/purity.rs | 176 +++++++++---- sway-core/src/metadata.rs | 234 ++++++++++++++---- .../ast_node/declaration/auto_impl.rs | 5 +- .../ast_node/declaration/function.rs | 4 - .../ast_node/declaration/trait_fn.rs | 2 +- .../typed_expression/function_application.rs | 9 +- .../typed_expression/method_application.rs | 18 +- .../semantic_analysis/type_check_context.rs | 28 +-- sway-error/src/error.rs | 126 +++++++++- sway-error/src/formatting.rs | 18 +- sway-ir/src/function.rs | 4 +- sway-ir/src/instruction.rs | 5 +- sway-ir/src/optimize/dce.rs | 5 +- .../abi_pure_calls_impure/Forc.lock | 9 +- .../abi_pure_calls_impure/Forc.toml | 3 + .../abi_pure_calls_impure/src/main.sw | 1 + .../abi_pure_calls_impure/test.toml | 9 +- .../contract_pure_calls_impure/Forc.lock | 5 + .../contract_pure_calls_impure/Forc.toml | 3 + .../contract_pure_calls_impure/src/main.sw | 6 +- .../contract_pure_calls_impure/test.toml | 11 +- .../Forc.lock | 9 +- .../Forc.toml | 3 + .../src/main.sw | 1 + .../test.toml | 12 +- .../impure_read_calls_impure_write/Forc.lock | 3 - .../impure_read_calls_impure_write/Forc.toml | 6 - .../src/main.sw | 12 - .../impure_read_calls_impure_write/test.toml | 3 - .../Forc.lock | 9 +- .../Forc.toml | 3 + .../src/main.sw | 15 +- .../test.toml | 12 +- .../should_fail/nested_impure/Forc.lock | 9 +- .../should_fail/nested_impure/Forc.toml | 3 + .../should_fail/nested_impure/src/main.sw | 17 +- .../should_fail/nested_impure/test.toml | 26 +- .../should_fail/pure_calls_impure/Forc.lock | 3 - .../should_fail/pure_calls_impure/Forc.toml | 6 - .../should_fail/pure_calls_impure/src/main.sw | 14 -- .../should_fail/pure_calls_impure/test.toml | 3 - .../Forc.lock | 8 + .../Forc.toml | 8 + .../src/main.sw | 74 ++++++ .../test.toml | 76 ++++++ .../Forc.lock | 7 +- .../Forc.toml | 2 +- .../src/main.sw | 2 +- .../test.toml | 10 +- .../should_fail/storage_conflict/Forc.lock | 13 - .../should_fail/storage_conflict/test.toml | 49 ---- .../storage_ops_in_library/Forc.lock | 9 +- .../storage_ops_in_library/Forc.toml | 4 + .../storage_ops_in_library/test.toml | 19 +- .../storage_purity_conflict/Forc.lock | 8 + .../Forc.toml | 4 +- .../src/main.sw | 6 +- .../storage_purity_conflict/test.toml | 131 ++++++++++ .../trait_pure_calls_impure/Forc.lock | 9 +- .../trait_pure_calls_impure/Forc.toml | 3 + .../trait_pure_calls_impure/src/main.sw | 22 ++ .../trait_pure_calls_impure/test.toml | 9 +- 67 files changed, 1027 insertions(+), 339 deletions(-) delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.lock delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.toml delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/src/main.sw delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/test.toml delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.lock delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.toml delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/src/main.sw delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/test.toml delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.lock delete mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.lock rename test/src/e2e_vm_tests/test_programs/should_fail/{storage_conflict => storage_purity_conflict}/Forc.toml (60%) rename test/src/e2e_vm_tests/test_programs/should_fail/{storage_conflict => storage_purity_conflict}/src/main.sw (95%) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/test.toml diff --git a/docs/book/src/blockchain-development/purity.md b/docs/book/src/blockchain-development/purity.md index b6ba55a54a8..35149ecfd74 100644 --- a/docs/book/src/blockchain-development/purity.md +++ b/docs/book/src/blockchain-development/purity.md @@ -5,6 +5,8 @@ A function is _pure_ if it does not access any [persistent storage](./storage.md). Conversely, the function is _impure_ if it does access any storage. Naturally, as storage is only available in smart contracts, impure functions cannot be used in predicates, scripts, or libraries. A pure function cannot call an impure function. In Sway, functions are pure by default but can be opted into impurity via the `storage` function attribute. The `storage` attribute may take `read` and/or `write` arguments indicating which type of access the function requires. + +The `storage` attribute without any arguments, `#[storage()]`, indicates a pure function, and has the same effect as not having the attribute at all. ```sway @@ -17,6 +19,15 @@ fn get_amount() -> u64 { fn increment_amount(increment: u64) -> u64 { ... } + +fn a_pure_function() { + ... +} + +#[storage()] +fn also_a_pure_function() { + ... +} ``` > **Note**: the `#[storage(write)]` attribute also permits a function to read from storage. This is due to the fact that partially writing a storage slot requires first reading the slot. @@ -31,6 +42,8 @@ The `storage` attribute may also be applied to [methods and associated functions A pure function gives you some guarantees: you will not incur excessive storage gas costs, the compiler can apply additional optimizations, and they are generally easy to reason about and audit. + +> **Note**: Purity does not provide an absolute guarantee that a storage access will not happen as a result of calling a pure function. E.g., it is possible for a pure function to call another contract, which can then call a write function in the original contract. The guarantee that the purity gives in this example is, that the original pure function itself does not change the storage, as well as that any function later called, that accesses storage, is clearly marked as impure. [A similar concept exists in Solidity](https://docs.soliditylang.org/en/v0.8.10/contracts.html#pure-functions). Note that Solidity refers to contract storage as _contract state_, and in the Sway/Fuel ecosystem, these two terms are largely interchangeable. diff --git a/sway-ast/src/assignable.rs b/sway-ast/src/assignable.rs index 0eedcc3bf45..a6a07eb8a9b 100644 --- a/sway-ast/src/assignable.rs +++ b/sway-ast/src/assignable.rs @@ -7,6 +7,7 @@ pub enum Assignable { /// E.g.: /// - `my_variable` /// - `array[0].field.x.1` + /// /// Note that within the path, we cannot have dereferencing /// (except, of course, in expressions inside of array index operator). /// This is guaranteed by the grammar. diff --git a/sway-core/src/ir_generation.rs b/sway-core/src/ir_generation.rs index 0ce7cf5e075..dc08281c82e 100644 --- a/sway-core/src/ir_generation.rs +++ b/sway-core/src/ir_generation.rs @@ -93,6 +93,7 @@ impl CompiledFunctionCache { md_mgr, module, &callee_fn_decl, + &decl.name, logged_types_map, messages_types_map, is_entry, diff --git a/sway-core/src/ir_generation/compile.rs b/sway-core/src/ir_generation/compile.rs index c608b9ae911..135b398ca74 100644 --- a/sway-core/src/ir_generation/compile.rs +++ b/sway-core/src/ir_generation/compile.rs @@ -18,7 +18,7 @@ use super::{ use sway_error::{error::CompileError, handler::Handler}; use sway_ir::{metadata::combine as md_combine, *}; -use sway_types::Spanned; +use sway_types::{Ident, Spanned}; use std::{collections::HashMap, sync::Arc}; @@ -390,6 +390,7 @@ pub(super) fn compile_function( md_mgr: &mut MetadataManager, module: Module, ast_fn_decl: &ty::TyFunctionDecl, + original_name: &Ident, logged_types_map: &HashMap, messages_types_map: &HashMap, is_entry: bool, @@ -408,6 +409,7 @@ pub(super) fn compile_function( md_mgr, module, ast_fn_decl, + original_name, is_entry, is_original_entry, None, @@ -443,6 +445,7 @@ pub(super) fn compile_entry_function( md_mgr, module, &ast_fn_decl, + &ast_fn_decl.name, logged_types_map, messages_types_map, is_entry, @@ -489,6 +492,11 @@ fn compile_fn( md_mgr: &mut MetadataManager, module: Module, ast_fn_decl: &ty::TyFunctionDecl, + // Original function name, before it is postfixed with + // a number, to get a unique name. + // The span in the name must point to the name in the + // function declaration. + original_name: &Ident, is_entry: bool, is_original_entry: bool, selector: Option<[u8; 4]>, @@ -567,7 +575,9 @@ fn compile_fn( let span_md_idx = md_mgr.span_to_md(context, span); let storage_md_idx = md_mgr.purity_to_md(context, *purity); + let name_span_md_idx = md_mgr.fn_name_span_to_md(context, original_name); let mut metadata = md_combine(context, &span_md_idx, &storage_md_idx); + metadata = md_combine(context, &metadata, &name_span_md_idx); let decl_index = test_decl_ref.map(|decl_ref| *decl_ref.id()); if let Some(decl_index) = decl_index { @@ -688,6 +698,7 @@ fn compile_abi_method( md_mgr, module, &ast_fn_decl, + &ast_fn_decl.name, // ABI methods are only entries when the "new encoding" is off !context.experimental.new_encoding, // ABI methods are always original entries diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index 9e6a265b589..7c333f55185 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -523,7 +523,14 @@ impl<'eng> FnCompiler<'eng> { ) } else { let function_decl = self.engines.de().get_function(fn_ref); - self.compile_fn_call(context, md_mgr, arguments, &function_decl, span_md_idx) + self.compile_fn_call( + context, + md_mgr, + arguments, + &function_decl, + span_md_idx, + name, + ) } } ty::TyExpressionVariant::LazyOperator { op, lhs, rhs } => { @@ -2868,6 +2875,7 @@ impl<'eng> FnCompiler<'eng> { ast_args: &[(Ident, ty::TyExpression)], callee: &ty::TyFunctionDecl, span_md_idx: Option, + call_path: &CallPath, ) -> Result { let new_callee = self.cache.ty_function_decl_to_unique_function( self.engines, @@ -2893,11 +2901,14 @@ impl<'eng> FnCompiler<'eng> { args.push(arg); } + let call_path_span_md_idx = md_mgr.fn_call_path_span_to_md(context, call_path); + let md_idx = combine(context, &span_md_idx, &call_path_span_md_idx); + let val = self .current_block .append(context) .call(new_callee, &args) - .add_metadatum(context, span_md_idx); + .add_metadatum(context, md_idx); Ok(TerminatorValue::new(val, context)) } diff --git a/sway-core/src/ir_generation/purity.rs b/sway-core/src/ir_generation/purity.rs index 487a982c551..79d61df830f 100644 --- a/sway-core/src/ir_generation/purity.rs +++ b/sway-core/src/ir_generation/purity.rs @@ -3,11 +3,14 @@ use crate::{ promote_purity, Purity::{self, *}, }, - metadata::{MetadataManager, StorageOperation}, + metadata::MetadataManager, }; -use sway_error::warning::{CompileWarning, Warning}; use sway_error::{error::CompileError, handler::Handler}; +use sway_error::{ + error::StorageAccess, + warning::{CompileWarning, Warning}, +}; use sway_ir::{Context, FuelVmInstruction, Function, InstOp}; use sway_types::span::Span; @@ -34,8 +37,11 @@ pub(crate) fn check_function_purity( // Iterate for each instruction in the function and gather whether we have read and/or // write storage operations: // - via the storage IR instructions, - // - via ASM blocks with storage VM instructions or + // - via ASM blocks with storage VM instructions, or // - via calls into functions with the above. + let attributed_purity = md_mgr.md_to_purity(context, function.get_metadata(context)); + + let mut storage_access_violations = vec![]; let (reads, writes) = function.instruction_iter(context).fold( (false, false), |(reads, writes), (_block, ins_value)| { @@ -43,27 +49,53 @@ pub(crate) fn check_function_purity( .get_instruction(context) .map(|instruction| { match &instruction.op { - InstOp::FuelVm(FuelVmInstruction::StateLoadQuadWord { .. }) - | InstOp::FuelVm(FuelVmInstruction::StateLoadWord(_)) => (true, writes), + InstOp::FuelVm(inst) if is_store_access_fuel_vm_instruction(inst) => { + let storage_access = store_access_fuel_vm_instruction_to_storage_access(inst); + if violates_purity(&storage_access, &attributed_purity) { + // When compiling Sway code, the only way to get FuelVM store access instructions in the IR + // is via store access intrinsics. So we know that the span stored in the metadata will be + // the intrinsic's call span which is suitable for error reporting. + let intrinsic_call_span = md_mgr.md_to_span(context, ins_value.get_metadata(context)).unwrap_or(Span::dummy()); + storage_access_violations.push((intrinsic_call_span, storage_access)); + } - InstOp::FuelVm(FuelVmInstruction::StateClear { .. }) - | InstOp::FuelVm(FuelVmInstruction::StateStoreQuadWord { .. }) - | InstOp::FuelVm(FuelVmInstruction::StateStoreWord { .. }) => (reads, true), + match inst { + FuelVmInstruction::StateLoadQuadWord { .. } + | FuelVmInstruction::StateLoadWord(_) => (true, writes), + FuelVmInstruction::StateClear { .. } + | FuelVmInstruction::StateStoreQuadWord { .. } + | FuelVmInstruction::StateStoreWord { .. } => (reads, true), + _ => unreachable!("The FuelVM instruction is checked to be a store access instruction."), + } + } // Iterate for and check each instruction in the ASM block. InstOp::AsmBlock(asm_block, _args) => asm_block.body.iter().fold( (reads, writes), - |(reads, writes), asm_op| match asm_op.op_name.as_str() { - "scwq" | "srw" | "srwq" => (true, writes), - "sww" | "swwq" => (reads, true), - _ => (reads, writes), - }, + |(reads, writes), asm_op| { + let inst = asm_op.op_name.as_str(); + if is_store_access_asm_instruction(inst) { + let storage_access = store_access_asm_instruction_to_storage_access(inst); + if violates_purity(&storage_access, &attributed_purity) { + let asm_inst_span = md_mgr.md_to_span(context, asm_op.metadata).unwrap_or(Span::dummy()); + storage_access_violations.push((asm_inst_span, storage_access)); + } + + match inst { + "srw" | "srwq" => (true, writes), + "scwq" | "sww" | "swwq" => (reads, true), + _ => unreachable!("The ASM instruction is checked to be a store access instruction."), + } + } else { + (reads, writes) + } + } ), // Recurse to find the called function purity. Use memoisation to // avoid redoing work. InstOp::Call(callee, _args) => { - let (called_fn_reads, called_fn_writes) = + let (callee_reads, callee_writes) = env.memos.get(callee).copied().unwrap_or_else(|| { let r_w = check_function_purity( handler, env, context, md_mgr, callee, @@ -71,7 +103,14 @@ pub(crate) fn check_function_purity( env.memos.insert(*callee, r_w); r_w }); - (reads || called_fn_reads, writes || called_fn_writes) + if callee_reads || callee_writes { + let callee_span = md_mgr.md_to_fn_call_path_span(context, ins_value.get_metadata(context)).unwrap_or(Span::dummy()); + let storage_access = StorageAccess::ImpureFunctionCall(callee_span.clone(), callee_reads, callee_writes); + if violates_purity(&storage_access, &attributed_purity) { + storage_access_violations.push((callee_span, storage_access)); + } + } + (reads || callee_reads, writes || callee_writes) } _otherwise => (reads, writes), @@ -81,19 +120,21 @@ pub(crate) fn check_function_purity( }, ); - let attributed_purity = md_mgr.md_to_storage_op(context, function.get_metadata(context)); - let span = md_mgr - .md_to_span(context, function.get_metadata(context)) - .unwrap_or_else(Span::dummy); - // Simple closures for each of the error types. - let error = |span, storage_op, existing, needed| { - handler.emit_err(CompileError::ImpureInPureContext { - storage_op, - attrs: promote_purity(existing, needed).to_attribute_syntax(), - span, - }); + let error = |span: Span, needed| { + // We don't emit errors on the generated `__entry` function + // but do on the original entry functions and all other functions. + if !function.is_entry(context) || function.is_original_entry(context) { + handler.emit_err(CompileError::StorageAccessMismatched { + span, + is_pure: matches!(attributed_purity, Pure), + suggested_attributes: promote_purity(attributed_purity, needed) + .to_attribute_syntax(), + storage_access_violations, + }); + } }; + let warn = |span, purity: Purity| { // Do not warn on generated code if span != Span::dummy() { @@ -106,28 +147,79 @@ pub(crate) fn check_function_purity( } }; + let span = md_mgr + .md_to_fn_name_span(context, function.get_metadata(context)) + .unwrap_or_else(Span::dummy); + match (attributed_purity, reads, writes) { // Has no attributes but needs some. - (None, true, false) => error(span, "read", Pure, Reads), - (None, false, true) => error(span, "write", Pure, Writes), - (None, true, true) => error(span, "read & write", Pure, ReadsWrites), + (Pure, true, false) => error(span, Reads), + (Pure, false, true) => error(span, Writes), + (Pure, true, true) => error(span, ReadsWrites), - // Or the attribute must match the behaviour. - (Some(StorageOperation::Reads), _, true) => error(span, "write", Reads, Writes), + // Or the attribute must match the behavior. + (Reads, _, true) => error(span, Writes), // Or we have unneeded attributes. - (Some(StorageOperation::ReadsWrites), false, true) => warn(span, Reads), - (Some(StorageOperation::ReadsWrites), true, false) => warn(span, Writes), - (Some(StorageOperation::ReadsWrites), false, false) => warn(span, ReadsWrites), - (Some(StorageOperation::Reads), false, false) => warn(span, Reads), - (Some(StorageOperation::Writes), _, false) => warn(span, Writes), - - // Attributes and effects are in total agreement - (None, false, false) - | (Some(StorageOperation::Reads), true, false) - | (Some(StorageOperation::Writes), _, true) // storage(write) allows reading as well - | (Some(StorageOperation::ReadsWrites), true, true) => (), + (ReadsWrites, false, true) => warn(span, Reads), + (ReadsWrites, true, false) => warn(span, Writes), + (ReadsWrites, false, false) => warn(span, ReadsWrites), + (Reads, false, false) => warn(span, Reads), + (Writes, _, false) => warn(span, Writes), + + // Attributes and effects are in total agreement. + (Pure, false, false) + | (Reads, true, false) + | (Writes, _, true) // storage(write) allows reading as well + | (ReadsWrites, true, true) => (), }; (reads, writes) } + +fn is_store_access_fuel_vm_instruction(inst: &FuelVmInstruction) -> bool { + matches!( + inst, + FuelVmInstruction::StateLoadWord(_) + | FuelVmInstruction::StateLoadQuadWord { .. } + | FuelVmInstruction::StateClear { .. } + | FuelVmInstruction::StateStoreWord { .. } + | FuelVmInstruction::StateStoreQuadWord { .. } + ) +} + +fn store_access_fuel_vm_instruction_to_storage_access(inst: &FuelVmInstruction) -> StorageAccess { + match inst { + FuelVmInstruction::StateLoadWord(_) => StorageAccess::ReadWord, + FuelVmInstruction::StateLoadQuadWord { .. } => StorageAccess::ReadSlots, + FuelVmInstruction::StateClear { .. } => StorageAccess::Clear, + FuelVmInstruction::StateStoreWord { .. } => StorageAccess::WriteWord, + FuelVmInstruction::StateStoreQuadWord { .. } => StorageAccess::WriteSlots, + _ => panic!("The FuelVM instruction is not a store access instruction."), + } +} + +fn is_store_access_asm_instruction(inst: &str) -> bool { + matches!(inst, "srw" | "srwq" | "scwq" | "sww" | "swwq") +} + +fn store_access_asm_instruction_to_storage_access(inst: &str) -> StorageAccess { + match inst { + "srw" => StorageAccess::ReadWord, + "srwq" => StorageAccess::ReadSlots, + "scwq" => StorageAccess::Clear, + "sww" => StorageAccess::WriteWord, + "swwq" => StorageAccess::WriteSlots, + _ => panic!("The ASM instruction \"{inst}\" is not a store access instruction."), + } +} + +/// Returns true if the `storage_access` violates the given expected `purity`. +fn violates_purity(storage_access: &StorageAccess, purity: &Purity) -> bool { + match purity { + Pure => true, + Reads => storage_access.is_write(), + Writes => false, + ReadsWrites => false, + } +} diff --git a/sway-core/src/metadata.rs b/sway-core/src/metadata.rs index b55d03ed1a7..86da8c72ff4 100644 --- a/sway-core/src/metadata.rs +++ b/sway-core/src/metadata.rs @@ -1,43 +1,48 @@ use crate::{ decl_engine::DeclId, - language::{ty::TyFunctionDecl, Inline, Purity}, + language::{ty::TyFunctionDecl, CallPath, Inline, Purity}, }; use sway_ir::{Context, MetadataIndex, Metadatum, Value}; -use sway_types::{SourceId, Span}; +use sway_types::{Ident, SourceId, Span, Spanned}; use std::{collections::HashMap, path::PathBuf, sync::Arc}; -/// IR metadata needs to be consistent between IR generation (converting Spans, etc. to metadata) +/// IR metadata needs to be consistent between IR generation (converting [Span]s, etc. to metadata) /// and ASM generation (converting the metadata back again). Here we consolidate all of /// `sway-core`s metadata needs into a single place to enable that consistency. /// -/// The [`MetadataManager`] also does its best to reduce redundancy by caching certain common +/// The [MetadataManager] also does its best to reduce redundancy by caching certain common /// elements, such as source paths and storage attributes, and to avoid recreating the same /// indices repeatedly. #[derive(Default)] pub(crate) struct MetadataManager { + // We want to be able to store more then one `Span` per `MetadataIndex`. + // E.g., storing the span of the function name, and the whole function declaration. + // The spans differ then by the tag property of their `Metadatum::Struct`. + // We could cache all such spans in a single `HashMap` where the key would be (Span, tag). + // But since the vast majority of stored spans will be tagged with the generic "span" tag, + // and only a few elements will have additional spans in their `MetadataIndex`, it is + // more efficient to provide two separate caches, one for the spans tagged with "span", + // and one for all other spans, tagged with arbitrary tags. + /// Holds [Span]s tagged with "span". md_span_cache: HashMap, + /// Holds [Span]s tagged with an arbitrary tag. + md_tagged_span_cache: HashMap, md_file_loc_cache: HashMap, Arc)>, - md_storage_op_cache: HashMap, + md_purity_cache: HashMap, md_inline_cache: HashMap, md_test_decl_index_cache: HashMap>, span_md_cache: HashMap, + tagged_span_md_cache: HashMap<(Span, &'static str), MetadataIndex>, file_loc_md_cache: HashMap, - storage_op_md_cache: HashMap, + purity_md_cache: HashMap, inline_md_cache: HashMap, test_decl_index_md_cache: HashMap, MetadataIndex>, } -#[derive(Clone, Copy)] -pub(crate) enum StorageOperation { - Reads, - Writes, - ReadsWrites, -} - impl MetadataManager { pub(crate) fn md_to_span( &mut self, @@ -46,26 +51,69 @@ impl MetadataManager { ) -> Option { Self::for_each_md_idx(context, md_idx, |md_idx| { self.md_span_cache.get(&md_idx).cloned().or_else(|| { - // Create a new span and save it in the cache. md_idx .get_content(context) .unwrap_struct("span", 3) .and_then(|fields| { - let (path, src) = self.md_to_file_location(context, &fields[0])?; - let start = fields[1].unwrap_integer()?; - let end = fields[2].unwrap_integer()?; - let source_engine = context.source_engine(); - let source_id = source_engine.get_source_id(&path); - let span = Span::new(src, start as usize, end as usize, Some(source_id))?; - + // Create a new span and save it in the cache. + let span = self.create_span_from_metadatum_fields(context, fields)?; self.md_span_cache.insert(md_idx, span.clone()); - Some(span) }) }) }) } + /// Returns the [Span] tagged with `tag` from the `md_idx`, + /// or `None` if such span does not exist. + /// If there are more spans tagged with `tag` inside of the + /// `md_idx`, the first one will be returned. + pub(crate) fn md_to_tagged_span( + &mut self, + context: &Context, + md_idx: Option, + tag: &'static str, + ) -> Option { + Self::for_each_md_idx(context, md_idx, |md_idx| { + let fields = md_idx.get_content(context).unwrap_struct(tag, 3); + + match fields { + Some(fields) => self + .md_tagged_span_cache + .get(&md_idx) + .map(|span_and_tag| span_and_tag.0.clone()) + .or_else(|| { + // Create a new span and save it in the cache. + let span = self.create_span_from_metadatum_fields(context, fields)?; + self.md_tagged_span_cache + .insert(md_idx, (span.clone(), tag)); + Some(span) + }), + None => None, + } + }) + } + + /// Returns the [Span] pointing to the function name in the function declaration from the `md_idx`, + /// or `None` if such span does not exist. + pub(crate) fn md_to_fn_name_span( + &mut self, + context: &Context, + md_idx: Option, + ) -> Option { + self.md_to_tagged_span(context, md_idx, "fn_name_span") + } + + /// Returns the [Span] pointing to the call path in the function call from the `md_idx`, + /// or `None` if such span does not exist. + pub(crate) fn md_to_fn_call_path_span( + &mut self, + context: &Context, + md_idx: Option, + ) -> Option { + self.md_to_tagged_span(context, md_idx, "fn_call_path_span") + } + pub(crate) fn md_to_test_decl_index( &mut self, context: &Context, @@ -91,33 +139,37 @@ impl MetadataManager { }) } - pub(crate) fn md_to_storage_op( + pub(crate) fn md_to_purity( &mut self, context: &Context, md_idx: Option, - ) -> Option { + ) -> Purity { + // If the purity metadata is not available, we assume the function to + // be pure, because in the case of a pure function, we do not store + // its purity attribute, to avoid bloating the metadata. Self::for_each_md_idx(context, md_idx, |md_idx| { - self.md_storage_op_cache.get(&md_idx).copied().or_else(|| { - // Create a new storage op and save it in the cache. + self.md_purity_cache.get(&md_idx).copied().or_else(|| { + // Create a new purity and save it in the cache. md_idx .get_content(context) - .unwrap_struct("storage", 1) + .unwrap_struct("purity", 1) .and_then(|fields| { - fields[0].unwrap_string().and_then(|stor_str| { - let op = match stor_str { - "reads" => Some(StorageOperation::Reads), - "writes" => Some(StorageOperation::Writes), - "readswrites" => Some(StorageOperation::ReadsWrites), - _otherwise => None, + fields[0].unwrap_string().and_then(|purity_str| { + let purity = match purity_str { + "reads" => Some(Purity::Reads), + "writes" => Some(Purity::Writes), + "readswrites" => Some(Purity::ReadsWrites), + _otherwise => panic!("Invalid purity metadata: {purity_str}."), }?; - self.md_storage_op_cache.insert(md_idx, op); + self.md_purity_cache.insert(md_idx, purity); - Some(op) + Some(purity) }) }) }) }) + .unwrap_or(Purity::Pure) } /// Gets Inline information from metadata index. @@ -189,25 +241,69 @@ impl MetadataManager { ) -> Option { self.span_md_cache.get(span).copied().or_else(|| { span.source_id().and_then(|source_id| { - // Create new metadata. - let file_location_md_idx = self.file_location_to_md(context, *source_id)?; - let md_idx = MetadataIndex::new_struct( - context, - "span", - vec![ - Metadatum::Index(file_location_md_idx), - Metadatum::Integer(span.start() as u64), - Metadatum::Integer(span.end() as u64), - ], - ); - + let md_idx = self.create_metadata_from_span(context, source_id, span, "span")?; self.span_md_cache.insert(span.clone(), md_idx); - Some(md_idx) }) }) } + /// Returns [MetadataIndex] with [Metadatum::Struct] tagged with `tag` + /// whose content will be the provided `span`. + /// + /// If the `span` does not have [Span::source_id], `None` is returned. + /// + /// This [Span] can later be retrieved from the [MetadataIndex] by calling + /// [Self::md_to_tagged_span]. + pub(crate) fn tagged_span_to_md( + &mut self, + context: &mut Context, + span: &Span, + tag: &'static str, + ) -> Option { + let span_and_tag = (span.clone(), tag); + self.tagged_span_md_cache + .get(&span_and_tag) + .copied() + .or_else(|| { + span.source_id().and_then(|source_id| { + let md_idx = self.create_metadata_from_span(context, source_id, span, tag)?; + self.tagged_span_md_cache.insert(span_and_tag, md_idx); + Some(md_idx) + }) + }) + } + + /// Returns [MetadataIndex] with [Metadatum::Struct] + /// whose content will be the [Span] of the `fn_name` [Ident]. + /// + /// If that span does not have [Span::source_id], `None` is returned. + /// + /// This [Span] can later be retrieved from the [MetadataIndex] by calling + /// [Self::md_to_fn_name_span]. + pub(crate) fn fn_name_span_to_md( + &mut self, + context: &mut Context, + fn_name: &Ident, + ) -> Option { + self.tagged_span_to_md(context, &fn_name.span(), "fn_name_span") + } + + /// Returns [MetadataIndex] with [Metadatum::Struct] + /// whose content will be the [Span] of the `call_path`. + /// + /// If that span does not have [Span::source_id], `None` is returned. + /// + /// This [Span] can later be retrieved from the [MetadataIndex] by calling + /// [Self::md_to_fn_call_path_span]. + pub(crate) fn fn_call_path_span_to_md( + &mut self, + context: &mut Context, + call_path: &CallPath, + ) -> Option { + self.tagged_span_to_md(context, &call_path.span(), "fn_call_path_span") + } + pub(crate) fn test_decl_index_to_md( &mut self, context: &mut Context, @@ -233,8 +329,10 @@ impl MetadataManager { context: &mut Context, purity: Purity, ) -> Option { + // If the function is pure, we do not store the purity attribute, + // to avoid bloating the metadata. (purity != Purity::Pure).then(|| { - self.storage_op_md_cache + self.purity_md_cache .get(&purity) .copied() .unwrap_or_else(|| { @@ -247,11 +345,11 @@ impl MetadataManager { }; let md_idx = MetadataIndex::new_struct( context, - "storage", + "purity", vec![Metadatum::String(field.to_owned())], ); - self.storage_op_md_cache.insert(purity, md_idx); + self.purity_md_cache.insert(purity, md_idx); md_idx }) @@ -314,4 +412,38 @@ impl MetadataManager { } }) } + + fn create_span_from_metadatum_fields( + &mut self, + context: &Context, + fields: &[Metadatum], + ) -> Option { + let (path, src) = self.md_to_file_location(context, &fields[0])?; + let start = fields[1].unwrap_integer()?; + let end = fields[2].unwrap_integer()?; + let source_engine = context.source_engine(); + let source_id = source_engine.get_source_id(&path); + let span = Span::new(src, start as usize, end as usize, Some(source_id))?; + Some(span) + } + + fn create_metadata_from_span( + &mut self, + context: &mut Context, + source_id: &SourceId, + span: &Span, + tag: &'static str, + ) -> Option { + let file_location_md_idx = self.file_location_to_md(context, *source_id)?; + let md_idx = MetadataIndex::new_struct( + context, + tag, + vec![ + Metadatum::Index(file_location_md_idx), + Metadatum::Integer(span.start() as u64), + Metadatum::Integer(span.end() as u64), + ], + ); + Some(md_idx) + } } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs index 871fc8b8d7c..7d31ce74e83 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs @@ -679,13 +679,12 @@ where format!("__revert({});", MISMATCHED_SELECTOR_REVERT_CODE) }; - let att: String = match (reads, writes) { + let att = match (reads, writes) { (true, true) => "#[storage(read, write)]", (true, false) => "#[storage(read)]", (false, true) => "#[storage(write)]", (false, false) => "", - } - .into(); + }; let code = format!( "{att} pub fn __entry() {{ diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 45132000b9b..f5b9b8bb00e 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -83,7 +83,6 @@ impl ty::TyFunctionDecl { // create a namespace for the function ctx.by_ref() - .with_purity(*purity) .with_const_shadowing_mode(ConstShadowingMode::Sequential) .disallow_functions() .scoped(|mut ctx| { @@ -181,7 +180,6 @@ impl ty::TyFunctionDecl { ) -> Result { // create a namespace for the function ctx.by_ref() - .with_purity(ty_fn_decl.purity) .with_const_shadowing_mode(ConstShadowingMode::Sequential) .disallow_functions() .scoped(|mut ctx| { @@ -189,7 +187,6 @@ impl ty::TyFunctionDecl { let ty::TyFunctionDecl { parameters, - purity, return_type, type_parameters, .. @@ -212,7 +209,6 @@ impl ty::TyFunctionDecl { let mut ctx = ctx .by_ref() - .with_purity(*purity) .with_help_text( "Function body's return type does not match up with its return type annotation.", ) diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs index 453eed4b7ac..241fb856db7 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs @@ -31,7 +31,7 @@ impl ty::TyTraitFn { let engines = ctx.engines(); // Create a namespace for the trait function. - ctx.by_ref().with_purity(*purity).scoped(|mut ctx| { + ctx.by_ref().scoped(|mut ctx| { // TODO: when we add type parameters to trait fns, type check them here // Type check the parameters. diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/function_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/function_application.rs index ae526f6ef15..cf39c305572 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/function_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/function_application.rs @@ -39,15 +39,8 @@ pub(crate) fn instantiate_function_application( }), ); } - let arguments = arguments.unwrap_or_default(); - // 'purity' is that of the callee, 'opts.purity' of the caller. - if !ctx.purity().can_call(function_decl.purity) { - handler.emit_err(CompileError::StorageAccessMismatch { - attrs: promote_purity(ctx.purity(), function_decl.purity).to_attribute_syntax(), - span: call_path_binding.span(), - }); - } + let arguments = arguments.unwrap_or_default(); // check that the number of parameters and the number of the arguments is the same check_function_arguments_arity( diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 99c4dea63bf..ea83cd08847 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -159,20 +159,10 @@ pub(crate) fn type_check_method_application( })); } - // check the function storage purity - if !method.is_contract_call { - // 'method.purity' is that of the callee, 'opts.purity' of the caller. - if !ctx.purity().can_call(method.purity) { - handler.emit_err(CompileError::StorageAccessMismatch { - attrs: promote_purity(ctx.purity(), method.purity).to_attribute_syntax(), - span: method_name_binding.inner.easy_name().span(), - }); - } - if !contract_call_params.is_empty() { - handler.emit_err(CompileError::CallParamForNonContractCallMethod { - span: contract_call_params[0].name.span(), - }); - } + if !method.is_contract_call && !contract_call_params.is_empty() { + handler.emit_err(CompileError::CallParamForNonContractCallMethod { + span: contract_call_params[0].name.span(), + }); } // generate the map of the contract call params diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index dc1d4661e98..b0a5029d4f2 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -7,7 +7,7 @@ use crate::{ language::{ parsed::TreeType, ty::{self, TyDecl, TyTraitItem}, - CallPath, Purity, QualifiedCallPath, Visibility, + CallPath, QualifiedCallPath, Visibility, }, namespace::{ IsExtendingExistingImpl, IsImplSelf, ModulePath, ResolvedDeclaration, @@ -43,6 +43,9 @@ pub struct TypeCheckContext<'a> { pub(crate) engines: &'a Engines, + /// Set of experimental flags. + pub(crate) experimental: ExperimentalFlags, + // The following set of fields are intentionally private. When a `TypeCheckContext` is passed // into a new node during type checking, these fields should be updated using the `with_*` // methods which provides a new `TypeCheckContext`, ensuring we don't leak our changes into @@ -80,9 +83,6 @@ pub struct TypeCheckContext<'a> { // TODO: We probably shouldn't carry this through the `Context`, but instead pass it directly // to `unify` as necessary? help_text: &'static str, - /// Tracks the purity of the context, e.g. whether or not we should be allowed to write to - /// storage. - purity: Purity, /// Provides the kind of the module. /// This is useful for example to throw an error when while loops are present in predicates. kind: TreeType, @@ -94,9 +94,6 @@ pub struct TypeCheckContext<'a> { /// Indicates when semantic analysis is type checking storage declaration. storage_declaration: bool, - - /// Set of experimental flags - pub experimental: ExperimentalFlags, } impl<'a> TypeCheckContext<'a> { @@ -118,7 +115,6 @@ impl<'a> TypeCheckContext<'a> { abi_mode: AbiMode::NonAbi, const_shadowing_mode: ConstShadowingMode::ItemStyle, generic_shadowing_mode: GenericShadowingMode::Disallow, - purity: Purity::default(), kind: TreeType::Contract, disallow_functions: false, storage_declaration: false, @@ -133,7 +129,6 @@ impl<'a> TypeCheckContext<'a> { /// - type_annotation: unknown /// - mode: NoneAbi /// - help_text: "" - /// - purity: Pure pub fn from_root( root_namespace: &'a mut Namespace, engines: &'a Engines, @@ -159,7 +154,6 @@ impl<'a> TypeCheckContext<'a> { abi_mode: AbiMode::NonAbi, const_shadowing_mode: ConstShadowingMode::ItemStyle, generic_shadowing_mode: GenericShadowingMode::Disallow, - purity: Purity::default(), kind: TreeType::Contract, disallow_functions: false, storage_declaration: false, @@ -187,7 +181,6 @@ impl<'a> TypeCheckContext<'a> { const_shadowing_mode: self.const_shadowing_mode, generic_shadowing_mode: self.generic_shadowing_mode, help_text: self.help_text, - purity: self.purity, kind: self.kind, engines: self.engines, disallow_functions: self.disallow_functions, @@ -213,7 +206,6 @@ impl<'a> TypeCheckContext<'a> { const_shadowing_mode: self.const_shadowing_mode, generic_shadowing_mode: self.generic_shadowing_mode, help_text: self.help_text, - purity: self.purity, kind: self.kind, engines: self.engines, disallow_functions: self.disallow_functions, @@ -240,7 +232,6 @@ impl<'a> TypeCheckContext<'a> { const_shadowing_mode: self.const_shadowing_mode, generic_shadowing_mode: self.generic_shadowing_mode, help_text: self.help_text, - purity: self.purity, kind: self.kind, engines: self.engines, disallow_functions: self.disallow_functions, @@ -347,17 +338,12 @@ impl<'a> TypeCheckContext<'a> { } } - /// Map this `TypeCheckContext` instance to a new one with the given purity. - pub(crate) fn with_purity(self, purity: Purity) -> Self { - Self { purity, ..self } - } - /// Map this `TypeCheckContext` instance to a new one with the given module kind. pub(crate) fn with_kind(self, kind: TreeType) -> Self { Self { kind, ..self } } - /// Map this `TypeCheckContext` instance to a new one with the given purity. + /// Map this `TypeCheckContext` instance to a new one with the given self type. pub(crate) fn with_self_type(self, self_type: Option) -> Self { Self { self_type, ..self } } @@ -420,10 +406,6 @@ impl<'a> TypeCheckContext<'a> { self.abi_mode.clone() } - pub(crate) fn purity(&self) -> Purity { - self.purity - } - #[allow(dead_code)] pub(crate) fn kind(&self) -> TreeType { self.kind diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index de771f32aec..2131c63f238 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -781,11 +781,6 @@ pub enum CompileError { supported_types_message: Vec<&'static str>, span: Span, }, - #[error( - "Storage attribute access mismatch. Try giving the surrounding function more access by \ - adding \"#[{STORAGE_PURITY_ATTRIBUTE_NAME}({attrs})]\" to the function declaration." - )] - StorageAccessMismatch { attrs: String, span: Span }, #[error( "The function \"{fn_name}\" in {interface_name} is pure, but this \ implementation is not. The \"{STORAGE_PURITY_ATTRIBUTE_NAME}\" annotation must be \ @@ -811,13 +806,17 @@ pub enum CompileError { #[error("Impure function inside of non-contract. Contract storage is only accessible from contracts.")] ImpureInNonContract { span: Span }, #[error( - "This function performs a storage {storage_op} but does not have the required \ - attribute(s). Try adding \"#[{STORAGE_PURITY_ATTRIBUTE_NAME}({attrs})]\" to the function \ + "This function performs storage access but does not have the required storage \ + attribute(s). Try adding \"#[{STORAGE_PURITY_ATTRIBUTE_NAME}({suggested_attributes})]\" to the function \ declaration." )] - ImpureInPureContext { - storage_op: &'static str, - attrs: String, + StorageAccessMismatched { + /// True if the function with mismatched access is pure. + is_pure: bool, + storage_access_violations: Vec<(Span, StorageAccess)>, + suggested_attributes: String, + /// Span pointing to the name of the function in the function declaration, + /// whose storage attributes mismatch the storage access patterns. span: Span, }, #[error( @@ -1162,7 +1161,6 @@ impl Spanned for CompileError { MatchArmVariableMismatchedType { variable, .. } => variable.span(), MatchedValueIsNotValid { span, .. } => span.clone(), NotAnEnum { span, .. } => span.clone(), - StorageAccessMismatch { span, .. } => span.clone(), TraitDeclPureImplImpure { span, .. } => span.clone(), TraitImplPurityMismatch { span, .. } => span.clone(), DeclIsNotAnEnum { span, .. } => span.clone(), @@ -1177,7 +1175,7 @@ impl Spanned for CompileError { DeclIsNotAConstant { span, .. } => span.clone(), DeclIsNotATypeAlias { span, .. } => span.clone(), ImpureInNonContract { span, .. } => span.clone(), - ImpureInPureContext { span, .. } => span.clone(), + StorageAccessMismatched { span, .. } => span.clone(), ParameterRefMutabilityMismatch { span, .. } => span.clone(), IntegerTooLarge { span, .. } => span.clone(), IntegerTooSmall { span, .. } => span.clone(), @@ -2671,6 +2669,60 @@ impl ToDiagnostic for CompileError { "Verify that you are using a version of the \"core\" standard library that contains this function.".into(), ], }, + StorageAccessMismatched { span, is_pure, suggested_attributes, storage_access_violations } => Diagnostic { + // Pure function cannot access storage + // or + // Storage read-only function cannot write to storage + reason: Some(Reason::new(code(1), format!("{} function cannot {} storage", + if *is_pure { + "Pure" + } else { + "Storage read-only" + }, + if *is_pure { + "access" + } else { + "write to" + } + ))), + issue: Issue::error( + source_engine, + span.clone(), + format!("Function \"{}\" is {} and cannot {} storage.", + span.as_str(), + if *is_pure { + "pure" + } else { + "declared as `#[storage(read)]`" + }, + if *is_pure { + "access" + } else { + "write to" + }, + ) + ), + hints: storage_access_violations + .iter() + .map(|(span, storage_access)| Hint::info( + source_engine, + span.clone(), + format!("{storage_access}") + )) + .collect(), + help: vec![ + format!("Consider declaring the function \"{}\" as `#[{STORAGE_PURITY_ATTRIBUTE_NAME}({suggested_attributes})]`,", + span.as_str() + ), + format!("or removing the {} from the function body.", + if *is_pure { + "storage access code".to_string() + } else { + format!("storage write{}", plural_s(storage_access_violations.len())) + } + ), + ], + }, _ => Diagnostic { // TODO: Temporary we use self here to achieve backward compatibility. // In general, self must not be used and will not be used once we @@ -2761,3 +2813,53 @@ impl fmt::Display for ShadowingSource { } } } + +/// Defines how a storage gets accessed within a function body. +/// E.g., calling `__state_clear` intrinsic or using `scwq` ASM instruction +/// represent a [StorageAccess::Clear] access. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum StorageAccess { + Clear, + ReadWord, + ReadSlots, + WriteWord, + WriteSlots, + /// Storage access happens via call to an impure function. + /// The parameters are the call path span and if the called function + /// reads from and writes to the storage: (call_path, reads, writes). + ImpureFunctionCall(Span, bool, bool), +} + +impl StorageAccess { + pub fn is_write(&self) -> bool { + matches!( + self, + Self::Clear | Self::WriteWord | Self::WriteSlots | Self::ImpureFunctionCall(_, _, true) + ) + } +} + +impl fmt::Display for StorageAccess { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Clear => f.write_str("Clearing the storage happens here."), + Self::ReadWord => f.write_str("Reading a word from the storage happens here."), + Self::ReadSlots => f.write_str("Reading storage slots happens here."), + Self::WriteWord => f.write_str("Writing a word to the storage happens here."), + Self::WriteSlots => f.write_str("Writing to storage slots happens here."), + Self::ImpureFunctionCall(call_path, reads, writes) => f.write_fmt(format_args!( + "Function \"{}\" {} the storage.", + call_path_suffix_with_args(&call_path.as_str().to_string()), + match (reads, writes) { + (true, true) => "reads from and writes to", + (true, false) => "reads from", + (false, true) => "writes to", + (false, false) => unreachable!( + "Function \"{}\" is impure, so it must read from or write to the storage.", + call_path.as_str() + ), + } + )), + } + } +} diff --git a/sway-error/src/formatting.rs b/sway-error/src/formatting.rs index 84adf117f4f..a81f289df4b 100644 --- a/sway-error/src/formatting.rs +++ b/sway-error/src/formatting.rs @@ -152,15 +152,15 @@ where /// with the text "and more". /// /// E.g.: -/// [a] => -/// - a -/// [a, b] => -/// - a -/// - b -/// [a, b, c, d, e] => -/// - a -/// - b -/// - and three more +/// * [a] => +/// - a +/// * [a, b] => +/// - a +/// - b +/// * [a, b, c, d, e] => +/// - a +/// - b +/// - and three more /// /// Panics if the `sequence` is empty, or `max_items` is zero. pub(crate) fn sequence_to_list(sequence: &[T], indent: Indent, max_items: usize) -> Vec diff --git a/sway-ir/src/function.rs b/sway-ir/src/function.rs index 14bf40b615a..cedfee85930 100644 --- a/sway-ir/src/function.rs +++ b/sway-ir/src/function.rs @@ -39,8 +39,8 @@ pub struct FunctionContent { pub module: Module, pub is_public: bool, pub is_entry: bool, - // True if the function was an entry, before getting wrapped - // by the `__entry` function. E.g, a script `main` function. + /// True if the function was an entry, before getting wrapped + /// by the `__entry` function. E.g, a script `main` function. pub is_original_entry: bool, pub is_fallback: bool, pub selector: Option<[u8; 4]>, diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index 325938cf8af..d0fe6e6a5f4 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -137,8 +137,9 @@ pub enum FuelVmInstruction { ReadRegister(Register), /// Revert VM execution. Revert(Value), - /// - Sends a message to an output via the `smo` FuelVM instruction. The first operand must be - /// a `B256` representing the recipient. The second operand is the message data being sent. + /// - Sends a message to an output via the `smo` FuelVM instruction. + /// - The first operand must be a `B256` representing the recipient. + /// - The second operand is the message data being sent. /// - `message_size` and `coins` must be of type `U64`. Smo { recipient: Value, diff --git a/sway-ir/src/optimize/dce.rs b/sway-ir/src/optimize/dce.rs index df0014020f0..8adeef01fc5 100644 --- a/sway-ir/src/optimize/dce.rs +++ b/sway-ir/src/optimize/dce.rs @@ -1,9 +1,10 @@ //! ## Dead Code Elimination //! -//! This optimization removes unused definitions. The pass is a combination of +//! This optimization removes unused definitions. The pass is a combination of: //! 1. A liveness analysis that keeps track of the uses of a definition, //! 2. At the time of inspecting a definition, if it has no uses, it is removed. -//! This pass does not do CFG transformations. That is handled by simplify_cfg. +//! +//! This pass does not do CFG transformations. That is handled by `simplify_cfg`. use rustc_hash::FxHashSet; diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.lock index a33bdc00acc..63d163df6ac 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'abi_pure_calls_impure' -source = 'member' +name = "abi_pure_calls_impure" +source = "member" +dependencies = ["core"] + +[[package]] +name = "core" +source = "path+from-root-966EA6A554E875FB" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.toml index d80c89b5739..e473123a27a 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/Forc.toml @@ -4,3 +4,6 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/src/main.sw index 0c20e9c3856..8e8085fe9a9 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/src/main.sw @@ -12,5 +12,6 @@ impl MyContract for Contract { #[storage(read)] fn f() -> bool { + let _ = __state_load_word(b256::zero()); true } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/test.toml index 19bab69fb33..2ce9f943995 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/abi_pure_calls_impure/test.toml @@ -1,4 +1,9 @@ category = "fail" -# check: f() -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function() -> bool +#nextln: $()Function "test_function" is pure and cannot access storage. +#check: $()f() +#nextln: $()Function "f" reads from the storage. +#check: $()Consider declaring the function "test_function" as `#[storage(read)]` diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.lock index 139912752c9..8e713d15485 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.lock @@ -1,3 +1,8 @@ [[package]] name = "contract_pure_calls_impure" source = "member" +dependencies = ["core"] + +[[package]] +name = "core" +source = "path+from-root-35CE415E0DBF1633" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.toml index 1c3209095f4..964d059814d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/Forc.toml @@ -4,3 +4,6 @@ license = "Apache-2.0" name = "contract_pure_calls_impure" entry = "main.sw" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/src/main.sw index d23692d31f5..4e0765dd605 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/src/main.sw @@ -11,5 +11,7 @@ impl ImpurityTest for Contract { } } -#[storage(read, write)] -fn foo() {} +#[storage(write)] +fn foo() { + let _ = __state_store_word(b256::zero(), 0); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/test.toml index b1ee853337d..d8d44b4bd1d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/contract_pure_calls_impure/test.toml @@ -1,4 +1,11 @@ category = "fail" -# check: foo(); -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read, write)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn impure_func() -> bool +#nextln: $()Function "impure_func" is pure and cannot access storage. +#check: $()foo(); +#nextln: $()Function "foo" writes to the storage. +#check: $()Consider declaring the function "impure_func" as `#[storage(write)]` + +#check: $()1 error. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.lock index 3a21fac4e30..377ae4a89f8 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'impure_abi_read_calls_impure_write' -source = 'member' +name = "core" +source = "path+from-root-1FE39E672D7A7D13" + +[[package]] +name = "impure_abi_read_calls_impure_write" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.toml index 74421825e53..61394d53f8f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/Forc.toml @@ -4,3 +4,6 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/src/main.sw index 3ae3b741955..358f2940515 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/src/main.sw @@ -14,5 +14,6 @@ impl MyContract for Contract { #[storage(write)] fn f() -> bool { + let _ = __state_store_word(b256::zero(), 0); true } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/test.toml index b6888964bb9..55884b2bde2 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_abi_read_calls_impure_write/test.toml @@ -1,5 +1,11 @@ category = "fail" -# check: fn test_function() -> bool { -# check: f() -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read, write)]" to the function declaration. +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()fn test_function() -> bool +#nextln: $()Function "test_function" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()f() +#nextln: $()Function "f" writes to the storage. +#check: $()Consider declaring the function "test_function" as `#[storage(read, write)]` + +#check: $()1 error. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.lock deleted file mode 100644 index 3f79475f867..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.lock +++ /dev/null @@ -1,3 +0,0 @@ -[[package]] -name = 'impure_read_calls_impure_write' -source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.toml deleted file mode 100644 index ba104fe3ae6..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/Forc.toml +++ /dev/null @@ -1,6 +0,0 @@ -[project] -authors = ["Fuel Labs "] -license = "Apache-2.0" -name = "impure_read_calls_impure_write" -entry = "main.sw" -implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/src/main.sw deleted file mode 100644 index c2cc8febb22..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/src/main.sw +++ /dev/null @@ -1,12 +0,0 @@ -contract; - -fn main() { -} - -#[storage(read)] -fn can_read() { - can_write(); -} - -#[storage(write)] -fn can_write() {} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/test.toml deleted file mode 100644 index 2c13c211b9b..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_read_calls_impure_write/test.toml +++ /dev/null @@ -1,3 +0,0 @@ -category = "fail" - -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read, write)]" to the function declaration. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.lock index b209a503e5f..2549f45d2cb 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'impure_trait_read_calls_impure_write' -source = 'member' +name = "core" +source = "path+from-root-DA75D6DB25B7E1EA" + +[[package]] +name = "impure_trait_read_calls_impure_write" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.toml index 5681bb22946..be0354fefb5 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/Forc.toml @@ -4,3 +4,6 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/src/main.sw index af8f5caba34..0e391395045 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/src/main.sw @@ -8,7 +8,20 @@ trait A { impl A for bool { #[storage(write)] fn f(self) -> bool { - self + let _ = __state_store_word(b256::zero(), 0); + true + } +} + +abi Abi { + #[storage(read, write)] + fn test() -> bool; +} + +impl Abi for Contract { + #[storage(read, write)] + fn test() -> bool { + g() } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/test.toml index 59172c85dfc..ae1396a8f82 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impure_trait_read_calls_impure_write/test.toml @@ -1,5 +1,11 @@ category = "fail" -# check: pub fn g() -> bool { -# nextln: true.f() -# nextln: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read, write)]" to the function declaration. +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()pub fn g() -> bool +#nextln: $()Function "g" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()true.f() +#nextln: $()Function "f" writes to the storage. +#check: $()Consider declaring the function "g" as `#[storage(read, write)]` + +#check: 1 error. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.lock index 8734cdecdfe..a0b7e6c299c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'nested_impure' -source = 'member' +name = "core" +source = "path+from-root-817B68A1DC27AFAA" + +[[package]] +name = "nested_impure" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.toml index d2c96a4c91b..5455877c6f9 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/Forc.toml @@ -4,3 +4,6 @@ license = "Apache-2.0" name = "nested_impure" entry = "main.sw" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/src/main.sw index 8ff02c223a7..65ad037f20d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/src/main.sw @@ -1,22 +1,29 @@ contract; -fn main() { - foo(); +abi Abi { + fn test(); +} + +impl Abi for Contract { + fn test() { + foo(); + } } fn foo() { bar(); - baz(); + let _ = baz(); } // Although annotated, with no args is pure. #[storage()] fn bar() { - let z = baz(); + let _ = baz(); } // Explicitly impure. #[storage(read)] fn baz() -> u64 { - 5 + let _ = __state_load_word(b256::zero()); + 5 } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/test.toml index 7ce22172e4c..4f41f7c91e8 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nested_impure/test.toml @@ -1,7 +1,27 @@ category = "fail" -# check: $()This returns a value of type u64, which is not assigned to anything and is ignored. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn bar() +#nextln: $()Function "bar" is pure and cannot access storage. +#check: $()let _ = baz(); +#nextln: $()Function "baz" reads from the storage. +#check: $()Consider declaring the function "bar" as `#[storage(read)]` -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn foo() +#nextln: $()Function "foo" is pure and cannot access storage. +#check: $()bar(); +#nextln: $()Function "bar" reads from the storage. +#check: $()let _ = baz(); +#nextln: $()Function "baz" reads from the storage. +#check: $()Consider declaring the function "foo" as `#[storage(read)]` -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test() +#nextln: $()Function "test" is pure and cannot access storage. +#check: $()foo(); +#nextln: $()Function "foo" reads from the storage. +#check: $()Consider declaring the function "test" as `#[storage(read)]` \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.lock deleted file mode 100644 index d0af35185a5..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.lock +++ /dev/null @@ -1,3 +0,0 @@ -[[package]] -name = 'pure_calls_impure' -source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.toml deleted file mode 100644 index 65b425cdf2e..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/Forc.toml +++ /dev/null @@ -1,6 +0,0 @@ -[project] -authors = ["Fuel Labs "] -license = "Apache-2.0" -name = "pure_calls_impure" -entry = "main.sw" -implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/src/main.sw deleted file mode 100644 index 404c78ef543..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/src/main.sw +++ /dev/null @@ -1,14 +0,0 @@ -contract; - -// this should fail because a pure function cannot call an impure function - -fn main() { -} - - -fn pure_function() { - impure_function(); -} - -#[storage(write)] -fn impure_function() {} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/test.toml deleted file mode 100644 index 41a279dacfc..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/pure_calls_impure/test.toml +++ /dev/null @@ -1,3 +0,0 @@ -category = "fail" - -# check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(write)]" to the function declaration. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.lock new file mode 100644 index 00000000000..087c33928e3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-EF0A42ACCA18193C" + +[[package]] +name = "purity_of_asm_instructions_and_intrinsics" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.toml new file mode 100644 index 00000000000..094bbe327e1 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "purity_of_asm_instructions_and_intrinsics" + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/src/main.sw new file mode 100644 index 00000000000..de537f296c0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/src/main.sw @@ -0,0 +1,74 @@ +contract; + +abi Abi { + fn test(); +} + +struct S { } + +impl S { + fn read_intrinsics(self) -> Self { + let ptr = asm (p: 0) { p: raw_ptr }; + let _ = __state_load_word(b256::zero()); + let _ = __state_load_quad(b256::zero(), ptr, 1); + + self + } + + #[storage(read)] + fn write_intrinsics(self) -> Self { + let ptr = asm (p: 0) { p: raw_ptr }; + let _ = __state_store_word(b256::zero(), 0); + let _ = __state_store_quad(b256::zero(), ptr, 1); + + self + } + + #[storage(read)] + fn clear_intrinsic(self) -> Self { + let _ = __state_clear(b256::zero(), 1); + + self + } +} + +impl Abi for Contract { + fn test() { + read_asm_instructions(); + write_asm_instructions(); + clear_asm_instruction(); + + let s = S {}; + let _ = s.read_intrinsics(); + let _ = s.write_intrinsics(); + let _ = s.clear_intrinsic(); + } +} + +fn read_asm_instructions() { + asm(r1, r2, r3: 0) { + srw r1 r2 r3; + } + + asm(r1: 0, r2, r3: 0, r4: 0) { + srwq r1 r2 r3 r4; + } +} + +#[storage(read)] +fn write_asm_instructions() { + asm(r1: 0, r2, r3: 0) { + sww r1 r2 r3; + } + + asm(r1: 0, r2, r3: 0, r4: 0) { + swwq r1 r2 r3 r4; + } +} + +#[storage(read)] +fn clear_asm_instruction() { + asm(r1: 0, r2, r3: 0) { + scwq r1 r2 r3; + } +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/test.toml new file mode 100644 index 00000000000..fa92c337a22 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/purity_of_asm_instructions_and_intrinsics/test.toml @@ -0,0 +1,76 @@ +category = "fail" + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn read_asm_instructions() +#nextln: $()Function "read_asm_instructions" is pure and cannot access storage. +#check: $()srw r1 r2 r3; +#nextln: $()Reading a word from the storage happens here. +#check: $()srwq r1 r2 r3 r4; +#nextln: $()Reading storage slots happens here. +#check: $()Consider declaring the function "read_asm_instructions" as `#[storage(read)]` + +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()fn write_asm_instructions() +#nextln: $()Function "write_asm_instructions" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()sww r1 r2 r3; +#nextln: $()Writing a word to the storage happens here. +#check: $()swwq r1 r2 r3 r4; +#nextln: $()Writing to storage slots happens here. +#check: $()Consider declaring the function "write_asm_instructions" as `#[storage(read, write)]` + +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()fn clear_asm_instruction() +#nextln: $()Function "clear_asm_instruction" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()scwq r1 r2 r3; +#nextln: $()Clearing the storage happens here. +#check: $()Consider declaring the function "clear_asm_instruction" as `#[storage(read, write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn read_intrinsics(self) -> Self +#nextln: $()Function "read_intrinsics" is pure and cannot access storage. +#check: $()let _ = __state_load_word(b256::zero()); +#nextln: $()Reading a word from the storage happens here. +#check: $()let _ = __state_load_quad(b256::zero(), ptr, 1); +#nextln: $()Reading storage slots happens here. +#check: $()Consider declaring the function "read_intrinsics" as `#[storage(read)]` + +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()fn write_intrinsics(self) -> Self +#nextln: $()Function "write_intrinsics" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()let _ = __state_store_word(b256::zero(), 0); +#nextln: $()Writing a word to the storage happens here. +#check: $()let _ = __state_store_quad(b256::zero(), ptr, 1); +#nextln: $()Writing to storage slots happens here. +#check: $()Consider declaring the function "write_intrinsics" as `#[storage(read, write)]` + +#check: $()error +#sameln: $()Storage read-only function cannot write to storage +#check: $()fn clear_intrinsic(self) -> Self +#nextln: $()Function "clear_intrinsic" is declared as `#[storage(read)]` and cannot write to storage. +#check: $()let _ = __state_clear(b256::zero(), 1); +#nextln: $()Clearing the storage happens here. +#check: $()Consider declaring the function "clear_intrinsic" as `#[storage(read, write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test() +#nextln: $()Function "test" is pure and cannot access storage. +#check: $()read_asm_instructions(); +#nextln: $()Function "read_asm_instructions" reads from the storage. +#check: $()write_asm_instructions(); +#nextln: $()Function "write_asm_instructions" writes to the storage. +#check: $()clear_asm_instruction(); +#nextln: $()Function "clear_asm_instruction" writes to the storage. +#check: $()let _ = s.read_intrinsics(); +#nextln: $()Function "read_intrinsics" reads from the storage. +#check: $()let _ = s.write_intrinsics(); +#nextln: $()Function "write_intrinsics" writes to the storage. +#check: $()let _ = s.clear_intrinsic(); +#nextln: $()Function "clear_intrinsic" writes to the storage. + +#check: $()7 errors. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.lock index 8d57bc1441b..1994ee4c330 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.lock @@ -2,12 +2,7 @@ name = "core" source = "path+from-root-BB0D5D60D9FB76A1" -[[package]] -name = "std" -source = "path+from-root-BB0D5D60D9FB76A1" -dependencies = ["core"] - [[package]] name = "storage_clear_incorrect_purity_annotation" source = "member" -dependencies = ["std"] +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.toml index 6ab9ffc89f7..fd7eb4ab86d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/Forc.toml @@ -5,4 +5,4 @@ license = "Apache-2.0" name = "storage_clear_incorrect_purity_annotation" [dependencies] -std = { path = "../../../../../../sway-lib-std" } +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/src/main.sw index c498c6f1d9e..b86cacc78ae 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/src/main.sw @@ -6,6 +6,6 @@ abi MyContract { impl MyContract for Contract { fn test_function() { - std::storage::storage_api::clear(0x0000000000000000000000000000000000000000000000000000000000000000); + let _ = __state_clear(b256::zero(), 0); } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/test.toml index 2d0cc28b07b..b4da49e0acb 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_clear_incorrect_purity_annotation/test.toml @@ -1,3 +1,11 @@ category = "fail" -#check: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(write)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function() +#nextln: $()Function "test_function" is pure and cannot access storage. +#check: $()let _ = __state_clear(b256::zero(), 0); +#nextln: $()Clearing the storage happens here. +#check: $()Consider declaring the function "test_function" as `#[storage(write)]` + +#check: $()1 error. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.lock deleted file mode 100644 index d2cbe280e7b..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.lock +++ /dev/null @@ -1,13 +0,0 @@ -[[package]] -name = 'core' -source = 'path+from-root-408A0F6A51E5F189' - -[[package]] -name = 'std' -source = 'path+from-root-408A0F6A51E5F189' -dependencies = ['core'] - -[[package]] -name = 'storage_conflict' -source = 'member' -dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/test.toml deleted file mode 100644 index dcb08e5325b..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/test.toml +++ /dev/null @@ -1,49 +0,0 @@ -category = "fail" - -# check: storage_conflict/src/main.sw:103:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:46:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:33:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:13:5 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:110:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:57:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:53:1 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:16:5 -# check: $()This function performs a storage read but does not have the required attribute(s). Try adding "#[storage(read)]" to the function declaration. - -# check: storage_conflict/src/main.sw:118:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:71:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:64:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:19:5 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:124:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:87:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:82:1 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. - -# check: storage_conflict/src/main.sw:22:5 -# check: $()This function performs a storage write but does not have the required attribute(s). Try adding "#[storage(write)]" to the function declaration. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.lock index c9fecc9c847..e6a2b048916 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'storage_ops_in_library' -source = 'member' +name = "core" +source = "path+from-root-5340A91F7729E05D" + +[[package]] +name = "storage_ops_in_library" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.toml index a4224f898ad..02f7043dfe5 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/Forc.toml @@ -4,3 +4,7 @@ entry = "main.sw" license = "Apache-2.0" name = "storage_ops_in_library" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } + diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/test.toml index 118b1b04091..3ada048855f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_ops_in_library/test.toml @@ -1,4 +1,19 @@ category = "fail" -# check: do_storage::side_effects(); -# nextln: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read, write)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn main() +#nextln: $()Function "main" is pure and cannot access storage. +#check: $()do_storage::side_effects(); +#nextln: $()Function "side_effects" reads from and writes to the storage. +#check: $()Consider declaring the function "main" as `#[storage(read, write)]` + +#check: $()srw v is_set key; +#nextln: $()Contract storage cannot be used in an external context. + +#check: $()sww key is_set v; + +#check: $()sww key is_set v; +#nextln: $()Contract storage cannot be used in an external context. + +#check: $()3 errors. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.lock new file mode 100644 index 00000000000..d083d106abb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-AB487C347066147A" + +[[package]] +name = "storage_purity_conflict" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.toml similarity index 60% rename from test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.toml index 702203ab0a5..84569cf591e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/Forc.toml @@ -1,9 +1,9 @@ [project] -name = "storage_conflict" +name = "storage_purity_conflict" authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" implicit-std = false [dependencies] -std = { path = "../../../../../../sway-lib-std" } +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/src/main.sw similarity index 95% rename from test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/src/main.sw rename to test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/src/main.sw index d9cf66c1e19..fae1a32f505 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/storage_conflict/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/src/main.sw @@ -32,7 +32,7 @@ impl MyContract for Contract { fn do_impure_stuff_a(choice: bool) -> bool { if choice { - let s = do_more_impure_stuff_a(); + let _ = do_more_impure_stuff_a(); false } else { true @@ -55,7 +55,7 @@ fn do_impure_stuff_b() -> bool { } fn do_more_impure_stuff_b() -> bool { - let a = read_storage_b256(); + let _ = read_storage_b256(); true } @@ -80,7 +80,7 @@ enum E { } fn do_impure_stuff_d() -> bool { - let a = E::b(do_more_impure_stuff_d()); + let _ = E::b(do_more_impure_stuff_d()); true } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/test.toml new file mode 100644 index 00000000000..f02b083598d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/storage_purity_conflict/test.toml @@ -0,0 +1,131 @@ +category = "fail" + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn read_storage_word() -> u64 +#nextln: $()Function "read_storage_word" is pure and cannot access storage. +#check: $()srw res is_set key; +#nextln: $()Reading a word from the storage happens here. +#check: $()Consider declaring the function "read_storage_word" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_more_impure_stuff_a() -> S +#nextln: $()Function "do_more_impure_stuff_a" is pure and cannot access storage. +#check: $()let a = read_storage_word(); +#nextln: $()Function "read_storage_word" reads from the storage. +#check: $()Consider declaring the function "do_more_impure_stuff_a" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_impure_stuff_a(choice: bool) -> bool +#nextln: $()Function "do_impure_stuff_a" is pure and cannot access storage. +#check: $()let _ = do_more_impure_stuff_a(); +#nextln: $()Function "do_more_impure_stuff_a" reads from the storage. +#check: $()Consider declaring the function "do_impure_stuff_a" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function_a() -> bool +#nextln: $()Function "test_function_a" is pure and cannot access storage. +#check: $()do_impure_stuff_a(true) +#nextln: $()Function "do_impure_stuff_a" reads from the storage. +#check: $()Consider declaring the function "test_function_a" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn read_storage_b256() -> b256 +#nextln: $()Function "read_storage_b256" is pure and cannot access storage. +#check: $()srwq buf is_set key count; +#nextln: $()Reading storage slots happens here. +#check: $()Consider declaring the function "read_storage_b256" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_more_impure_stuff_b() -> bool +#nextln: $()Function "do_more_impure_stuff_b" is pure and cannot access storage. +#check: $()let _ = read_storage_b256(); +#nextln: $()Function "read_storage_b256" reads from the storage. +#check: $()Consider declaring the function "do_more_impure_stuff_b" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_impure_stuff_b() -> bool +#nextln: $()Function "do_impure_stuff_b" is pure and cannot access storage. +#check: $()do_more_impure_stuff_b() +#nextln: $()Function "do_more_impure_stuff_b" reads from the storage. +#check: $()Consider declaring the function "do_impure_stuff_b" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function_b() -> bool +#nextln: $()Function "test_function_b" is pure and cannot access storage. +#check: $()do_impure_stuff_b() +#nextln: $()Function "do_impure_stuff_b" reads from the storage. +#check: $()Consider declaring the function "test_function_b" as `#[storage(read)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn write_storage_word() +#nextln: $()Function "write_storage_word" is pure and cannot access storage. +#check: $()sww key is_set val; +#nextln: $()Writing a word to the storage happens here. +#check: $()Consider declaring the function "write_storage_word" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_more_impure_stuff_c() +#nextln: $()Function "do_more_impure_stuff_c" is pure and cannot access storage. +#check: $()write_storage_word(); +#nextln: $()Function "write_storage_word" writes to the storage. +#check: $()Consider declaring the function "do_more_impure_stuff_c" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_impure_stuff_c() -> bool +#nextln: $()Function "do_impure_stuff_c" is pure and cannot access storage. +#check: $()do_more_impure_stuff_c(); +#nextln: $()Function "do_more_impure_stuff_c" writes to the storage. +#check: $()Consider declaring the function "do_impure_stuff_c" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function_c() -> bool +#nextln: $()Function "test_function_c" is pure and cannot access storage. +#check: $()do_impure_stuff_c() +#nextln: $()Function "do_impure_stuff_c" writes to the storage. +#check: $()Consider declaring the function "test_function_c" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn write_storage_b256() +#nextln: $()Function "write_storage_b256" is pure and cannot access storage. +#check: $()swwq key is_set val count; +#nextln: $()Writing to storage slots happens here. +#check: $()Consider declaring the function "write_storage_b256" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_more_impure_stuff_d() -> bool +#nextln: $()Function "do_more_impure_stuff_d" is pure and cannot access storage. +#check: $()write_storage_b256(); +#nextln: $()Function "write_storage_b256" writes to the storage. +#check: $()Consider declaring the function "do_more_impure_stuff_d" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn do_impure_stuff_d() -> bool +#nextln: $()Function "do_impure_stuff_d" is pure and cannot access storage. +#check: $()let _ = E::b(do_more_impure_stuff_d()); +#nextln: $()Function "do_more_impure_stuff_d" writes to the storage. +#check: $()Consider declaring the function "do_impure_stuff_d" as `#[storage(write)]` + +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn test_function_d() -> bool +#nextln: $()Function "test_function_d" is pure and cannot access storage. +#check: $()do_impure_stuff_d() +#nextln: $()Function "do_impure_stuff_d" writes to the storage. +#check: $()Consider declaring the function "test_function_d" as `#[storage(write)]` + +#check: $()16 errors. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.lock index 441af3974f3..c918557d18e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.lock @@ -1,3 +1,8 @@ [[package]] -name = 'trait_pure_calls_impure' -source = 'member' +name = "core" +source = "path+from-root-C42A95D87AAB6BDD" + +[[package]] +name = "trait_pure_calls_impure" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.toml index cf4b4d43dae..20ab2e0ecfe 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/Forc.toml @@ -4,3 +4,6 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/src/main.sw index b2ed9386c54..5e70bda7632 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/src/main.sw @@ -8,3 +8,25 @@ trait A { self.f() } } + +struct S {} + +impl A for S { + #[storage(read)] + fn f(self) -> bool { + let _ = __state_load_word(b256::zero()); + true + } +} + +abi Abi { + #[storage(read)] + fn test() -> bool; +} + +impl Abi for Contract { + #[storage(read)] + fn test() -> bool { + S {}.g() + } +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/test.toml index 4df8d76a952..5f48cb0399a 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/trait_pure_calls_impure/test.toml @@ -1,4 +1,9 @@ category = "fail" -# check: self.f() -# nextln: $()Storage attribute access mismatch. Try giving the surrounding function more access by adding "#[storage(read)]" to the function declaration. +#check: $()error +#sameln: $()Pure function cannot access storage +#check: $()fn g(self) -> bool +#nextln: $()Function "g" is pure and cannot access storage. +#check: $()self.f() +#nextln: $()Function "f" reads from the storage. +#check: $()Consider declaring the function "g" as `#[storage(read)]` \ No newline at end of file From fdf83eaa6bbeb15b0a73ab2c89d96a45f51702f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ron=C4=8Devi=C4=87?= Date: Sun, 18 Aug 2024 22:42:02 +0200 Subject: [PATCH 2/2] Fix failing `ir_generation` tests --- test/src/ir_generation/tests/addr_of.sw | 15 +++++++-------- test/src/ir_generation/tests/eq_intrinsic.sw | 4 ++-- test/src/ir_generation/tests/size_of_val.sw | 4 ++-- .../ir_generation/tests/state_load_word.sw | 12 ++++++------ .../ir_generation/tests/storage_metadata.sw | 19 ++++++++++++------- test/src/ir_generation/tests/str_slice.sw | 6 +++--- test/src/ir_generation/tests/test_metadata.sw | 3 ++- 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/test/src/ir_generation/tests/addr_of.sw b/test/src/ir_generation/tests/addr_of.sw index e8ec2c8ed88..9590d222a52 100644 --- a/test/src/ir_generation/tests/addr_of.sw +++ b/test/src/ir_generation/tests/addr_of.sw @@ -1,4 +1,3 @@ - script; fn main() { @@ -6,10 +5,10 @@ fn main() { let _ = __addr_of(a); } -// check: v0 = get_local ptr b256, a, !2 -// check: v1 = const b256 0x0000000000000000000000000000000000000000000000000000000000000001, !3 -// check: store v1 to v0, !2 -// check: v2 = get_local ptr b256, a, !4 -// check: v3 = ptr_to_int v2 to u64, !5 -// check: v4 = get_local ptr u64, _, !6 -// check: store v3 to v4, !6 +// check: v0 = get_local ptr b256, a, +// check: v1 = const b256 0x0000000000000000000000000000000000000000000000000000000000000001, +// check: store v1 to v0, +// check: v2 = get_local ptr b256, a, +// check: v3 = ptr_to_int v2 to u64, +// check: v4 = get_local ptr u64, _, +// check: store v3 to v4, diff --git a/test/src/ir_generation/tests/eq_intrinsic.sw b/test/src/ir_generation/tests/eq_intrinsic.sw index 832c0ea958e..8e69094fd5a 100644 --- a/test/src/ir_generation/tests/eq_intrinsic.sw +++ b/test/src/ir_generation/tests/eq_intrinsic.sw @@ -5,6 +5,6 @@ fn main() -> bool { true } -// check: $(l=$VAL) = const u64 1, !2 -// check: $(r=$VAL) = const u64 2, !3 +// check: $(l=$VAL) = const u64 1, +// check: $(r=$VAL) = const u64 2, // check: cmp eq $l $r diff --git a/test/src/ir_generation/tests/size_of_val.sw b/test/src/ir_generation/tests/size_of_val.sw index e146794d1e7..22ce1aa011d 100644 --- a/test/src/ir_generation/tests/size_of_val.sw +++ b/test/src/ir_generation/tests/size_of_val.sw @@ -4,6 +4,6 @@ fn main() { let _ = __size_of_val(1); } -// check: v0 = get_local ptr u64, _, !2 +// check: v0 = get_local ptr u64, _, // check: v1 = const u64 8 -// check: store v1 to v0, !2 +// check: store v1 to v0, diff --git a/test/src/ir_generation/tests/state_load_word.sw b/test/src/ir_generation/tests/state_load_word.sw index 72557769ae1..0f9e9578434 100644 --- a/test/src/ir_generation/tests/state_load_word.sw +++ b/test/src/ir_generation/tests/state_load_word.sw @@ -6,9 +6,9 @@ fn main() { ); } -// check: v0 = get_local ptr b256, key_for_storage, !2 -// check: v1 = const b256 0x0000000000000000000000000000000000000000000000000000000000000001, !3 -// check: store v1 to v0, !2 -// check: v2 = state_load_word key v0, !2 -// check: v3 = get_local ptr u64, _, !4 -// check: store v2 to v3, !4 +// check: v0 = get_local ptr b256, key_for_storage, +// check: v1 = const b256 0x0000000000000000000000000000000000000000000000000000000000000001, +// check: store v1 to v0, +// check: v2 = state_load_word key v0, +// check: v3 = get_local ptr u64, _, +// check: store v2 to v3, diff --git a/test/src/ir_generation/tests/storage_metadata.sw b/test/src/ir_generation/tests/storage_metadata.sw index c29fc473715..0b3345f1265 100644 --- a/test/src/ir_generation/tests/storage_metadata.sw +++ b/test/src/ir_generation/tests/storage_metadata.sw @@ -49,12 +49,17 @@ impl Incrementor for Contract { // check: fn increment(increment_by $MD: u64) -> u64, $(increment_md=$MD) { // check: fn initialize<557ac400>(initial_value $MD: u64) -> u64, $(init_md=$MD) { -// unordered: $(write_md=$MD) = storage "writes" -// unordered: $(readwrite_md=$MD) = storage "readswrites" -// unordered: $(read_md=$MD) = storage "reads" +// unordered: $(write_md=$MD) = purity "writes" +// unordered: $(write_fn_name_md=$MD) = fn_name_span $MD 359 369 -// The span idx is first, then the storage attribute. +// unordered: $(readwrite_md=$MD) = purity "readswrites" +// unordered: $(readwrite_fn_name_md=$MD) = fn_name_span $MD 553 562 -// unordered: $init_md = ($MD $write_md) -// unordered: $increment_md = ($MD $readwrite_md) -// unordered: $get_md = ($MD $read_md) +// unordered: $(read_md=$MD) = purity "reads" +// unordered: $(read_fn_name_md=$MD) = fn_name_span $MD 833 836 + +// The span idx is first, then the storage attribute, then the function name attribute. + +// unordered: $init_md = ($MD $write_md $write_fn_name_md) +// unordered: $increment_md = ($MD $readwrite_md $readwrite_fn_name_md) +// unordered: $get_md = ($MD $read_md $read_fn_name_md) \ No newline at end of file diff --git a/test/src/ir_generation/tests/str_slice.sw b/test/src/ir_generation/tests/str_slice.sw index ad2305d3652..e4a2e6c3ceb 100644 --- a/test/src/ir_generation/tests/str_slice.sw +++ b/test/src/ir_generation/tests/str_slice.sw @@ -11,11 +11,11 @@ fn main() -> u64 { // check: local slice a // check: v0 = const string<3> "abc" -// check: v1 = ptr_to_int v0 to u64, !2 -// check: v2 = get_local ptr { u64, u64 }, __anon_0, !2 +// check: v1 = ptr_to_int v0 to u64, +// check: v2 = get_local ptr { u64, u64 }, __anon_0, // check: v3 = const u64 0 // check: v4 = get_elem_ptr v2, ptr u64, v3 -// check: store v1 to v4, !2 +// check: store v1 to v4, // ::check-ir-optimized:: // pass: o1 diff --git a/test/src/ir_generation/tests/test_metadata.sw b/test/src/ir_generation/tests/test_metadata.sw index 491a61a5e6a..f5811a1787f 100644 --- a/test/src/ir_generation/tests/test_metadata.sw +++ b/test/src/ir_generation/tests/test_metadata.sw @@ -11,5 +11,6 @@ fn my_test_func() { // check: fn main() -> (), $(main_md=$MD) { // check: fn my_test_func() -> (), $(test_md=$MD) { +// check: $(fn_name_span_md=$MD) = fn_name_span $MD 52 64 // check: $(decl_index_md=$MD) = decl_index -// check: $test_md = ($MD $decl_index_md) +// check: $test_md = ($MD $fn_name_span_md $decl_index_md)