diff --git a/scripts/AsmJsonImportTest.sh b/scripts/AsmJsonImportTest.sh new file mode 100755 index 000000000..7709197f0 --- /dev/null +++ b/scripts/AsmJsonImportTest.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +# Bash script to test the asm-json-import input mode of the compiler by +# first exporting a .sol file to JSON that containing assembly json and corresponding bytecode, +# then the compiler is invoked in assembly json import mode `--import-asm-json` and uses the previously +# generated assembly json as input, where the corresponding bytecode output will be stored. +# Finally, the originally generated bytecode will be compared with the one that was generated by using the +# assembly json file as input. + +set -eo pipefail +READLINK=readlink +if [[ "$OSTYPE" == "darwin"* ]]; then + READLINK=greadlink +fi +REPO_ROOT=$(${READLINK} -f "$(dirname "$0")"/..) +# shellcheck source=scripts/common_import.sh +source "${REPO_ROOT}/scripts/common_import.sh" + +SEMANTICTESTS_DIR="${REPO_ROOT}/test/libsolidity/semanticTests" +NSOURCES="$(find "$SEMANTICTESTS_DIR" -type f | wc -l)" + +init_import_tests + +# function tests whether importing an assembly json file creates identical bytecode. +# Results are recorded by adding to FAILED or UNCOMPILABLE. +# Also, in case of a mismatch a diff and the respective ASTs are printed +# Expected parameters: +# $1 name of the file to be exported and imported +# $2 any files needed to do so that might be in parent directories +function testImportExportEquivalence { + local nth_input_file="$1" + IFS=" " read -r -a all_input_files <<< "$2" + + if $SOLC "$nth_input_file" "${all_input_files[@]}" --combined-json asm,bin > /dev/null 2>&1 + then + local types=( "bin" "bin-runtime" "opcodes" "asm" "srcmap" "srcmap-runtime" ) + local test_types=( "bin" "bin-runtime" "opcodes" "asm" ) + + # save exported json as expected result (silently) + $SOLC --combined-json asm,opcodes,bin,srcmap,srcmap-runtime,bin-runtime --pretty-json "$nth_input_file" "${all_input_files[@]}" > expected.json 2> /dev/null + for contract in $(jq '.contracts | keys | .[]' expected.json 2> /dev/null) + do + for type in "${types[@]}" + do + jq --raw-output ".contracts.${contract}.\"${type}\"" expected.json > "expected.${type}" + done + expected_bin=$(cat expected.bin) + if [[ $expected_bin == "" ]] + then + continue + fi + + if ! "$SOLC" --import-asm-json expected.asm --combined-json asm,opcodes,bin,srcmap,srcmap-runtime,bin-runtime > imported.json 2> /dev/null + then + # For investigating, use exit 1 here so the script stops at the + # first failing test + # exit 1 + echo "" + echo "Failed with contract ${contract}!?" + echo "" + FAILED=$((FAILED + 1)) + return 1 + fi + + for type in "${test_types[@]}" + do + jq --raw-output ".contracts.\"expected.asm\".\"${type}\"" imported.json > "imported.${type}" + if ! diff "expected.${type}" "imported.${type}" + then + echo "" + echo "Failed with contract ${contract} (${type})." + echo "" + if [ "$DIFFVIEW" == "" ] + then + echo "Expected:" + cat "./expected.${type}" + echo "Obtained:" + cat "./imported.${type}" + else + # Use user supplied diff view binary + $DIFFVIEW "expected.${type}" "imported.${type}" + fi + FAILED=$((FAILED + 1)) + return 2 + fi + done + done + TESTED=$((TESTED + 1)) + rm -f expected.json asm.json expected.bin imported.bin + else + # echo "contract $solfile could not be compiled " + UNCOMPILABLE=$((UNCOMPILABLE + 1)) + fi + # return 0 +} +echo "Looking at $NSOURCES .sol files..." + +TEST_FILES=$(find "$SEMANTICTESTS_DIR" -name "*.sol") +run_import_tests "$TEST_FILES" "$SPLITSOURCES" "$NSOURCES" "$PWD" diff --git a/scripts/common_import.sh b/scripts/common_import.sh new file mode 100644 index 000000000..37cbbe307 --- /dev/null +++ b/scripts/common_import.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +set -eo pipefail + +function init_import_tests() +{ + export SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build} + export SOLC=${SOLIDITY_BUILD_DIR}/solc/solc + export SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py + export FAILED=0 + export UNCOMPILABLE=0 + export TESTED=0 + + if [[ "$(find . -maxdepth 0 -type d -empty)" == "" ]]; then + echo "Test directory not empty. Skipping!" + exit 1 + fi +} + +function run_import_tests() +{ + local TEST_FILES=$1 + local SPLITSOURCES=$2 + local NSOURCES=$3 + local WORKINGDIR=$4 + + for solfile in $TEST_FILES; do + echo -n "." + # create a temporary sub-directory + local FILETMP + FILETMP=$(mktemp -d) + cd "$FILETMP" + + set +e + local OUTPUT + OUTPUT=$("$SPLITSOURCES" "$solfile") + local SPLITSOURCES_RC=$? + set -e + if [ ${SPLITSOURCES_RC} == 0 ]; then + # echo $OUTPUT + NSOURCES=$((NSOURCES - 1)) + for i in $OUTPUT; do + testImportExportEquivalence "$i" "$OUTPUT" + NSOURCES=$((NSOURCES + 1)) + done + elif [ ${SPLITSOURCES_RC} == 1 ]; then + testImportExportEquivalence "$solfile" + elif [ ${SPLITSOURCES_RC} == 2 ]; then + # The script will exit with return code 2, if an UnicodeDecodeError occurred. + # This is the case if e.g. some tests are using invalid utf-8 sequences. We will ignore + # these errors, but print the actual output of the script. + echo -e "\n${OUTPUT}\n" + testImportExportEquivalence "$solfile" + else + # All other return codes will be treated as critical errors. The script will exit. + echo -e "\nGot unexpected return code ${SPLITSOURCES_RC} from ${SPLITSOURCES}. Aborting." + echo -e "\n${OUTPUT}\n" + + cd "$WORKINGDIR" + # Delete temporary files + rm -rf "$FILETMP" + + exit 1 + fi + + cd "$WORKINGDIR" + # Delete temporary files + rm -rf "$FILETMP" + done + + echo "" + + if [ "$FAILED" = 0 ]; then + echo "SUCCESS: $TESTED tests passed, $FAILED failed, $UNCOMPILABLE could not be compiled ($NSOURCES sources total)." + else + echo "FAILURE: Out of $NSOURCES sources, $FAILED failed, ($UNCOMPILABLE could not be compiled)." + exit 1 + fi +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index ed694ab74..dcefb0e67 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -790,7 +790,7 @@ bool CommandLineInterface::compile() return false; } } - if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) + else if (m_options.input.mode == InputMode::CompilerWithEvmAssemblyJsonImport) { solAssert(m_fileReader.sourceCodes().size() == 1, ""); try diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 2076eb993..87820bf64 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -154,7 +154,7 @@ function ask_expectation_update # General helper function for testing SOLC behaviour, based on file name, compile opts, exit code, stdout and stderr. # An failure is expected. -function test_solc_behaviour +function test_solc_behaviour() { local filename="${1}" local solc_args @@ -288,7 +288,7 @@ EOF } -function test_solc_assembly_output +function test_solc_assembly_output() { local input="${1}" local expected="${2}" @@ -572,6 +572,18 @@ SOLTMPDIR=$(mktemp -d) ) rm -r "$SOLTMPDIR" +printTask "Testing ASM-JSON import..." +SOLTMPDIR=$(mktemp -d) +( + cd "$SOLTMPDIR" + if ! "$REPO_ROOT/scripts/AsmJsonImportTest.sh" + then + rm -rf "$SOLTMPDIR" + exit 1 + fi +) +rm -rf "$SOLTMPDIR" + printTask "Testing AST export with stop-after=parsing..." "$REPO_ROOT/test/stopAfterParseTests.sh" diff --git a/test/cmdlineTests/asm_json/output b/test/cmdlineTests/asm_json/output index 26ab87147..0128c541c 100644 --- a/test/cmdlineTests/asm_json/output +++ b/test/cmdlineTests/asm_json/output @@ -1582,5 +1582,10 @@ EVM assembly: } ] } - } + }, + "sourceList": + [ + "asm_json/input.sol", + "#utility.yul" + ] }