diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3946e83..2ddaec6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,7 @@ on: - "crates/**" - '!crates/e2e-move-tests/**' - "libmovevm/**" + - "libcompiler/**" - "Cargo.toml" concurrency: @@ -69,6 +70,6 @@ jobs: - name: Commit shared libraries uses: EndBug/add-and-commit@v9 with: - add: '["api/libmovevm.dylib", "api/libmovevm.aarch64.so", "api/libmovevm.x86_64.so"]' + add: '["api/libmovevm.dylib", "api/libmovevm.aarch64.so", "api/libmovevm.x86_64.so", "api/libcompiler.dylib", "api/libcompiler.aarch64.so", "api/libcompiler.x86_64.so"]' default_author: github_actions message: "update shared libraries" diff --git a/.gitignore b/.gitignore index a2f6d799..e865b47c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ lib*.a # Move build result **/build !types/compiler/build + +# Generated by Builders +**/artifacts/ diff --git a/Cargo.toml b/Cargo.toml index 05e97001..d5cfbc56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ + "libcompiler", "libmovevm", "crates/compiler", "crates/gas", @@ -13,7 +14,6 @@ members = [ "tools/generate-bcs-go", "tools/precompile", ] -exclude = [] [profile.release] opt-level = 3 @@ -25,7 +25,6 @@ codegen-units = 16 panic = 'unwind' incremental = true overflow-checks = true -strip = "debuginfo" [profile.bench] debug = true @@ -45,6 +44,7 @@ codegen-units = 16 version = "0.1.0" edition = "2021" license = "BUSL-1.1" +license-file = "LICENSE" homepage = "https://initia.xyz" repository = "https://github.com/initia-labs/movevm" rust-version = "1.76.0" diff --git a/Makefile b/Makefile index 0febb7e9..c21e72fe 100644 --- a/Makefile +++ b/Makefile @@ -10,18 +10,26 @@ USER_GROUP = $(shell id -g) SHARED_LIB_SRC = "" # File name of the shared library as created by the Rust build system SHARED_LIB_DST = "" # File name of the shared library that we store +COMPILER_SHARED_LIB_SRC = "" +COMPILER_SHARED_LIB_DST = "" ifeq ($(OS),Windows_NT) SHARED_LIB_SRC = movevm.dll SHARED_LIB_DST = movevm.dll + COMPILER_SHARED_LIB_SRC = compiler.dll + COMPILER_SHARED_LIB_DST = compiler.dll else UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) SHARED_LIB_SRC = libmovevm.so SHARED_LIB_DST = libmovevm.$(shell rustc --print cfg | grep target_arch | cut -d '"' -f 2).so + COMPILER_SHARED_LIB_SRC = libcompiler.so + COMPILER_SHARED_LIB_DST = libcompiler.$(shell rustc --print cfg | grep target_arch | cut -d '"' -f 2).so endif ifeq ($(UNAME_S),Darwin) SHARED_LIB_SRC = libmovevm.dylib SHARED_LIB_DST = libmovevm.dylib + COMPILER_SHARED_LIB_SRC = libcompiler.dylib + COMPILER_SHARED_LIB_DST = libcompiler.dylib endif endif @@ -31,6 +39,8 @@ all: test-filenames build test test-filenames: echo $(SHARED_LIB_DST) echo $(SHARED_LIB_SRC) + echo $(COMPILER_SHARED_LIB_DST) + echo $(COMPILER_SHARED_LIB_SRC) test: precompile test-rust test-go @@ -69,16 +79,18 @@ fmt: cargo fmt update-bindings: - # After we build libmovevm, we have to copy the generated bindings for Go code to use. - # We cannot use symlinks as those are not reliably resolved by `go get` (https://github.com/CosmWasm/wasmvm/pull/235). cp libmovevm/bindings.h api - + cp libcompiler/bindings_compiler.h api # Use debug build for quick testing. # In order to use "--features backtraces" here we need a Rust nightly toolchain, which we don't have by default build-rust-debug: cargo build -p movevm + cargo build -p compiler + cp -fp target/debug/$(SHARED_LIB_SRC) api/$(SHARED_LIB_DST) + cp -fp target/debug/$(COMPILER_SHARED_LIB_SRC) api/$(COMPILER_SHARED_LIB_DST) + make update-bindings # use release build to actually ship - smaller and much faster @@ -87,16 +99,22 @@ build-rust-debug: # enable stripping through cargo (if that is desired). build-rust-release: cargo build -p movevm --release + cargo build -p compiler --release rm -f api/$(SHARED_LIB_DST) + rm -f api/$(COMPILER_SHARED_LIB_DST) cp -fp target/release/$(SHARED_LIB_SRC) api/$(SHARED_LIB_DST) + cp -fp target/release/$(COMPILER_SHARED_LIB_SRC) api/$(COMPILER_SHARED_LIB_DST) make update-bindings @ #this pulls out ELF symbols, 80% size reduction! clean: cargo clean @-rm api/bindings.h - @-rm api/libmovevm.dylib + @-rm api/bindings_compiler.h @-rm libmovevm/bindings.h + @-rm libcompiler/bindings_compiler.h + @-rm api/$(SHARED_LIB_DST) + @-rm api/$(COMPILER_SHARED_LIB_DST) @echo cleaned. # Creates a release build in a containerized build environment of the static library for Alpine Linux (.a) @@ -106,8 +124,10 @@ release-build-alpine: docker run --rm -u $(USER_ID):$(USER_GROUP) \ -v $(shell pwd):/code/ \ $(BUILDERS_PREFIX)-alpine - cp libmovevm/artifacts/libmovevm_muslc.x86_64.a api - cp libmovevm/artifacts/libmovevm_muslc.aarch64.a api + cp artifacts/libmovevm_muslc.x86_64.a api + cp artifacts/libmovevm_muslc.aarch64.a api + cp artifacts/libcompiler_muslc.x86_64.a api + cp artifacts/libcompiler_muslc.aarch64.a api make update-bindings # try running go tests using this lib with muslc # docker run --rm -u $(USER_ID):$(USER_GROUP) -v $(shell pwd):/mnt/testrun -w /mnt/testrun $(ALPINE_TESTER) go build -tags muslc ./... @@ -120,8 +140,10 @@ release-build-linux: docker run --rm -u $(USER_ID):$(USER_GROUP) \ -v $(shell pwd):/code/ \ $(BUILDERS_PREFIX)-centos7 - cp libmovevm/artifacts/libmovevm.x86_64.so api - cp libmovevm/artifacts/libmovevm.aarch64.so api + cp artifacts/libmovevm.x86_64.so api + cp artifacts/libmovevm.aarch64.so api + cp artifacts/libcompiler.x86_64.so api + cp artifacts/libcompiler.aarch64.so api make update-bindings # Creates a release build in a containerized build environment of the shared library for macOS (.dylib) @@ -131,7 +153,8 @@ release-build-macos: docker run --rm -u $(USER_ID):$(USER_GROUP) \ -v $(shell pwd):/code/ \ $(BUILDERS_PREFIX)-cross build_macos.sh - cp libmovevm/artifacts/libmovevm.dylib api + cp artifacts/libmovevm.dylib api + cp artifacts/libcompiler.dylib api make update-bindings release-build: diff --git a/api/bindings.h b/api/bindings.h index 7b37817e..c1f376b6 100644 --- a/api/bindings.h +++ b/api/bindings.h @@ -1,4 +1,4 @@ -/* (c) 2022 initia labs. Licensed under BUSL-1.1 */ +/* (c) 2024 initia labs. Licensed under BUSL-1.1 */ #ifndef __LIBMOVEVM__ #define __LIBMOVEVM__ @@ -14,22 +14,6 @@ #include -enum CoverageOption { - /** - * Display a coverage summary for all modules in this package - */ - CoverageOption_Summary = 0, - /** - * Display coverage information about the module against source code - */ - CoverageOption_Source = 1, - /** - * Display coverage information about the module against disassembled bytecode - */ - CoverageOption_Bytecode = 2, -}; -typedef uint8_t CoverageOption; - enum ErrnoValue { ErrnoValue_Success = 0, ErrnoValue_Other = 1, @@ -137,82 +121,6 @@ typedef struct { size_t len; } ByteSliceView; -typedef struct { - /** - * Compile in 'dev' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used if - * this flag is set. This flag is useful for development of packages that expose named - * addresses that are not set to a specific value. - */ - bool dev_mode; - /** - * Compile in 'test' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used - * along with any code in the 'tests' directory. - */ - bool test_mode; - /** - * Generate documentation for packages - */ - bool generate_docs; - /** - * Generate ABIs for packages - */ - bool generate_abis; - /** - * Installation directory for compiled artifacts. Defaults to current directory. - */ - ByteSliceView install_dir; - /** - * Force recompilation of all packages - */ - bool force_recompilation; - /** - * Only fetch dependency repos to MOVE_HOME - */ - bool fetch_deps_only; - /** - * Skip fetching latest git dependencies - */ - bool skip_fetch_latest_git_deps; - /** - * bytecode version. set 0 to unset and to use default - */ - uint32_t bytecode_version; -} CompilerBuildConfig; - -typedef struct { - /** - * Path to a package which the command should be run with respect to. - */ - ByteSliceView package_path; - /** - * Print additional diagnostics if available. - */ - bool verbose; - /** - * Package build options - */ - CompilerBuildConfig build_config; -} CompilerArgument; - -typedef struct { - ByteSliceView module_name; -} CompilerCoverageBytecodeOption; - -typedef struct { - ByteSliceView module_name; -} CompilerCoverageSourceOption; - -typedef struct { - /** - * Whether function coverage summaries should be displayed - */ - bool functions; - /** - * Output CSV data of coverage - */ - bool output_csv; -} CompilerCoverageSummaryOption; - typedef struct { uint8_t _private[0]; } db_t; @@ -267,47 +175,6 @@ typedef struct { Db_vtable vtable; } Db; -typedef struct { - /** - * Whether to include private declarations and implementations into the generated - * documentation. Defaults to false. - */ - bool include_impl; - /** - * Whether to include specifications in the generated documentation. Defaults to false. - */ - bool include_specs; - /** - * Whether specifications should be put side-by-side with declarations or into a separate - * section. Defaults to false. - */ - bool specs_inlined; - /** - * Whether to include a dependency diagram. Defaults to false. - */ - bool include_dep_diagram; - /** - * Whether details should be put into collapsed sections. This is not supported by - * all markdown, but the github dialect. Defaults to false. - */ - bool collapsed_sections; - /** - * Package-relative path to an optional markdown template which is a used to create a - * landing page. Placeholders in this file are substituted as follows: `> {{move-toc}}` is - * replaced by a table of contents of all modules; `> {{move-index}}` is replaced by an index, - * and `> {{move-include NAME_OF_MODULE_OR_SCRIP}}` is replaced by the the full - * documentation of the named entity. (The given entity will not longer be placed in - * its own file, so this can be used to create a single manually populated page for - * the package.) - */ - ByteSliceView landing_page_template; - /** - * Package-relative path to a file whose content is added to each generated markdown file. - * This can contain common markdown references fpr this package (e.g. `[move-book]: `). - */ - ByteSliceView references_file; -} CompilerDocgenOption; - typedef struct { uint8_t _private[0]; } api_t; @@ -352,120 +219,12 @@ typedef struct { GoApi_vtable vtable; } GoApi; -typedef struct { - ByteSliceView verbosity; - /** - * Filters targets out from the package. Any module with a matching file name will - * be a target, similar as with `cargo test`. - */ - ByteSliceView filter; - /** - * Whether to display additional information in error reports. This may help - * debugging but also can make verification slower. - */ - bool trace; - /** - * Whether to use cvc5 as the smt solver backend. The environment variable - * `CVC5_EXE` should point to the binary. - */ - bool cvc5; - /** - * The depth until which stratified functions are expanded. - */ - size_t stratification_depth; - /** - * A seed for the prover. - */ - size_t random_seed; - /** - * The number of cores to use for parallel processing of verification conditions. - */ - size_t proc_cores; - /** - * A (soft) timeout for the solver, per verification condition, in seconds. - */ - size_t vc_timeout; - /** - * Whether to check consistency of specs by injecting impossible assertions. - */ - bool check_inconsistency; - /** - * Whether to keep loops as they are and pass them on to the underlying solver. - */ - bool keep_loops; - /** - * Number of iterations to unroll loops. set 0 to unset - */ - uint64_t loop_unroll; - /** - * Whether output for e.g. diagnosis shall be stable/redacted so it can be used in test - * output. - */ - bool stable_test_output; - /** - * Whether to dump intermediate step results to files. - */ - bool dump; - /** - * indicating that this prover run is for a test. - */ - bool for_test; -} CompilerProveOption; - -typedef struct { - /** - * A filter string to determine which unit tests to run. A unit test will be run only if it - * contains this string in its fully qualified (::::) name. - */ - ByteSliceView filter; - /** - * Report test statistics at the end of testing - */ - bool report_statistics; - /** - * Show the storage state at the end of execution of a failing test - */ - bool report_storage_on_error; - /** - * Ignore compiler's warning, and continue run tests - */ - bool ignore_compile_warnings; - /** - * Collect coverage information for later use with the various `package coverage` subcommands - */ - bool compute_coverage; -} CompilerTestOption; - vm_t *allocate_vm(size_t module_cache_capacity, size_t script_cache_capacity); -UnmanagedVector build_move_package(UnmanagedVector *errmsg, CompilerArgument compiler_args); - -UnmanagedVector clean_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - bool clean_cache, - bool clean_byproduct, - bool force); - UnmanagedVector convert_module_name(UnmanagedVector *errmsg, ByteSliceView precompiled, ByteSliceView module_name); -UnmanagedVector coverage_bytecode_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageBytecodeOption coverage_opt); - -UnmanagedVector coverage_source_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageSourceOption coverage_opt); - -UnmanagedVector coverage_summary_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageSummaryOption coverage_opt); - -UnmanagedVector create_new_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - ByteSliceView name_view); - UnmanagedVector decode_module_bytes(UnmanagedVector *errmsg, ByteSliceView module_bytes); UnmanagedVector decode_move_resource(Db db, @@ -482,10 +241,6 @@ UnmanagedVector decode_script_bytes(UnmanagedVector *errmsg, ByteSliceView scrip void destroy_unmanaged_vector(UnmanagedVector v); -UnmanagedVector docgen_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerDocgenOption docgen_opt); - UnmanagedVector execute_contract(vm_t *vm_ptr, Db db, GoApi api, @@ -525,25 +280,10 @@ UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, size_t length UnmanagedVector parse_struct_tag(UnmanagedVector *errmsg, ByteSliceView struct_tag_str); -UnmanagedVector prove_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerProveOption prove_opt); - UnmanagedVector read_module_info(UnmanagedVector *errmsg, ByteSliceView compiled); void release_vm(vm_t *vm); UnmanagedVector stringify_struct_tag(UnmanagedVector *errmsg, ByteSliceView struct_tag); -UnmanagedVector test_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerTestOption test_opt); - -/** - * Returns a version number of this library as a C string. - * - * The string is owned by libmovevm and must not be mutated or destroyed by the caller. - */ -const char *version_str(void); - #endif /* __LIBMOVEVM__ */ diff --git a/api/bindings_compiler.h b/api/bindings_compiler.h new file mode 100644 index 00000000..d7148367 --- /dev/null +++ b/api/bindings_compiler.h @@ -0,0 +1,341 @@ +/* (c) 2024 initia labs. Licensed under BUSL-1.1 */ + +#ifndef __LIBCOMPILER__ +#define __LIBCOMPILER__ + +/* Generated with cbindgen:0.26.0 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#include + + +enum CoverageOption { + /** + * Display a coverage summary for all modules in this package + */ + CoverageOption_Summary = 0, + /** + * Display coverage information about the module against source code + */ + CoverageOption_Source = 1, + /** + * Display coverage information about the module against disassembled bytecode + */ + CoverageOption_Bytecode = 2, +}; +typedef uint8_t CoverageOption; + +enum ErrnoValue { + ErrnoValue_Success = 0, + ErrnoValue_Other = 1, +}; +typedef int32_t ErrnoValue; + +/** + * An optional Vector type that requires explicit creation and destruction + * and can be sent via FFI. + * It can be created from `Option>` and be converted into `Option>`. + * + * This type is always created in Rust and always dropped in Rust. + * If Go code want to create it, it must instruct Rust to do so via the + * [`new_unmanaged_vector`] FFI export. If Go code wants to consume its data, + * it must create a copy and instruct Rust to destroy it via the + * [`destroy_unmanaged_vector`] FFI export. + * + * An UnmanagedVector is immutable. + * + * ## Ownership + * + * Ownership is the right and the obligation to destroy an `UnmanagedVector` + * exactly once. Both Rust and Go can create an `UnmanagedVector`, which gives + * then ownership. Sometimes it is necessary to transfer ownership. + * + * ### Transfer ownership from Rust to Go + * + * When an `UnmanagedVector` was created in Rust using [`UnmanagedVector::new`], [`UnmanagedVector::default`] + * or [`new_unmanaged_vector`], it can be passted to Go as a return value. + * Rust then has no chance to destroy the vector anymore, so ownership is transferred to Go. + * In Go, the data has to be copied to a garbage collected `[]byte`. Then the vector must be destroyed + * using [`destroy_unmanaged_vector`]. + * + * ### Transfer ownership from Go to Rust + * + * When Rust code calls into Go (using the vtable methods), return data or error messages must be created + * in Go. This is done by calling [`new_unmanaged_vector`] from Go, which copies data into a newly created + * `UnmanagedVector`. Since Go created it, it owns it. The ownership is then passed to Rust via the + * mutable return value pointers. On the Rust side, the vector is destroyed using [`UnmanagedVector::consume`]. + * + */ +typedef struct { + /** + * True if and only if this is None. If this is true, the other fields must be ignored. + */ + bool is_none; + uint8_t *ptr; + size_t len; + size_t cap; +} UnmanagedVector; + +/** + * A view into an externally owned byte slice (Go `[]byte`). + * Use this for the current call only. A view cannot be copied for safety reasons. + * If you need a copy, use [`ByteSliceView::to_owned`]. + * + * Go's nil value is fully supported, such that we can differentiate between nil and an empty slice. + */ +typedef struct { + /** + * True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored. + */ + bool is_nil; + const uint8_t *ptr; + size_t len; +} ByteSliceView; + +typedef struct { + /** + * Compile in 'dev' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used if + * this flag is set. This flag is useful for development of packages that expose named + * addresses that are not set to a specific value. + */ + bool dev_mode; + /** + * Compile in 'test' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used + * along with any code in the 'tests' directory. + */ + bool test_mode; + /** + * Generate documentation for packages + */ + bool generate_docs; + /** + * Generate ABIs for packages + */ + bool generate_abis; + /** + * Installation directory for compiled artifacts. Defaults to current directory. + */ + ByteSliceView install_dir; + /** + * Force recompilation of all packages + */ + bool force_recompilation; + /** + * Only fetch dependency repos to MOVE_HOME + */ + bool fetch_deps_only; + /** + * Skip fetching latest git dependencies + */ + bool skip_fetch_latest_git_deps; + /** + * bytecode version. set 0 to unset and to use default + */ + uint32_t bytecode_version; +} CompilerBuildConfig; + +typedef struct { + /** + * Path to a package which the command should be run with respect to. + */ + ByteSliceView package_path; + /** + * Print additional diagnostics if available. + */ + bool verbose; + /** + * Package build options + */ + CompilerBuildConfig build_config; +} CompilerArgument; + +typedef struct { + ByteSliceView module_name; +} CompilerCoverageBytecodeOption; + +typedef struct { + ByteSliceView module_name; +} CompilerCoverageSourceOption; + +typedef struct { + /** + * Whether function coverage summaries should be displayed + */ + bool functions; + /** + * Output CSV data of coverage + */ + bool output_csv; +} CompilerCoverageSummaryOption; + +typedef struct { + /** + * Whether to include private declarations and implementations into the generated + * documentation. Defaults to false. + */ + bool include_impl; + /** + * Whether to include specifications in the generated documentation. Defaults to false. + */ + bool include_specs; + /** + * Whether specifications should be put side-by-side with declarations or into a separate + * section. Defaults to false. + */ + bool specs_inlined; + /** + * Whether to include a dependency diagram. Defaults to false. + */ + bool include_dep_diagram; + /** + * Whether details should be put into collapsed sections. This is not supported by + * all markdown, but the github dialect. Defaults to false. + */ + bool collapsed_sections; + /** + * Package-relative path to an optional markdown template which is a used to create a + * landing page. Placeholders in this file are substituted as follows: `> {{move-toc}}` is + * replaced by a table of contents of all modules; `> {{move-index}}` is replaced by an index, + * and `> {{move-include NAME_OF_MODULE_OR_SCRIP}}` is replaced by the the full + * documentation of the named entity. (The given entity will not longer be placed in + * its own file, so this can be used to create a single manually populated page for + * the package.) + */ + ByteSliceView landing_page_template; + /** + * Package-relative path to a file whose content is added to each generated markdown file. + * This can contain common markdown references fpr this package (e.g. `[move-book]: `). + */ + ByteSliceView references_file; +} CompilerDocgenOption; + +typedef struct { + ByteSliceView verbosity; + /** + * Filters targets out from the package. Any module with a matching file name will + * be a target, similar as with `cargo test`. + */ + ByteSliceView filter; + /** + * Whether to display additional information in error reports. This may help + * debugging but also can make verification slower. + */ + bool trace; + /** + * Whether to use cvc5 as the smt solver backend. The environment variable + * `CVC5_EXE` should point to the binary. + */ + bool cvc5; + /** + * The depth until which stratified functions are expanded. + */ + size_t stratification_depth; + /** + * A seed for the prover. + */ + size_t random_seed; + /** + * The number of cores to use for parallel processing of verification conditions. + */ + size_t proc_cores; + /** + * A (soft) timeout for the solver, per verification condition, in seconds. + */ + size_t vc_timeout; + /** + * Whether to check consistency of specs by injecting impossible assertions. + */ + bool check_inconsistency; + /** + * Whether to keep loops as they are and pass them on to the underlying solver. + */ + bool keep_loops; + /** + * Number of iterations to unroll loops. set 0 to unset + */ + uint64_t loop_unroll; + /** + * Whether output for e.g. diagnosis shall be stable/redacted so it can be used in test + * output. + */ + bool stable_test_output; + /** + * Whether to dump intermediate step results to files. + */ + bool dump; + /** + * indicating that this prover run is for a test. + */ + bool for_test; +} CompilerProveOption; + +typedef struct { + /** + * A filter string to determine which unit tests to run. A unit test will be run only if it + * contains this string in its fully qualified (::::) name. + */ + ByteSliceView filter; + /** + * Report test statistics at the end of testing + */ + bool report_statistics; + /** + * Show the storage state at the end of execution of a failing test + */ + bool report_storage_on_error; + /** + * Ignore compiler's warning, and continue run tests + */ + bool ignore_compile_warnings; + /** + * Collect coverage information for later use with the various `package coverage` subcommands + */ + bool compute_coverage; +} CompilerTestOption; + +UnmanagedVector build_move_package(UnmanagedVector *errmsg, CompilerArgument compiler_args); + +UnmanagedVector clean_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + bool clean_cache, + bool clean_byproduct, + bool force); + +UnmanagedVector coverage_bytecode_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageBytecodeOption coverage_opt); + +UnmanagedVector coverage_source_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageSourceOption coverage_opt); + +UnmanagedVector coverage_summary_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageSummaryOption coverage_opt); + +UnmanagedVector create_new_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + ByteSliceView name_view); + +void destroy_unmanaged_vector(UnmanagedVector v); + +UnmanagedVector docgen_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerDocgenOption docgen_opt); + +UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, size_t length); + +UnmanagedVector prove_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerProveOption prove_opt); + +UnmanagedVector test_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerTestOption test_opt); + +#endif /* __LIBCOMPILER__ */ diff --git a/api/compiler.go b/api/compiler.go index 3774c93e..28cff054 100644 --- a/api/compiler.go +++ b/api/compiler.go @@ -1,7 +1,7 @@ package api // #include -// #include "bindings.h" +// #include "bindings_compiler.h" import "C" import ( diff --git a/api/link_linux_glibc_aarch64.go b/api/link_linux_glibc_aarch64.go index 87e825ca..45d1c506 100644 --- a/api/link_linux_glibc_aarch64.go +++ b/api/link_linux_glibc_aarch64.go @@ -2,5 +2,5 @@ package api -// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm.aarch64 +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm.aarch64 -lcompiler.aarch64 import "C" diff --git a/api/link_linux_glibc_x86_64.go b/api/link_linux_glibc_x86_64.go index 948613f2..cb6b6ae6 100644 --- a/api/link_linux_glibc_x86_64.go +++ b/api/link_linux_glibc_x86_64.go @@ -2,5 +2,5 @@ package api -// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm.x86_64 +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm.x86_64 -lcompiler.x86_64 import "C" diff --git a/api/link_macos.go b/api/link_macos.go index 554da236..ed4008a4 100644 --- a/api/link_macos.go +++ b/api/link_macos.go @@ -2,5 +2,5 @@ package api -// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm -lcompiler import "C" diff --git a/api/link_muslc.go b/api/link_muslc.go index c521042b..d4bc9dc7 100644 --- a/api/link_muslc.go +++ b/api/link_muslc.go @@ -2,5 +2,5 @@ package api -// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm_muslc +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lmovevm_muslc -lcompiler_muslc import "C" diff --git a/api/link_system.go b/api/link_system.go index 6c4c798c..9767adb4 100644 --- a/api/link_system.go +++ b/api/link_system.go @@ -2,5 +2,5 @@ package api -// #cgo LDFLAGS: -lmovevm +// #cgo LDFLAGS: -lmovevm -lcompiler import "C" diff --git a/api/version.go b/api/version.go deleted file mode 100644 index 6d468ff4..00000000 --- a/api/version.go +++ /dev/null @@ -1,16 +0,0 @@ -package api - -/* -#include "bindings.h" -*/ -import "C" - -func LibMoveVMVersion() (string, error) { - version_ptr, err := C.version_str() - if err != nil { - return "", err - } - // For C.GoString documentation see https://pkg.go.dev/cmd/cgo and - // https://gist.github.com/helinwang/2c7bd2867ea5110f70e6431a7c80cd9b - return C.GoString(version_ptr), nil -} diff --git a/api/version_test.go b/api/version_test.go deleted file mode 100644 index d3055125..00000000 --- a/api/version_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package api - -import ( - "regexp" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestLibMoveVMVersion(t *testing.T) { - version, err := LibMoveVMVersion() - require.NoError(t, err) - require.Regexp(t, regexp.MustCompile("^([0-9]+).([0-9]+).([0-9]+)(-[a-z0-9.]+)?$"), version) -} diff --git a/builders/Dockerfile.alpine b/builders/Dockerfile.alpine index 78b48753..b8c36c17 100644 --- a/builders/Dockerfile.alpine +++ b/builders/Dockerfile.alpine @@ -88,6 +88,5 @@ RUN mkdir /.cargo RUN chmod +rx /.cargo COPY guest/cargo-config /.cargo/config -WORKDIR /code/libmovevm - +WORKDIR /code CMD ["/opt/build_muslc.sh"] diff --git a/builders/Dockerfile.centos7 b/builders/Dockerfile.centos7 index 964f9ff1..1ba39a0e 100644 --- a/builders/Dockerfile.centos7 +++ b/builders/Dockerfile.centos7 @@ -86,6 +86,6 @@ RUN tar -zxvf ./openssl-1.1.1k.tar.gz \ RUN rm ./openssl-1.1.1k.tar.gz -WORKDIR /code/libmovevm +WORKDIR /code CMD ["/opt/build_linux.sh"] diff --git a/builders/Dockerfile.cross b/builders/Dockerfile.cross index 689c90e6..d765cce4 100644 --- a/builders/Dockerfile.cross +++ b/builders/Dockerfile.cross @@ -49,6 +49,6 @@ RUN mkdir /.cargo RUN chmod +rx /.cargo COPY guest/cargo-config /.cargo/config -WORKDIR /code/libmovevm +WORKDIR /code CMD ["bash", "-c", "echo 'Argument missing. Pass one build script (e.g. build_macos.sh) to docker run' && exit 1"] diff --git a/builders/guest/build_linux.sh b/builders/guest/build_linux.sh index f33d916f..753d6cae 100755 --- a/builders/guest/build_linux.sh +++ b/builders/guest/build_linux.sh @@ -15,8 +15,10 @@ export CC=clang export CXX=clang++ export OPENSSL_STATIC=1 export OPENSSL_DIR=/opt/x86_64-openssl -cargo build --release --target x86_64-unknown-linux-gnu -cp ../target/x86_64-unknown-linux-gnu/release/libmovevm.so artifacts/libmovevm.x86_64.so +(cd libmovevm && cargo build --release --target x86_64-unknown-linux-gnu) +(cd libcompiler && cargo build --release --target x86_64-unknown-linux-gnu) +cp ./target/x86_64-unknown-linux-gnu/release/libmovevm.so artifacts/libmovevm.x86_64.so +cp ./target/x86_64-unknown-linux-gnu/release/libcompiler.so artifacts/libcompiler.x86_64.so echo "Starting aarch64-unknown-linux-gnu build" export qemu_aarch64="qemu-aarch64 -L /usr/aarch64-linux-gnu" @@ -29,5 +31,7 @@ export OPENSSL_STATIC=1 export OPENSSL_DIR=/opt/aarch64-openssl # build libmovevm for aarch64 -cargo build --release --target aarch64-unknown-linux-gnu -cp -R ../target/aarch64-unknown-linux-gnu/release/libmovevm.so artifacts/libmovevm.aarch64.so +(cd libmovevm && cargo build --release --target aarch64-unknown-linux-gnu) +(cd libcompiler && cargo build --release --target aarch64-unknown-linux-gnu) +cp -R ./target/aarch64-unknown-linux-gnu/release/libmovevm.so artifacts/libmovevm.aarch64.so +cp -R ./target/aarch64-unknown-linux-gnu/release/libcompiler.so artifacts/libcompiler.aarch64.so diff --git a/builders/guest/build_macos.sh b/builders/guest/build_macos.sh index ee8a769d..ed016718 100755 --- a/builders/guest/build_macos.sh +++ b/builders/guest/build_macos.sh @@ -17,14 +17,20 @@ export LIBZ_SYS_STATIC=1 echo "Starting aarch64-apple-darwin build" export CC=aarch64-apple-darwin20.4-clang export CXX=aarch64-apple-darwin20.4-clang++ -cargo build --release --target aarch64-apple-darwin +(cd libmovevm && cargo build --release --target aarch64-apple-darwin) +(cd libcompiler && cargo build --release --target aarch64-apple-darwin) echo "Starting x86_64-apple-darwin build" export CC=o64-clang export CXX=o64-clang++ -cargo build --release --target x86_64-apple-darwin +(cd libmovevm && cargo build --release --target x86_64-apple-darwin) +(cd libcompiler && cargo build --release --target x86_64-apple-darwin) # Create a universal library with both archs lipo -output artifacts/libmovevm.dylib -create \ - ../target/x86_64-apple-darwin/release/deps/libmovevm.dylib \ - ../target/aarch64-apple-darwin/release/deps/libmovevm.dylib + ./target/x86_64-apple-darwin/release/deps/libmovevm.dylib \ + ./target/aarch64-apple-darwin/release/deps/libmovevm.dylib + +lipo -output artifacts/libcompiler.dylib -create \ + ./target/x86_64-apple-darwin/release/deps/libcompiler.dylib \ + ./target/aarch64-apple-darwin/release/deps/libcompiler.dylib diff --git a/builders/guest/build_muslc.sh b/builders/guest/build_muslc.sh index 6330e6d4..ba856aeb 100644 --- a/builders/guest/build_muslc.sh +++ b/builders/guest/build_muslc.sh @@ -9,8 +9,10 @@ export PKG_CONFIG_ALLOW_CROSS=1 echo "Starting x86_64-unknown-linux-musl build" export OPENSSL_DIR=/opt/x86_64-openssl -cargo build --release --target x86_64-unknown-linux-musl --example muslc -cp ../target/x86_64-unknown-linux-musl/release/examples/libmuslc.a artifacts/libmovevm_muslc.x86_64.a +(cd libmovevm && cargo build --release --target x86_64-unknown-linux-musl --example movevmstatic) +(cd libcompiler && cargo build --release --target x86_64-unknown-linux-musl --example compilerstatic) +cp ./target/x86_64-unknown-linux-musl/release/examples/libmovevmstatic.a artifacts/libmovevm_muslc.x86_64.a +cp ./target/x86_64-unknown-linux-musl/release/examples/libcompilerstatic.a artifacts/libcompiler_muslc.x86_64.a # See https://github.com/CosmWasm/wasmvm/issues/222#issuecomment-880616953 for two approaches to # enable stripping through cargo (if that is desired). @@ -19,5 +21,7 @@ echo "Starting aarch64-unknown-linux-musl build" export CC=aarch64-linux-musl-gcc export OPENSSL_DIR=/opt/aarch64-openssl -cargo build --release --target aarch64-unknown-linux-musl --example muslc -cp ../target/aarch64-unknown-linux-musl/release/examples/libmuslc.a artifacts/libmovevm_muslc.aarch64.a +(cd libmovevm && cargo build --release --target aarch64-unknown-linux-musl --example movevmstatic) +(cd libcompiler && cargo build --release --target aarch64-unknown-linux-musl --example compilerstatic) +cp ./target/aarch64-unknown-linux-musl/release/examples/libmovevmstatic.a artifacts/libmovevm_muslc.aarch64.a +cp ./target/aarch64-unknown-linux-musl/release/examples/libcompilerstatic.a artifacts/libcompiler_muslc.aarch64.a diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index 47502144..06489ec5 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "initia-move-compiler" version = "0.1.0" -edition = "2021" -repository = "https://github.com/initia-labs/movevm/tree/main/crates/compiler" -rust-version = { workspace = true } +description = "Initia Move Compiler" +repository = "https://github.com/initia-labs/movevm" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +edition = { workspace = true } +rust-version = { workspace = true } [dependencies] anyhow = { workspace = true } @@ -23,15 +23,16 @@ log = { workspace = true } tempfile = { workspace = true } once_cell = { workspace = true } -initia-move-natives = { workspace = true } -initia-move-gas = { workspace = true } +initia-move-natives = { workspace = true, features = ["testing"] } +initia-move-gas = { workspace = true, features = ["testing"] } initia-move-types = { workspace = true } + move-command-line-common = { workspace = true } move-cli = { workspace = true } move-core-types = { workspace = true } move-package = { workspace = true } move-unit-test = { workspace = true } -move-vm-runtime = { workspace = true } +move-vm-runtime = { workspace = true, features = ["testing"] } move-vm-types = { workspace = true } move-vm-test-utils = { workspace = true } move-binary-format = { workspace = true } @@ -46,11 +47,3 @@ move-resource-viewer = { workspace = true } [dev-dependencies] serial_test = { workspace = true } -initia-move-natives = { workspace = true, features = ["testing"] } -initia-move-gas = { workspace = true, features = ["testing"] } -move-vm-runtime = { workspace = true, features = ["testing"] } - -[build-dependencies] -initia-move-natives = { workspace = true, features = ["testing"] } -initia-move-gas = { workspace = true, features = ["testing"] } -move-vm-runtime = { workspace = true, features = ["testing"] } diff --git a/crates/natives/src/query.rs b/crates/natives/src/query.rs index 9ebfd35a..cdc28a0b 100644 --- a/crates/natives/src/query.rs +++ b/crates/natives/src/query.rs @@ -11,14 +11,7 @@ use move_vm_types::{ }; use smallvec::{smallvec, SmallVec}; -use std::collections::{BTreeMap, VecDeque}; - -#[cfg(feature = "testing")] -use bech32::{Bech32, Hrp}; -#[cfg(feature = "testing")] -use move_core_types::account_address::AccountAddress; -#[cfg(feature = "testing")] -use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; use crate::{ interface::{ @@ -232,6 +225,15 @@ fn partial_error(code: StatusCode, msg: impl ToString) -> PartialVMError { PartialVMError::new(code).with_message(msg.to_string()) } +#[cfg(feature = "testing")] +use bech32::{Bech32, Hrp}; +#[cfg(feature = "testing")] +use move_core_types::account_address::AccountAddress; +#[cfg(feature = "testing")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "testing")] +use std::collections::BTreeMap; + #[cfg(feature = "testing")] use sha3::{Digest, Sha3_256}; diff --git a/libcompiler/Cargo.toml b/libcompiler/Cargo.toml new file mode 100644 index 00000000..d63bfc35 --- /dev/null +++ b/libcompiler/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "compiler" +version = "0.1.0" +publish = false +description = "Initia Move Compiler" +homepage = "https://initia.xyz/" +repository = "https://github.com/initia-labs/movevm" +exclude = [".gitignore"] + +edition = { workspace = true } +rust-version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +# the example is to allow us to compile a muslc static lib with the same codebase as we compile the +# normal dynamic libs (best workaround I could find to override crate-type on the command line) +[[example]] +name = "compilerstatic" +path = "src/examples/compilerstatic.rs" +crate-type = ["staticlib"] + +[features] +default = [] +# This feature requires Rust nightly because it depends on the unstable backtrace feature. +backtraces = [] + +[dependencies] +errno = { workspace = true } +thiserror = { workspace = true } +log = { workspace = true, features = [ + "max_level_debug", + "release_max_level_warn", +] } + +initia-move-compiler = { workspace = true } + +move-core-types = { workspace = true } +move-cli = { workspace = true } +move-docgen = { workspace = true } +move-package = { workspace = true } + +[build-dependencies] +cbindgen = { workspace = true } diff --git a/libcompiler/bindings_compiler.h b/libcompiler/bindings_compiler.h new file mode 100644 index 00000000..d7148367 --- /dev/null +++ b/libcompiler/bindings_compiler.h @@ -0,0 +1,341 @@ +/* (c) 2024 initia labs. Licensed under BUSL-1.1 */ + +#ifndef __LIBCOMPILER__ +#define __LIBCOMPILER__ + +/* Generated with cbindgen:0.26.0 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#include + + +enum CoverageOption { + /** + * Display a coverage summary for all modules in this package + */ + CoverageOption_Summary = 0, + /** + * Display coverage information about the module against source code + */ + CoverageOption_Source = 1, + /** + * Display coverage information about the module against disassembled bytecode + */ + CoverageOption_Bytecode = 2, +}; +typedef uint8_t CoverageOption; + +enum ErrnoValue { + ErrnoValue_Success = 0, + ErrnoValue_Other = 1, +}; +typedef int32_t ErrnoValue; + +/** + * An optional Vector type that requires explicit creation and destruction + * and can be sent via FFI. + * It can be created from `Option>` and be converted into `Option>`. + * + * This type is always created in Rust and always dropped in Rust. + * If Go code want to create it, it must instruct Rust to do so via the + * [`new_unmanaged_vector`] FFI export. If Go code wants to consume its data, + * it must create a copy and instruct Rust to destroy it via the + * [`destroy_unmanaged_vector`] FFI export. + * + * An UnmanagedVector is immutable. + * + * ## Ownership + * + * Ownership is the right and the obligation to destroy an `UnmanagedVector` + * exactly once. Both Rust and Go can create an `UnmanagedVector`, which gives + * then ownership. Sometimes it is necessary to transfer ownership. + * + * ### Transfer ownership from Rust to Go + * + * When an `UnmanagedVector` was created in Rust using [`UnmanagedVector::new`], [`UnmanagedVector::default`] + * or [`new_unmanaged_vector`], it can be passted to Go as a return value. + * Rust then has no chance to destroy the vector anymore, so ownership is transferred to Go. + * In Go, the data has to be copied to a garbage collected `[]byte`. Then the vector must be destroyed + * using [`destroy_unmanaged_vector`]. + * + * ### Transfer ownership from Go to Rust + * + * When Rust code calls into Go (using the vtable methods), return data or error messages must be created + * in Go. This is done by calling [`new_unmanaged_vector`] from Go, which copies data into a newly created + * `UnmanagedVector`. Since Go created it, it owns it. The ownership is then passed to Rust via the + * mutable return value pointers. On the Rust side, the vector is destroyed using [`UnmanagedVector::consume`]. + * + */ +typedef struct { + /** + * True if and only if this is None. If this is true, the other fields must be ignored. + */ + bool is_none; + uint8_t *ptr; + size_t len; + size_t cap; +} UnmanagedVector; + +/** + * A view into an externally owned byte slice (Go `[]byte`). + * Use this for the current call only. A view cannot be copied for safety reasons. + * If you need a copy, use [`ByteSliceView::to_owned`]. + * + * Go's nil value is fully supported, such that we can differentiate between nil and an empty slice. + */ +typedef struct { + /** + * True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored. + */ + bool is_nil; + const uint8_t *ptr; + size_t len; +} ByteSliceView; + +typedef struct { + /** + * Compile in 'dev' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used if + * this flag is set. This flag is useful for development of packages that expose named + * addresses that are not set to a specific value. + */ + bool dev_mode; + /** + * Compile in 'test' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used + * along with any code in the 'tests' directory. + */ + bool test_mode; + /** + * Generate documentation for packages + */ + bool generate_docs; + /** + * Generate ABIs for packages + */ + bool generate_abis; + /** + * Installation directory for compiled artifacts. Defaults to current directory. + */ + ByteSliceView install_dir; + /** + * Force recompilation of all packages + */ + bool force_recompilation; + /** + * Only fetch dependency repos to MOVE_HOME + */ + bool fetch_deps_only; + /** + * Skip fetching latest git dependencies + */ + bool skip_fetch_latest_git_deps; + /** + * bytecode version. set 0 to unset and to use default + */ + uint32_t bytecode_version; +} CompilerBuildConfig; + +typedef struct { + /** + * Path to a package which the command should be run with respect to. + */ + ByteSliceView package_path; + /** + * Print additional diagnostics if available. + */ + bool verbose; + /** + * Package build options + */ + CompilerBuildConfig build_config; +} CompilerArgument; + +typedef struct { + ByteSliceView module_name; +} CompilerCoverageBytecodeOption; + +typedef struct { + ByteSliceView module_name; +} CompilerCoverageSourceOption; + +typedef struct { + /** + * Whether function coverage summaries should be displayed + */ + bool functions; + /** + * Output CSV data of coverage + */ + bool output_csv; +} CompilerCoverageSummaryOption; + +typedef struct { + /** + * Whether to include private declarations and implementations into the generated + * documentation. Defaults to false. + */ + bool include_impl; + /** + * Whether to include specifications in the generated documentation. Defaults to false. + */ + bool include_specs; + /** + * Whether specifications should be put side-by-side with declarations or into a separate + * section. Defaults to false. + */ + bool specs_inlined; + /** + * Whether to include a dependency diagram. Defaults to false. + */ + bool include_dep_diagram; + /** + * Whether details should be put into collapsed sections. This is not supported by + * all markdown, but the github dialect. Defaults to false. + */ + bool collapsed_sections; + /** + * Package-relative path to an optional markdown template which is a used to create a + * landing page. Placeholders in this file are substituted as follows: `> {{move-toc}}` is + * replaced by a table of contents of all modules; `> {{move-index}}` is replaced by an index, + * and `> {{move-include NAME_OF_MODULE_OR_SCRIP}}` is replaced by the the full + * documentation of the named entity. (The given entity will not longer be placed in + * its own file, so this can be used to create a single manually populated page for + * the package.) + */ + ByteSliceView landing_page_template; + /** + * Package-relative path to a file whose content is added to each generated markdown file. + * This can contain common markdown references fpr this package (e.g. `[move-book]: `). + */ + ByteSliceView references_file; +} CompilerDocgenOption; + +typedef struct { + ByteSliceView verbosity; + /** + * Filters targets out from the package. Any module with a matching file name will + * be a target, similar as with `cargo test`. + */ + ByteSliceView filter; + /** + * Whether to display additional information in error reports. This may help + * debugging but also can make verification slower. + */ + bool trace; + /** + * Whether to use cvc5 as the smt solver backend. The environment variable + * `CVC5_EXE` should point to the binary. + */ + bool cvc5; + /** + * The depth until which stratified functions are expanded. + */ + size_t stratification_depth; + /** + * A seed for the prover. + */ + size_t random_seed; + /** + * The number of cores to use for parallel processing of verification conditions. + */ + size_t proc_cores; + /** + * A (soft) timeout for the solver, per verification condition, in seconds. + */ + size_t vc_timeout; + /** + * Whether to check consistency of specs by injecting impossible assertions. + */ + bool check_inconsistency; + /** + * Whether to keep loops as they are and pass them on to the underlying solver. + */ + bool keep_loops; + /** + * Number of iterations to unroll loops. set 0 to unset + */ + uint64_t loop_unroll; + /** + * Whether output for e.g. diagnosis shall be stable/redacted so it can be used in test + * output. + */ + bool stable_test_output; + /** + * Whether to dump intermediate step results to files. + */ + bool dump; + /** + * indicating that this prover run is for a test. + */ + bool for_test; +} CompilerProveOption; + +typedef struct { + /** + * A filter string to determine which unit tests to run. A unit test will be run only if it + * contains this string in its fully qualified (::::) name. + */ + ByteSliceView filter; + /** + * Report test statistics at the end of testing + */ + bool report_statistics; + /** + * Show the storage state at the end of execution of a failing test + */ + bool report_storage_on_error; + /** + * Ignore compiler's warning, and continue run tests + */ + bool ignore_compile_warnings; + /** + * Collect coverage information for later use with the various `package coverage` subcommands + */ + bool compute_coverage; +} CompilerTestOption; + +UnmanagedVector build_move_package(UnmanagedVector *errmsg, CompilerArgument compiler_args); + +UnmanagedVector clean_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + bool clean_cache, + bool clean_byproduct, + bool force); + +UnmanagedVector coverage_bytecode_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageBytecodeOption coverage_opt); + +UnmanagedVector coverage_source_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageSourceOption coverage_opt); + +UnmanagedVector coverage_summary_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerCoverageSummaryOption coverage_opt); + +UnmanagedVector create_new_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + ByteSliceView name_view); + +void destroy_unmanaged_vector(UnmanagedVector v); + +UnmanagedVector docgen_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerDocgenOption docgen_opt); + +UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, size_t length); + +UnmanagedVector prove_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerProveOption prove_opt); + +UnmanagedVector test_move_package(UnmanagedVector *errmsg, + CompilerArgument compiler_args, + CompilerTestOption test_opt); + +#endif /* __LIBCOMPILER__ */ diff --git a/libcompiler/build.rs b/libcompiler/build.rs new file mode 100644 index 00000000..3a2e18fc --- /dev/null +++ b/libcompiler/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::generate(crate_dir) + .expect("Unable to generate compiler bindings") + .write_to_file("bindings_compiler.h"); +} diff --git a/libcompiler/cbindgen.toml b/libcompiler/cbindgen.toml new file mode 100644 index 00000000..1e9b1141 --- /dev/null +++ b/libcompiler/cbindgen.toml @@ -0,0 +1,152 @@ +# This is a template cbindgen.toml file with all of the default values. +# Some values are commented out because their absence is the real default. +# +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + + +language = "C" + + + +############## Options for Wrapping the Contents of the Header ################# + +header = "/* (c) 2024 initia labs. Licensed under BUSL-1.1 */" +# trailer = "/* Text to put at the end of the generated file */" +include_guard = "__LIBCOMPILER__" +# pragma_once = true +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = true +#namespace = "my_namespace" +namespaces = [] +using_namespaces = [] +sys_includes = [] +includes = [] +no_includes = false +after_includes = "" +#cpp_compat = true + + + +############################ Code Style Options ################################ + +braces = "SameLine" +line_length = 100 +tab_width = 2 +documentation = true +documentation_style = "auto" +documentation_length = "full" +line_endings = "LF" # also "CR", "CRLF", "Native" + + + + +############################# Codegen Options ################################## + +style = "type" # default: both +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +usize_is_size_t = true + + + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + + +[export] +# FIXME: not sure why they are not exported by default. +include = ["ByteSliceView", "ErrnoValue", "CoverageOption", "InitiaCompilerArgument", "InitiaCompilerBuildConfig", "InitiaCompilerTestOption", "InitiaCompilerProveOption"] +exclude = [] +item_types = [] +renaming_overrides_prefixing = false + + + +[export.rename] + + + +[export.body] + + +[export.mangle] + + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# no_return = "NO_RETURN" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" +#sort_by = "Name" + + + + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +derive_constructor = false +derive_eq = false +derive_neq = false +derive_lt = false +derive_lte = false +derive_gt = false +derive_gte = false + + + + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +add_sentinel = false +prefix_with_name = false +derive_helper_methods = false +derive_const_casts = false +derive_mut_casts = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_destructor = false +derive_tagged_enum_copy_constructor = false +#enum_class = true +private_default_tagged_enum_constructor = false + + + + +[const] +allow_static_const = true +#allow_constexpr = false +#sort_by = "Name" + + + + +[macro_expansion] +bitflags = false + + + + + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +exclude = [] +clean = false +extra_bindings = [] + + + +[parse.expand] +crates = [] +all_features = false +default_features = true +features = [] diff --git a/libcompiler/clippy.toml b/libcompiler/clippy.toml new file mode 100644 index 00000000..85355417 --- /dev/null +++ b/libcompiler/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 13 diff --git a/libmovevm/src/compiler.rs b/libcompiler/src/compiler.rs similarity index 98% rename from libmovevm/src/compiler.rs rename to libcompiler/src/compiler.rs index 834106e9..59e6dfe0 100644 --- a/libmovevm/src/compiler.rs +++ b/libcompiler/src/compiler.rs @@ -1,11 +1,13 @@ use log::LevelFilter; -use move_docgen::DocgenOptions; use std::{path::Path, str::FromStr}; -use crate::{error::Error, ByteSliceView}; - use initia_move_compiler::{self, prover::ProverOptions}; +use move_docgen::DocgenOptions; + +use crate::error::CompilerError; +use crate::memory::ByteSliceView; + use move_cli::{ base::{ coverage::{Coverage, CoverageSummaryOptions}, @@ -17,7 +19,7 @@ use move_package::{Architecture, BuildConfig, CompilerConfig}; pub use initia_move_compiler::Command; -pub fn execute(move_args: Move, cmd: Command) -> Result, Error> { +pub fn execute(move_args: Move, cmd: Command) -> Result, CompilerError> { let action = cmd.to_string(); let verbose = move_args.verbose; @@ -25,12 +27,12 @@ pub fn execute(move_args: Move, cmd: Command) -> Result, Error> { Ok(_) => Ok(Vec::from("ok")), Err(e) => { if verbose { - Err(Error::backend_failure(format!( + Err(CompilerError::compiler_failure(format!( "failed to {}: {:?}", action, e ))) } else { - Err(Error::backend_failure(format!( + Err(CompilerError::compiler_failure(format!( "failed to {}: {}", action, e ))) diff --git a/libcompiler/src/error.rs b/libcompiler/src/error.rs new file mode 100644 index 00000000..a97cabef --- /dev/null +++ b/libcompiler/src/error.rs @@ -0,0 +1,79 @@ +use errno::{set_errno, Errno}; +use thiserror::Error; + +use crate::memory::UnmanagedVector; + +#[derive(Error, Debug)] +pub enum CompilerError { + #[error("Caught panic")] + Panic {}, + #[error("failure occurred from compiler: {}", msg)] + CompilerFailure { msg: String }, +} + +impl CompilerError { + pub fn panic() -> Self { + Self::Panic {} + } + + pub fn compiler_failure(msg: S) -> Self { + Self::CompilerFailure { + msg: msg.to_string(), + } + } +} + +/// cbindgen:prefix-with-name +#[repr(i32)] +pub enum ErrnoValue { + Success = 0, + Other = 1, +} + +pub fn clear_error() { + set_errno(Errno(ErrnoValue::Success as i32)); +} + +pub fn set_error(err: CompilerError, error_msg: Option<&mut UnmanagedVector>) { + if let Some(error_msg) = error_msg { + if error_msg.is_some() { + panic!( + "There is an old error message in the given pointer that has not been \ + cleaned up. Error message pointers should not be reused for multiple calls." + ) + } + + let msg: Vec = err.to_string().into(); + *error_msg = UnmanagedVector::new(Some(msg)); + } else { + // The caller provided a nil pointer for the error message. + // That's not nice but we can live with it. + } + + set_errno(Errno(ErrnoValue::Other as i32)); +} + +/// If `result` is Ok, this returns the binary representation of the Ok value and clears [errno]. +/// Otherwise it returns an empty vector, writes the error message to `error_msg` and sets [errno]. +/// +/// [errno]: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCgoErrorReturns +#[allow(dead_code)] +pub fn handle_c_error_binary( + result: Result, + error_msg: Option<&mut UnmanagedVector>, +) -> Vec +where + T: Into>, +{ + // TODO remove this logger + match result { + Ok(value) => { + clear_error(); + value.into() + } + Err(error) => { + set_error(error, error_msg); + Vec::new() + } + } +} diff --git a/libcompiler/src/examples/compilerstatic.rs b/libcompiler/src/examples/compilerstatic.rs new file mode 100644 index 00000000..b5d2d76b --- /dev/null +++ b/libcompiler/src/examples/compilerstatic.rs @@ -0,0 +1,5 @@ +// This is an entry point into the library in order to set +// crate-type = ["staticlib"] via a command line argument. +// See `--example compilerstatic` + +pub use compiler::*; diff --git a/libcompiler/src/interface.rs b/libcompiler/src/interface.rs new file mode 100644 index 00000000..93946e5b --- /dev/null +++ b/libcompiler/src/interface.rs @@ -0,0 +1,180 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; + +use crate::compiler::{ + self, CompilerArgument, CompilerCoverageBytecodeOption, CompilerCoverageSourceOption, + CompilerCoverageSummaryOption, CompilerDocgenOption, CompilerProveOption, CompilerTestOption, +}; + +use crate::compiler::Command; +use crate::error::{handle_c_error_binary, CompilerError as Error}; +use crate::memory::{ByteSliceView, UnmanagedVector}; + +use initia_move_compiler::{self, New}; +use move_cli::base::build::Build; +use move_cli::base::test::Test; + +#[no_mangle] +pub extern "C" fn build_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, +) -> UnmanagedVector { + let cmd = Command::Build(Build); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn test_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + test_opt: CompilerTestOption, +) -> UnmanagedVector { + let mut test_opt: Test = test_opt.into(); + if compiler_args.verbose { + test_opt.verbose_mode = compiler_args.verbose; + } + + let cmd = Command::Test(test_opt); + let res: Result<_, Error> = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn coverage_summary_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + coverage_opt: CompilerCoverageSummaryOption, +) -> UnmanagedVector { + let cmd = Command::Coverage(coverage_opt.into()); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn coverage_source_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + coverage_opt: CompilerCoverageSourceOption, +) -> UnmanagedVector { + let cmd = Command::Coverage(coverage_opt.into()); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn coverage_bytecode_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + coverage_opt: CompilerCoverageBytecodeOption, +) -> UnmanagedVector { + let cmd = Command::Coverage(coverage_opt.into()); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn docgen_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + docgen_opt: CompilerDocgenOption, +) -> UnmanagedVector { + let cmd = Command::Document(docgen_opt.into()); + + let res: Result<_, Error> = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn create_new_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + name_view: ByteSliceView, +) -> UnmanagedVector { + let name: Option = name_view.into(); + + let cmd = Command::New(New { + name: name.unwrap_or_default(), + }); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn clean_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + clean_cache: bool, + clean_byproduct: bool, + force: bool, +) -> UnmanagedVector { + let cmd = Command::Clean(initia_move_compiler::Clean { + clean_cache, + clean_byproduct, + force, + }); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} + +#[no_mangle] +pub extern "C" fn prove_move_package( + errmsg: Option<&mut UnmanagedVector>, + compiler_args: CompilerArgument, + prove_opt: CompilerProveOption, +) -> UnmanagedVector { + let cmd = Command::Prove(prove_opt.into()); + + let res = catch_unwind(AssertUnwindSafe(move || { + compiler::execute(compiler_args.into(), cmd) + })) + .unwrap_or_else(|_| Err(Error::panic())); + + let ret = handle_c_error_binary(res, errmsg); + UnmanagedVector::new(Some(ret)) +} diff --git a/libcompiler/src/lib.rs b/libcompiler/src/lib.rs new file mode 100644 index 00000000..334a2898 --- /dev/null +++ b/libcompiler/src/lib.rs @@ -0,0 +1,15 @@ +#![cfg_attr(feature = "backtraces", feature(backtrace))] +#![allow(clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)] + +mod compiler; +mod error; +mod interface; +mod memory; + +// We only interact with this crate via `extern "C"` interfaces, not those public +// exports. There are no guarantees those exports are stable. +// We keep them here such that we can access them in the docs (`cargo doc`). +pub use memory::{destroy_unmanaged_vector, new_unmanaged_vector, ByteSliceView, UnmanagedVector}; + +#[cfg(test)] +mod tests; diff --git a/libcompiler/src/memory.rs b/libcompiler/src/memory.rs new file mode 100644 index 00000000..c8231fd7 --- /dev/null +++ b/libcompiler/src/memory.rs @@ -0,0 +1,330 @@ +use std::mem; +use std::path::Path; +use std::path::PathBuf; +use std::slice; + +use move_core_types::language_storage::TypeTag; +use move_core_types::parser::parse_transaction_argument; +use move_core_types::parser::parse_transaction_arguments; +use move_core_types::parser::parse_type_tag; +use move_core_types::parser::parse_type_tags; +use move_core_types::transaction_argument::TransactionArgument; + +// It is a copy of the one from cosmwasm/lib crate. We owe them a lot! + +/// A view into an externally owned byte slice (Go `[]byte`). +/// Use this for the current call only. A view cannot be copied for safety reasons. +/// If you need a copy, use [`ByteSliceView::to_owned`]. +/// +/// Go's nil value is fully supported, such that we can differentiate between nil and an empty slice. +#[repr(C)] +pub struct ByteSliceView { + /// True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored. + is_nil: bool, + ptr: *const u8, + len: usize, +} + +impl ByteSliceView { + /// ByteSliceViews are only constructed in Go. This constructor is a way to mimic the behaviour + /// when testing FFI calls from Rust. It must not be used in production code. + #[cfg(test)] + pub fn new(source: &[u8]) -> Self { + Self { + is_nil: false, + ptr: source.as_ptr(), + len: source.len(), + } + } + + /// ByteSliceViews are only constructed in Go. This constructor is a way to mimic the behaviour + /// when testing FFI calls from Rust. It must not be used in production code. + #[cfg(test)] + pub fn nil() -> Self { + Self { + is_nil: true, + ptr: std::ptr::null::(), + len: 0, + } + } + + /// Provides a reference to the included data to be parsed or copied elsewhere + /// This is safe as long as the `ByteSliceView` is constructed correctly. + pub fn read(&self) -> Option<&[u8]> { + if self.is_nil { + None + } else { + Some(unsafe { slice::from_raw_parts(self.ptr, self.len) }) + } + } + + /// Creates an owned copy that can safely be stored and mutated. + #[allow(dead_code)] + pub fn to_owned(&self) -> Option> { + self.read().map(|slice| slice.to_owned()) + } +} + +impl From for Option { + fn from(val: ByteSliceView) -> Self { + val.read().map(|s| String::from_utf8(s.to_vec()).unwrap()) + } +} + +impl From for Option> { + fn from(val: ByteSliceView) -> Self { + val.read().map(|s| { + String::from_utf8(s.to_vec()) + .unwrap() + .split(',') + .map(|o| o.to_string()) + .collect() + }) + } +} + +impl From for Option { + fn from(val: ByteSliceView) -> Self { + val.read() + .map(|s| Path::new(&String::from_utf8(s.to_vec()).unwrap()).to_path_buf()) + } +} + +impl From for Option { + fn from(val: ByteSliceView) -> Self { + val.read() + .map(|s| parse_type_tag(std::str::from_utf8(s).unwrap()).unwrap()) + } +} + +impl From for Option> { + fn from(val: ByteSliceView) -> Self { + val.read() + .map(|s| parse_type_tags(std::str::from_utf8(s).unwrap()).unwrap()) + } +} + +impl From for Option { + fn from(val: ByteSliceView) -> Self { + val.read() + .map(|s| parse_transaction_argument(std::str::from_utf8(s).unwrap()).unwrap()) + } +} + +impl From for Option> { + fn from(val: ByteSliceView) -> Self { + val.read() + .map(|s| parse_transaction_arguments(std::str::from_utf8(s).unwrap()).unwrap()) + } +} + +/// An optional Vector type that requires explicit creation and destruction +/// and can be sent via FFI. +/// It can be created from `Option>` and be converted into `Option>`. +/// +/// This type is always created in Rust and always dropped in Rust. +/// If Go code want to create it, it must instruct Rust to do so via the +/// [`new_unmanaged_vector`] FFI export. If Go code wants to consume its data, +/// it must create a copy and instruct Rust to destroy it via the +/// [`destroy_unmanaged_vector`] FFI export. +/// +/// An UnmanagedVector is immutable. +/// +/// ## Ownership +/// +/// Ownership is the right and the obligation to destroy an `UnmanagedVector` +/// exactly once. Both Rust and Go can create an `UnmanagedVector`, which gives +/// then ownership. Sometimes it is necessary to transfer ownership. +/// +/// ### Transfer ownership from Rust to Go +/// +/// When an `UnmanagedVector` was created in Rust using [`UnmanagedVector::new`], [`UnmanagedVector::default`] +/// or [`new_unmanaged_vector`], it can be passted to Go as a return value. +/// Rust then has no chance to destroy the vector anymore, so ownership is transferred to Go. +/// In Go, the data has to be copied to a garbage collected `[]byte`. Then the vector must be destroyed +/// using [`destroy_unmanaged_vector`]. +/// +/// ### Transfer ownership from Go to Rust +/// +/// When Rust code calls into Go (using the vtable methods), return data or error messages must be created +/// in Go. This is done by calling [`new_unmanaged_vector`] from Go, which copies data into a newly created +/// `UnmanagedVector`. Since Go created it, it owns it. The ownership is then passed to Rust via the +/// mutable return value pointers. On the Rust side, the vector is destroyed using [`UnmanagedVector::consume`]. +/// +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct UnmanagedVector { + /// True if and only if this is None. If this is true, the other fields must be ignored. + is_none: bool, + ptr: *mut u8, + len: usize, + cap: usize, +} + +impl UnmanagedVector { + /// Consumes this optional vector for manual management. + /// This is a zero-copy operation. + pub fn new(source: Option>) -> Self { + match source { + Some(data) => { + let (ptr, len, cap) = { + // Can be replaced with Vec::into_raw_parts when stable + // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_raw_parts + let mut data = mem::ManuallyDrop::new(data); + (data.as_mut_ptr(), data.len(), data.capacity()) + }; + Self { + is_none: false, + ptr, + len, + cap, + } + } + None => Self { + is_none: true, + ptr: std::ptr::null_mut::(), + len: 0, + cap: 0, + }, + } + } + + pub fn is_none(&self) -> bool { + self.is_none + } + + pub fn is_some(&self) -> bool { + !self.is_none() + } + + /// Takes this UnmanagedVector and turns it into a regular, managed Rust vector. + /// Calling this on two copies of UnmanagedVector leads to double free crashes. + pub fn consume(self) -> Option> { + if self.is_none { + None + } else { + Some(unsafe { Vec::from_raw_parts(self.ptr, self.len, self.cap) }) + } + } +} + +impl Default for UnmanagedVector { + fn default() -> Self { + Self::new(None) + } +} + +#[no_mangle] +pub extern "C" fn new_unmanaged_vector( + nil: bool, + ptr: *const u8, + length: usize, +) -> UnmanagedVector { + if nil { + UnmanagedVector::new(None) + } else if length == 0 { + UnmanagedVector::new(Some(Vec::new())) + } else { + let external_memory = unsafe { slice::from_raw_parts(ptr, length) }; + let copy = Vec::from(external_memory); + UnmanagedVector::new(Some(copy)) + } +} + +#[no_mangle] +pub extern "C" fn destroy_unmanaged_vector(v: UnmanagedVector) { + let _ = v.consume(); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn byte_slice_view_read_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[] as &[u8]); + + let view = ByteSliceView::nil(); + assert!(view.read().is_none()); + } + + #[test] + fn byte_slice_view_to_owned_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), vec![0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), Vec::::new()); + + let view = ByteSliceView::nil(); + assert!(view.to_owned().is_none()); + } + + #[test] + fn unmanaged_vector_new_works() { + // With data + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none); + assert_ne!(x.ptr as usize, 0); + assert_eq!(x.len, 2); + assert_eq!(x.cap, 2); + + // Empty data + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none); + assert_eq!(x.ptr as usize, 0x01); // We probably don't get any guarantee for this, but good to know where the 0x01 marker pointer can come from + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + + // None + let x = UnmanagedVector::new(None); + assert!(x.is_none); + assert_eq!(x.ptr as usize, 0); + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + } + + #[test] + fn unmanaged_vector_is_some_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(x.is_some()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(x.is_some()); + let x = UnmanagedVector::new(None); + assert!(!x.is_some()); + } + + #[test] + fn unmanaged_vector_is_none_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(None); + assert!(x.is_none()); + } + + #[test] + fn unmanaged_vector_consume_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert_eq!(x.consume(), Some(vec![0x11u8, 0x22])); + let x = UnmanagedVector::new(Some(vec![])); + assert_eq!(x.consume(), Some(Vec::::new())); + let x = UnmanagedVector::new(None); + assert_eq!(x.consume(), None); + } + + #[test] + fn unmanaged_vector_defaults_to_none() { + let x = UnmanagedVector::default(); + assert_eq!(x.consume(), None); + } +} diff --git a/libcompiler/src/tests/error_tests.rs b/libcompiler/src/tests/error_tests.rs new file mode 100644 index 00000000..d2d1533f --- /dev/null +++ b/libcompiler/src/tests/error_tests.rs @@ -0,0 +1,107 @@ +use crate::{ + error::{handle_c_error_binary, CompilerError, ErrnoValue}, + UnmanagedVector, +}; + +// CompilerError tests + +use errno::errno; + +#[test] +fn panic_works() { + let error = CompilerError::panic(); + match error { + CompilerError::Panic { .. } => {} + _ => panic!("expect different error"), + } +} + +#[test] +fn vm_err_works_for_errors() { + // No public interface exists to generate a BackendError directly + let error = CompilerError::compiler_failure("Failed to compile"); + match error { + CompilerError::CompilerFailure { msg, .. } => { + assert_eq!(msg, "Failed to compile"); + } + _ => panic!("expect different error"), + } +} + +#[test] +fn handle_c_error_binary_works() { + // Ok (non-empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, CompilerError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + + // Ok (empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, CompilerError> = Ok(vec![]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (non-empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], CompilerError> = Ok(b"foobar"); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::from(b"foobar" as &[u8])); + let _ = error_msg.consume(); + + // Ok (empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], CompilerError> = Ok(b""); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, CompilerError> = Err(CompilerError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], CompilerError> = Err(CompilerError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); +} + +#[test] +fn handle_c_error_binary_clears_an_old_error() { + // Err + let mut error_msg = UnmanagedVector::default(); + let res: Result, CompilerError> = Err(CompilerError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok + let mut error_msg = UnmanagedVector::default(); + let res: Result, CompilerError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); +} diff --git a/libcompiler/src/tests/memory_tests.rs b/libcompiler/src/tests/memory_tests.rs new file mode 100644 index 00000000..64a51de3 --- /dev/null +++ b/libcompiler/src/tests/memory_tests.rs @@ -0,0 +1,95 @@ +use crate::memory::{ByteSliceView, UnmanagedVector}; + +#[test] +fn byte_slice_view_read_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[] as &[u8]); + + let view = ByteSliceView::nil(); + assert!(view.read().is_none()); +} + +#[test] +fn byte_slice_view_to_owned_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), vec![0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), Vec::::new()); + + let view = ByteSliceView::nil(); + assert!(view.to_owned().is_none()); +} + +#[test] +fn unmanaged_vector_new_works() { + // With data + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none()); + /* + assert_ne!(x.ptr as usize, 0); + assert_eq!(x.len, 2); + assert_eq!(x.cap, 2); + */ + + // Empty data + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none()); + /* + assert_eq!(x.ptr as usize, 0x01); // We probably don't get any guarantee for this, but good to know where the 0x01 marker pointer can come from + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + */ + + // None + let x = UnmanagedVector::new(None); + assert!(x.is_none()); + /* + assert_eq!(x.ptr as usize, 0); + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + */ +} + +#[test] +fn unmanaged_vector_is_some_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(x.is_some()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(x.is_some()); + let x = UnmanagedVector::new(None); + assert!(!x.is_some()); +} + +#[test] +fn unmanaged_vector_is_none_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(None); + assert!(x.is_none()); +} + +#[test] +fn unmanaged_vector_consume_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert_eq!(x.consume(), Some(vec![0x11u8, 0x22])); + let x = UnmanagedVector::new(Some(vec![])); + assert_eq!(x.consume(), Some(Vec::::new())); + let x = UnmanagedVector::new(None); + assert_eq!(x.consume(), None); +} + +#[test] +fn unmanaged_vector_defaults_to_none() { + let x = UnmanagedVector::default(); + assert_eq!(x.consume(), None); +} diff --git a/libcompiler/src/tests/mod.rs b/libcompiler/src/tests/mod.rs new file mode 100644 index 00000000..47dacc83 --- /dev/null +++ b/libcompiler/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod error_tests; +pub mod memory_tests; diff --git a/libmovevm/.gitignore b/libmovevm/.gitignore deleted file mode 100644 index 1f2eb564..00000000 --- a/libmovevm/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Build results -target/ -artifacts/ diff --git a/libmovevm/Cargo.toml b/libmovevm/Cargo.toml index 533fc71b..6dac713a 100644 --- a/libmovevm/Cargo.toml +++ b/libmovevm/Cargo.toml @@ -1,30 +1,30 @@ [package] name = "movevm" version = "0.1.0" -edition = "2021" -rust-version = { workspace = true } -description = "Initia VM Engine" +publish = false +description = "Initia Move VM Engine" homepage = "https://initia.xyz/" repository = "https://github.com/initia-labs/movevm" -license-file = "LICENSE" exclude = [".gitignore"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +edition = { workspace = true } +rust-version = { workspace = true } # the example is to allow us to compile a muslc static lib with the same codebase as we compile the # normal dynamic libs (best workaround I could find to override crate-type on the command line) [[example]] -name = "muslc" -path = "src/lib.rs" +name = "movevmstatic" +path = "src/examples/movevmstatic.rs" crate-type = ["staticlib"] +[lib] +crate-type = ["cdylib", "rlib"] + [features] default = [] # This feature requires Rust nightly because it depends on the unstable backtrace feature. backtraces = [] -[lib] -crate-type = ["cdylib"] - [dependencies] anyhow = { workspace = true } bytes = { workspace = true } @@ -40,7 +40,6 @@ log = { workspace = true, features = [ "release_max_level_warn", ] } -initia-move-compiler = { workspace = true } initia-move-vm = { workspace = true, features = ["backtraces"] } initia-move-natives = { workspace = true } initia-move-types = { workspace = true } @@ -48,9 +47,6 @@ initia-move-gas = { workspace = true } initia-move-storage = { workspace = true } move-core-types = { workspace = true } -move-cli = { workspace = true } -move-docgen = { workspace = true } -move-package = { workspace = true } move-resource-viewer = { workspace = true } move-binary-format = { workspace = true } diff --git a/libmovevm/README.md b/libmovevm/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/libmovevm/bindings.h b/libmovevm/bindings.h index 7b37817e..c1f376b6 100644 --- a/libmovevm/bindings.h +++ b/libmovevm/bindings.h @@ -1,4 +1,4 @@ -/* (c) 2022 initia labs. Licensed under BUSL-1.1 */ +/* (c) 2024 initia labs. Licensed under BUSL-1.1 */ #ifndef __LIBMOVEVM__ #define __LIBMOVEVM__ @@ -14,22 +14,6 @@ #include -enum CoverageOption { - /** - * Display a coverage summary for all modules in this package - */ - CoverageOption_Summary = 0, - /** - * Display coverage information about the module against source code - */ - CoverageOption_Source = 1, - /** - * Display coverage information about the module against disassembled bytecode - */ - CoverageOption_Bytecode = 2, -}; -typedef uint8_t CoverageOption; - enum ErrnoValue { ErrnoValue_Success = 0, ErrnoValue_Other = 1, @@ -137,82 +121,6 @@ typedef struct { size_t len; } ByteSliceView; -typedef struct { - /** - * Compile in 'dev' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used if - * this flag is set. This flag is useful for development of packages that expose named - * addresses that are not set to a specific value. - */ - bool dev_mode; - /** - * Compile in 'test' mode. The 'dev-addresses' and 'dev-dependencies' fields will be used - * along with any code in the 'tests' directory. - */ - bool test_mode; - /** - * Generate documentation for packages - */ - bool generate_docs; - /** - * Generate ABIs for packages - */ - bool generate_abis; - /** - * Installation directory for compiled artifacts. Defaults to current directory. - */ - ByteSliceView install_dir; - /** - * Force recompilation of all packages - */ - bool force_recompilation; - /** - * Only fetch dependency repos to MOVE_HOME - */ - bool fetch_deps_only; - /** - * Skip fetching latest git dependencies - */ - bool skip_fetch_latest_git_deps; - /** - * bytecode version. set 0 to unset and to use default - */ - uint32_t bytecode_version; -} CompilerBuildConfig; - -typedef struct { - /** - * Path to a package which the command should be run with respect to. - */ - ByteSliceView package_path; - /** - * Print additional diagnostics if available. - */ - bool verbose; - /** - * Package build options - */ - CompilerBuildConfig build_config; -} CompilerArgument; - -typedef struct { - ByteSliceView module_name; -} CompilerCoverageBytecodeOption; - -typedef struct { - ByteSliceView module_name; -} CompilerCoverageSourceOption; - -typedef struct { - /** - * Whether function coverage summaries should be displayed - */ - bool functions; - /** - * Output CSV data of coverage - */ - bool output_csv; -} CompilerCoverageSummaryOption; - typedef struct { uint8_t _private[0]; } db_t; @@ -267,47 +175,6 @@ typedef struct { Db_vtable vtable; } Db; -typedef struct { - /** - * Whether to include private declarations and implementations into the generated - * documentation. Defaults to false. - */ - bool include_impl; - /** - * Whether to include specifications in the generated documentation. Defaults to false. - */ - bool include_specs; - /** - * Whether specifications should be put side-by-side with declarations or into a separate - * section. Defaults to false. - */ - bool specs_inlined; - /** - * Whether to include a dependency diagram. Defaults to false. - */ - bool include_dep_diagram; - /** - * Whether details should be put into collapsed sections. This is not supported by - * all markdown, but the github dialect. Defaults to false. - */ - bool collapsed_sections; - /** - * Package-relative path to an optional markdown template which is a used to create a - * landing page. Placeholders in this file are substituted as follows: `> {{move-toc}}` is - * replaced by a table of contents of all modules; `> {{move-index}}` is replaced by an index, - * and `> {{move-include NAME_OF_MODULE_OR_SCRIP}}` is replaced by the the full - * documentation of the named entity. (The given entity will not longer be placed in - * its own file, so this can be used to create a single manually populated page for - * the package.) - */ - ByteSliceView landing_page_template; - /** - * Package-relative path to a file whose content is added to each generated markdown file. - * This can contain common markdown references fpr this package (e.g. `[move-book]: `). - */ - ByteSliceView references_file; -} CompilerDocgenOption; - typedef struct { uint8_t _private[0]; } api_t; @@ -352,120 +219,12 @@ typedef struct { GoApi_vtable vtable; } GoApi; -typedef struct { - ByteSliceView verbosity; - /** - * Filters targets out from the package. Any module with a matching file name will - * be a target, similar as with `cargo test`. - */ - ByteSliceView filter; - /** - * Whether to display additional information in error reports. This may help - * debugging but also can make verification slower. - */ - bool trace; - /** - * Whether to use cvc5 as the smt solver backend. The environment variable - * `CVC5_EXE` should point to the binary. - */ - bool cvc5; - /** - * The depth until which stratified functions are expanded. - */ - size_t stratification_depth; - /** - * A seed for the prover. - */ - size_t random_seed; - /** - * The number of cores to use for parallel processing of verification conditions. - */ - size_t proc_cores; - /** - * A (soft) timeout for the solver, per verification condition, in seconds. - */ - size_t vc_timeout; - /** - * Whether to check consistency of specs by injecting impossible assertions. - */ - bool check_inconsistency; - /** - * Whether to keep loops as they are and pass them on to the underlying solver. - */ - bool keep_loops; - /** - * Number of iterations to unroll loops. set 0 to unset - */ - uint64_t loop_unroll; - /** - * Whether output for e.g. diagnosis shall be stable/redacted so it can be used in test - * output. - */ - bool stable_test_output; - /** - * Whether to dump intermediate step results to files. - */ - bool dump; - /** - * indicating that this prover run is for a test. - */ - bool for_test; -} CompilerProveOption; - -typedef struct { - /** - * A filter string to determine which unit tests to run. A unit test will be run only if it - * contains this string in its fully qualified (::::) name. - */ - ByteSliceView filter; - /** - * Report test statistics at the end of testing - */ - bool report_statistics; - /** - * Show the storage state at the end of execution of a failing test - */ - bool report_storage_on_error; - /** - * Ignore compiler's warning, and continue run tests - */ - bool ignore_compile_warnings; - /** - * Collect coverage information for later use with the various `package coverage` subcommands - */ - bool compute_coverage; -} CompilerTestOption; - vm_t *allocate_vm(size_t module_cache_capacity, size_t script_cache_capacity); -UnmanagedVector build_move_package(UnmanagedVector *errmsg, CompilerArgument compiler_args); - -UnmanagedVector clean_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - bool clean_cache, - bool clean_byproduct, - bool force); - UnmanagedVector convert_module_name(UnmanagedVector *errmsg, ByteSliceView precompiled, ByteSliceView module_name); -UnmanagedVector coverage_bytecode_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageBytecodeOption coverage_opt); - -UnmanagedVector coverage_source_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageSourceOption coverage_opt); - -UnmanagedVector coverage_summary_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerCoverageSummaryOption coverage_opt); - -UnmanagedVector create_new_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - ByteSliceView name_view); - UnmanagedVector decode_module_bytes(UnmanagedVector *errmsg, ByteSliceView module_bytes); UnmanagedVector decode_move_resource(Db db, @@ -482,10 +241,6 @@ UnmanagedVector decode_script_bytes(UnmanagedVector *errmsg, ByteSliceView scrip void destroy_unmanaged_vector(UnmanagedVector v); -UnmanagedVector docgen_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerDocgenOption docgen_opt); - UnmanagedVector execute_contract(vm_t *vm_ptr, Db db, GoApi api, @@ -525,25 +280,10 @@ UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, size_t length UnmanagedVector parse_struct_tag(UnmanagedVector *errmsg, ByteSliceView struct_tag_str); -UnmanagedVector prove_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerProveOption prove_opt); - UnmanagedVector read_module_info(UnmanagedVector *errmsg, ByteSliceView compiled); void release_vm(vm_t *vm); UnmanagedVector stringify_struct_tag(UnmanagedVector *errmsg, ByteSliceView struct_tag); -UnmanagedVector test_move_package(UnmanagedVector *errmsg, - CompilerArgument compiler_args, - CompilerTestOption test_opt); - -/** - * Returns a version number of this library as a C string. - * - * The string is owned by libmovevm and must not be mutated or destroyed by the caller. - */ -const char *version_str(void); - #endif /* __LIBMOVEVM__ */ diff --git a/libmovevm/cbindgen.toml b/libmovevm/cbindgen.toml index cf000408..cd6e285e 100644 --- a/libmovevm/cbindgen.toml +++ b/libmovevm/cbindgen.toml @@ -12,7 +12,7 @@ language = "C" ############## Options for Wrapping the Contents of the Header ################# -header = "/* (c) 2022 initia labs. Licensed under BUSL-1.1 */" +header = "/* (c) 2024 initia labs. Licensed under BUSL-1.1 */" # trailer = "/* Text to put at the end of the generated file */" include_guard = "__LIBMOVEVM__" # pragma_once = true @@ -58,7 +58,7 @@ usize_is_size_t = true [export] # FIXME: not sure why they are not exported by default. -include = ["ByteSliceView", "U8SliceView", "Db", "db_t", "GoError", "ErrnoValue", "GoApi", "CoverageOption", "InitiaCompilerArgument", "InitiaCompilerBuildConfig", "InitiaCompilerTestOption", "InitiaCompilerProveOption", "InitiaConcretizeMode", "InitiaExperimentalSubcommandType", "AccountType"] +include = ["ByteSliceView", "U8SliceView", "Db", "db_t", "GoError", "ErrnoValue", "GoApi"] exclude = [] item_types = [] renaming_overrides_prefixing = false diff --git a/libmovevm/src/error/mod.rs b/libmovevm/src/error/mod.rs index 6e542371..43b4dbc1 100644 --- a/libmovevm/src/error/mod.rs +++ b/libmovevm/src/error/mod.rs @@ -4,3 +4,6 @@ mod rust; pub use go::GoError; pub use rust::{handle_c_error_binary, handle_c_error_default, RustError as Error}; + +#[cfg(test)] +pub use rust::ErrnoValue; diff --git a/libmovevm/src/error/rust.rs b/libmovevm/src/error/rust.rs index 069f6647..a598be30 100644 --- a/libmovevm/src/error/rust.rs +++ b/libmovevm/src/error/rust.rs @@ -141,7 +141,7 @@ impl From for RustError { /// cbindgen:prefix-with-name #[repr(i32)] -enum ErrnoValue { +pub enum ErrnoValue { Success = 0, Other = 1, } diff --git a/libmovevm/src/examples/movevmstatic.rs b/libmovevm/src/examples/movevmstatic.rs new file mode 100644 index 00000000..7a519dd1 --- /dev/null +++ b/libmovevm/src/examples/movevmstatic.rs @@ -0,0 +1,5 @@ +// This is an entry point into the library in order to set +// crate-type = ["staticlib"] via a command line argument. +// See `--example movevmstatic` + +pub use movevm::*; diff --git a/libmovevm/src/interface.rs b/libmovevm/src/interface.rs index 71bb73e0..c9128434 100644 --- a/libmovevm/src/interface.rs +++ b/libmovevm/src/interface.rs @@ -1,18 +1,11 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; -use std::path::Path; use crate::args::VM_ARG; -use crate::compiler::{ - self, CompilerArgument, CompilerCoverageBytecodeOption, CompilerCoverageSourceOption, - CompilerCoverageSummaryOption, CompilerDocgenOption, CompilerProveOption, CompilerTestOption, -}; use crate::error::handle_c_error_default; use crate::error::{handle_c_error_binary, Error}; use crate::move_api::handler as api_handler; use crate::{api::GoApi, vm, ByteSliceView, Db, UnmanagedVector}; -use crate::compiler::Command; -use initia_move_compiler::{self, New}; use initia_move_types::entry_function::EntryFunction; use initia_move_types::env::Env; use initia_move_types::message::Message; @@ -20,11 +13,7 @@ use initia_move_types::module::ModuleBundle; use initia_move_types::script::Script; use initia_move_types::view_function::ViewFunction; use initia_move_vm::MoveVM; -use move_cli::base::build::Build; -use move_cli::base::test::Test; -use move_cli::Move; use move_core_types::account_address::AccountAddress; -use move_package::BuildConfig; #[allow(non_camel_case_types)] #[repr(C)] @@ -286,172 +275,6 @@ pub extern "C" fn decode_script_bytes( UnmanagedVector::new(Some(ret)) } -#[no_mangle] -pub extern "C" fn build_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, -) -> UnmanagedVector { - let cmd = Command::Build(Build); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn test_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - test_opt: CompilerTestOption, -) -> UnmanagedVector { - let mut test_opt: Test = test_opt.into(); - if compiler_args.verbose { - test_opt.verbose_mode = compiler_args.verbose; - } - - let cmd = Command::Test(test_opt); - let res: Result<_, Error> = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn coverage_summary_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - coverage_opt: CompilerCoverageSummaryOption, -) -> UnmanagedVector { - let cmd = Command::Coverage(coverage_opt.into()); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn coverage_source_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - coverage_opt: CompilerCoverageSourceOption, -) -> UnmanagedVector { - let cmd = Command::Coverage(coverage_opt.into()); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn coverage_bytecode_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - coverage_opt: CompilerCoverageBytecodeOption, -) -> UnmanagedVector { - let cmd = Command::Coverage(coverage_opt.into()); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn docgen_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - docgen_opt: CompilerDocgenOption, -) -> UnmanagedVector { - let cmd = Command::Document(docgen_opt.into()); - - let res: Result<_, Error> = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn create_new_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - name_view: ByteSliceView, -) -> UnmanagedVector { - let name: Option = name_view.into(); - - let cmd = Command::New(New { - name: name.unwrap_or_default(), - }); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn clean_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - clean_cache: bool, - clean_byproduct: bool, - force: bool, -) -> UnmanagedVector { - let cmd = Command::Clean(initia_move_compiler::Clean { - clean_cache, - clean_byproduct, - force, - }); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - -#[no_mangle] -pub extern "C" fn prove_move_package( - errmsg: Option<&mut UnmanagedVector>, - compiler_args: CompilerArgument, - prove_opt: CompilerProveOption, -) -> UnmanagedVector { - let cmd = Command::Prove(prove_opt.into()); - - let res = catch_unwind(AssertUnwindSafe(move || { - compiler::execute(compiler_args.into(), cmd) - })) - .unwrap_or_else(|_| Err(Error::panic())); - - let ret = handle_c_error_binary(res, errmsg); - UnmanagedVector::new(Some(ret)) -} - #[no_mangle] pub extern "C" fn parse_struct_tag( errmsg: Option<&mut UnmanagedVector>, @@ -481,23 +304,3 @@ pub extern "C" fn stringify_struct_tag( let ret = handle_c_error_binary(res, errmsg); UnmanagedVector::new(Some(ret)) } - -// -// internal functions -// - -#[allow(dead_code)] -fn generate_default_move_cli(package_path_slice: Option, verbose: bool) -> Move { - let package_path = match package_path_slice { - None => None, - Some(slice) => slice - .read() - .map(|s| Path::new(&String::from_utf8(s.to_vec()).unwrap()).to_path_buf()), - }; - - Move { - package_path, - verbose, - build_config: BuildConfig::default(), - } -} diff --git a/libmovevm/src/lib.rs b/libmovevm/src/lib.rs index 581d6978..9a76141a 100644 --- a/libmovevm/src/lib.rs +++ b/libmovevm/src/lib.rs @@ -3,7 +3,6 @@ mod api; mod args; -mod compiler; mod db; mod error; mod interface; @@ -13,10 +12,15 @@ mod move_api; mod result; mod storage; mod table_storage; -mod version; mod vm; +// We only interact with this crate via `extern "C"` interfaces, not those public +// exports. There are no guarantees those exports are stable. +// We keep them here such that we can access them in the docs (`cargo doc`). +pub use api::{GoApi, GoApi_vtable}; pub use db::{db_t, Db}; +pub use error::GoError; +pub use iterator::Iterator_vtable; pub use memory::{ destroy_unmanaged_vector, new_unmanaged_vector, ByteSliceView, U8SliceView, UnmanagedVector, }; diff --git a/libmovevm/src/memory.rs b/libmovevm/src/memory.rs index b1fd70b0..278e0150 100644 --- a/libmovevm/src/memory.rs +++ b/libmovevm/src/memory.rs @@ -2,7 +2,6 @@ use std::mem; use std::path::Path; use std::path::PathBuf; use std::slice; -use std::str::FromStr; use move_core_types::language_storage::TypeTag; use move_core_types::parser::parse_transaction_argument; @@ -11,8 +10,6 @@ use move_core_types::parser::parse_type_tag; use move_core_types::parser::parse_type_tags; use move_core_types::transaction_argument::TransactionArgument; -use crate::move_api::move_types::MoveType; - // It is a copy of the one from cosmwasm/lib crate. We owe them a lot! /// A view into an externally owned byte slice (Go `[]byte`). @@ -93,25 +90,6 @@ impl From for Option { } } -impl From for Option { - fn from(val: ByteSliceView) -> Self { - val.read() - .map(|s| MoveType::from_str(std::str::from_utf8(s).unwrap()).unwrap()) - } -} - -impl From for Option> { - fn from(val: ByteSliceView) -> Self { - match val.read() { - Some(s) => { - let slice = std::str::from_utf8(s).unwrap().split(','); - Some(slice.map(|s| MoveType::from_str(s).unwrap()).collect()) - } - None => None, - } - } -} - impl From for Option { fn from(val: ByteSliceView) -> Self { val.read() diff --git a/libmovevm/src/tests/error_tests.rs b/libmovevm/src/tests/error_tests.rs index 07df249e..08f415fe 100644 --- a/libmovevm/src/tests/error_tests.rs +++ b/libmovevm/src/tests/error_tests.rs @@ -1,4 +1,7 @@ -use crate::{error::GoError, UnmanagedVector}; +use crate::{ + error::{handle_c_error_binary, handle_c_error_default, GoError}, + UnmanagedVector, +}; use initia_move_types::errors::BackendError; // GoError test @@ -89,314 +92,262 @@ fn go_error_into_result_works() { } // RustError tests -/* - use errno::errno; - use std::str; - - #[test] - fn empty_arg_works() { - let error = RustError::empty_arg("gas"); - match error { - RustError::EmptyArg { name, .. } => { - assert_eq!(name, "gas"); - } - _ => panic!("expect different error"), - } - } - #[test] - fn invalid_utf8_works_for_strings() { - let error = RustError::invalid_utf8("my text"); - match error { - RustError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "my text"); - } - _ => panic!("expect different error"), - } - } +use crate::error::{ErrnoValue, Error as RustError}; +use errno::errno; +use std::str; - #[test] - fn invalid_utf8_works_for_errors() { - let original = String::from_utf8(vec![0x80]).unwrap_err(); - let error = RustError::invalid_utf8(original); - match error { - RustError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0"); - } - _ => panic!("expect different error"), +#[test] +fn invalid_utf8_works_for_strings() { + let error = RustError::invalid_utf8("my text"); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "my text"); } + _ => panic!("expect different error"), } +} - #[test] - fn panic_works() { - let error = RustError::panic(); - match error { - RustError::Panic { .. } => {} - _ => panic!("expect different error"), +#[test] +fn invalid_utf8_works_for_errors() { + let original = String::from_utf8(vec![0x80]).unwrap_err(); + let error = RustError::invalid_utf8(original); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0"); } + _ => panic!("expect different error"), } +} - #[test] - fn unset_arg_works() { - let error = RustError::unset_arg("gas"); - match error { - RustError::UnsetArg { name, .. } => { - assert_eq!(name, "gas"); - } - _ => panic!("expect different error"), - } +#[test] +fn panic_works() { + let error = RustError::panic(); + match error { + RustError::Panic { .. } => {} + _ => panic!("expect different error"), } +} - #[test] - fn vm_err_works_for_strings() { - let error = RustError::vm_err("my text"); - match error { - RustError::VmErr { msg, .. } => { - assert_eq!(msg, "my text"); - } - _ => panic!("expect different error"), +#[test] +fn unset_arg_works() { + let error = RustError::unset_arg("gas"); + match error { + RustError::UnsetArg { name, .. } => { + assert_eq!(name, "gas"); } + _ => panic!("expect different error"), } +} - #[test] - fn vm_err_works_for_errors() { - // No public interface exists to generate a BackendError directly - let original: BackendError = BackendError::out_of_gas().into(); - let error = RustError::vm_err(original); - match error { - RustError::VmErr { msg, .. } => { - assert_eq!(msg, "Ran out of gas during contract execution"); - } - _ => panic!("expect different error"), +#[test] +fn vm_err_works_for_strings() { + let error = RustError::vm_err("my text"); + match error { + RustError::VmError { msg, .. } => { + assert_eq!(msg, "my text"); } + _ => panic!("expect different error"), } +} + +// Tests of `impl From for RustError` converters - // Tests of `impl From for RustError` converters - - #[test] - fn from_std_str_utf8error_works() { - let error: RustError = str::from_utf8(b"Hello \xF0\x90\x80World") - .unwrap_err() - .into(); - match error { - RustError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") - } - _ => panic!("expect different error"), +#[test] +fn from_std_str_utf8error_works() { + #[allow(invalid_from_utf8)] + let error: RustError = str::from_utf8(b"Hello \xF0\x90\x80World") + .unwrap_err() + .into(); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") } + _ => panic!("expect different error"), } +} - #[test] - fn from_std_string_fromutf8error_works() { - let error: RustError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec()) - .unwrap_err() - .into(); - match error { - RustError::InvalidUtf8 { msg, .. } => { - assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") - } - _ => panic!("expect different error"), +#[test] +fn from_std_string_fromutf8error_works() { + let error: RustError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec()) + .unwrap_err() + .into(); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") } + _ => panic!("expect different error"), } +} - #[test] - fn handle_c_error_binary_works() { - // Ok (non-empty vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); - let _ = error_msg.consume(); - - // Ok (empty vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![]); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok (non-empty slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Ok(b"foobar"); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::from(b"foobar" as &[u8])); - let _ = error_msg.consume(); - - // Ok (empty slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Ok(b""); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok (checksum) - let mut error_msg = UnmanagedVector::default(); - let res: Result = Ok(Checksum::from([ - 0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34, - 0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28, - 0x84, 0x22, 0x71, 0x04, - ])); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!( - data, - vec![ - 0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34, - 0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28, - 0x84, 0x22, 0x71, 0x04, - ] - ); - let _ = error_msg.consume(); - - // Err (vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Err(RustError::panic()); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Err (slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Err(RustError::panic()); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Err (checksum) - let mut error_msg = UnmanagedVector::default(); - let res: Result = Err(RustError::panic()); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - } +#[test] +fn handle_c_error_binary_works() { + // Ok (non-empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + + // Ok (empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (non-empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b"foobar"); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::from(b"foobar" as &[u8])); + let _ = error_msg.consume(); + + // Ok (empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b""); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); +} - #[test] - fn handle_c_error_binary_clears_an_old_error() { - // Err - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Err(RustError::panic()); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); - let data = handle_c_error_binary(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); - let _ = error_msg.consume(); - } +#[test] +fn handle_c_error_binary_clears_an_old_error() { + // Err + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); +} - #[test] - fn handle_c_error_default_works() { - // Ok (non-empty vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); - let _ = error_msg.consume(); - - // Ok (empty vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![]); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok (non-empty slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Ok(b"foobar"); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::from(b"foobar" as &[u8])); - let _ = error_msg.consume(); - - // Ok (empty slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Ok(b""); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok (unit) - let mut error_msg = UnmanagedVector::default(); - let res: Result<(), RustError> = Ok(()); - let _data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - let _ = error_msg.consume(); - - // Err (vector) - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Err(RustError::panic()); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Err (slice) - let mut error_msg = UnmanagedVector::default(); - let res: Result<&[u8], RustError> = Err(RustError::panic()); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Err (unit) - let mut error_msg = UnmanagedVector::default(); - let res: Result<(), RustError> = Err(RustError::panic()); - let _data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - let _ = error_msg.consume(); - } +#[test] +fn handle_c_error_default_works() { + // Ok (non-empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + + // Ok (empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (non-empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b"foobar"); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::from(b"foobar" as &[u8])); + let _ = error_msg.consume(); + + // Ok (empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b""); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (unit) + let mut error_msg = UnmanagedVector::default(); + let res: Result<(), RustError> = Ok(()); + handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Err (vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (unit) + let mut error_msg = UnmanagedVector::default(); + let res: Result<(), RustError> = Err(RustError::panic()); + handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + let _ = error_msg.consume(); +} - #[test] - fn handle_c_error_default_clears_an_old_error() { - // Err - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Err(RustError::panic()); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Other as i32); - assert!(error_msg.is_some()); - assert_eq!(data, Vec::::new()); - let _ = error_msg.consume(); - - // Ok - let mut error_msg = UnmanagedVector::default(); - let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); - let data = handle_c_error_default(res, Some(&mut error_msg)); - assert_eq!(errno().0, ErrnoValue::Success as i32); - assert!(error_msg.is_none()); - assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); - let _ = error_msg.consume(); - } -*/ +#[test] +fn handle_c_error_default_clears_an_old_error() { + // Err + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); +} diff --git a/libmovevm/src/tests/mod.rs b/libmovevm/src/tests/mod.rs index 487128c3..091c4515 100644 --- a/libmovevm/src/tests/mod.rs +++ b/libmovevm/src/tests/mod.rs @@ -1,4 +1,3 @@ pub mod error_tests; pub mod memory_tests; pub mod precompile_test; -pub mod version_tests; diff --git a/libmovevm/src/tests/version_tests.rs b/libmovevm/src/tests/version_tests.rs deleted file mode 100644 index 90444a57..00000000 --- a/libmovevm/src/tests/version_tests.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::ffi::CStr; - -use crate::version::version_str; -use crate::version::VERSION; - -#[test] -fn test_version_str() { - let ver = unsafe { CStr::from_ptr(version_str()) }; - let mut verstr = ver.to_str().expect("test failed").to_owned(); - verstr.push('\0'); - assert_eq!(verstr.as_str(), VERSION); -} diff --git a/libmovevm/src/version.rs b/libmovevm/src/version.rs deleted file mode 100644 index f658aeca..00000000 --- a/libmovevm/src/version.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::os::raw::c_char; - -pub static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); // Add trailing NULL byte for C string - -/// Returns a version number of this library as a C string. -/// -/// The string is owned by libmovevm and must not be mutated or destroyed by the caller. -#[no_mangle] -pub extern "C" fn version_str() -> *const c_char { - VERSION.as_ptr() as *const _ -}