Merge pull request from ethereum/develop

Merge develop into breaking.
This commit is contained in:
chriseth 2020-07-16 17:14:45 +02:00 committed by GitHub
commit f945163909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 909 additions and 288 deletions
.circleci
.github/workflows
.gitignore.travis.ymlChangelog.md
libevmasm
libsmtutil
libsolidity
libyul
scripts
test

View File

@ -7,18 +7,21 @@
# - ems: Emscripten
version: 2.1
parameters:
ubuntu-1804-docker-image-rev:
ubuntu-1804-docker-image:
type: string
default: "4"
ubuntu-2004-docker-image-rev:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:4484ac3da8fdc337cc77a7a7be1af71cd0f28f9c890d934f1d6ae7532beb66b1"
ubuntu-2004-docker-image:
type: string
default: "2"
ubuntu-2004-clang-docker-image-rev:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:48b5bb6b91ac7dddfe9345c88876ebed126c652258800f114caed69db73b29bf"
ubuntu-2004-clang-docker-image:
type: string
default: "2"
ubuntu-1604-clang-ossfuzz-docker-image-rev:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d8775de58167db5a11690fdb6ef639317fe1e579ec5d46e9732d2d903b55d135"
ubuntu-1604-clang-ossfuzz-docker-image:
type: string
default: "2"
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:db52f3257396814215744a19904e42c07e040ab36b68be72a27ba71ad2f1083c"
emscripten-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc"
defaults:
@ -33,26 +36,11 @@ defaults:
- run_build: &run_build
name: Build
command: |
set -ex
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" -o -n "$FORCE_RELEASE" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
mkdir -p build
cd build
[ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON"
cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS -G "Unix Makefiles"
make -j4
command: scripts/ci/build.sh
- run_build_ossfuzz: &run_build_ossfuzz
name: Build_ossfuzz
command: |
mkdir -p build
cd build
protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
protoc --proto_path=../test/tools/ossfuzz abiV2Proto.proto --cpp_out=../test/tools/ossfuzz
protoc --proto_path=../test/tools/ossfuzz solProto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS
make ossfuzz ossfuzz_proto ossfuzz_abiv2 -j4
command: scripts/ci/build_ossfuzz.sh
- run_proofs: &run_proofs
name: Correctness proofs for optimization rules
@ -131,7 +119,7 @@ defaults:
- test_ubuntu1604_clang: &test_ubuntu1604_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image >>
steps:
- checkout
- attach_workspace:
@ -142,7 +130,7 @@ defaults:
- test_ubuntu2004_clang: &test_ubuntu2004_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-clang-<< pipeline.parameters.ubuntu-2004-clang-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
steps:
- checkout
- attach_workspace:
@ -153,7 +141,8 @@ defaults:
- test_ubuntu2004: &test_ubuntu2004
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
parallelism: 6
steps:
- checkout
- attach_workspace:
@ -388,7 +377,7 @@ jobs:
chk_docs_pragma_min_version:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment:
TERM: xterm
steps:
@ -397,7 +386,7 @@ jobs:
b_ubu_clang: &build_ubuntu2004_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-clang-<< pipeline.parameters.ubuntu-2004-clang-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
environment:
CC: clang
CXX: clang++
@ -410,7 +399,7 @@ jobs:
b_ubu_asan_clang: &build_ubuntu2004_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-clang-<< pipeline.parameters.ubuntu-2004-clang-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
environment:
CC: clang
CXX: clang++
@ -422,8 +411,11 @@ jobs:
- persist_to_workspace: *artifacts_executables
b_ubu: &build_ubuntu2004
resource_class: xlarge
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment:
MAKEFLAGS: -j 10
steps:
- checkout
- run: *run_build
@ -438,7 +430,7 @@ jobs:
b_ubu18: &build_ubuntu1804
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.ubuntu-1804-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-1804-docker-image >>
environment:
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
CMAKE_BUILD_TYPE: RelWithDebugInfo
@ -492,7 +484,7 @@ jobs:
b_ubu_ossfuzz: &build_ubuntu1604_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image >>
environment:
CC: clang
CXX: clang++
@ -598,9 +590,11 @@ jobs:
- store_artifacts: *artifacts_test_results
b_ems:
resource_class: xlarge
docker:
- image: ethereum/solidity-buildpack-deps:emsdk-1.39.15-2
- image: << pipeline.parameters.emscripten-docker-image >>
environment:
MAKEFLAGS: -j 10
TERM: xterm
steps:
- checkout
@ -634,7 +628,7 @@ jobs:
b_docs:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
steps:
- checkout
- run: *setup_prerelease_commit_hash
@ -650,7 +644,7 @@ jobs:
t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment:
EVM: constantinople
SOLTEST_FLAGS: --enforce-via-yul
@ -676,7 +670,7 @@ jobs:
t_ubu_cli: &t_ubu_cli
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment:
TERM: xterm
steps:

View File

@ -28,10 +28,49 @@ set -e
REPODIR="$(realpath $(dirname $0)/..)"
EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 ${REPODIR}/.circleci/soltest.sh
EVM_VALUES=(homestead byzantium constantinople petersburg istanbul)
OPTIMIZE_VALUES=(0 1)
STEPS=$(( 1 + ${#EVM_VALUES[@]} * ${#OPTIMIZE_VALUES[@]} ))
for OPTIMIZE in 0 1; do
for EVM in homestead byzantium constantinople petersburg istanbul; do
EVM=$EVM OPTIMIZE=$OPTIMIZE BOOST_TEST_ARGS="-t !@nooptions" ${REPODIR}/.circleci/soltest.sh
if (( $CIRCLE_NODE_TOTAL )) && (( $CIRCLE_NODE_TOTAL > 1 ))
then
# Run step 1 as the only step on the first executor
# and evenly distribute the other steps among
# the other executors.
# The first step takes much longer than the other steps.
if (( $CIRCLE_NODE_INDEX == 0 ))
then
RUN_STEPS="1"
else
export CIRCLE_NODE_INDEX=$(($CIRCLE_NODE_INDEX - 1))
export CIRCLE_NODE_TOTAL=$(($CIRCLE_NODE_TOTAL - 1))
RUN_STEPS=$(seq 2 "$STEPS" | circleci tests split)
fi
else
RUN_STEPS=$(seq "$STEPS")
fi
# turn newlines into spaces
RUN_STEPS=$(echo $RUN_STEPS)
echo "Running steps $RUN_STEPS..."
STEP=1
[[ " $RUN_STEPS " =~ " $STEP " ]] && EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 "${REPODIR}/.circleci/soltest.sh"
STEP=$(($STEP + 1))
for OPTIMIZE in ${OPTIMIZE_VALUES[@]}
do
for EVM in ${EVM_VALUES[@]}
do
[[ " $RUN_STEPS " =~ " $STEP " ]] && EVM="$EVM" OPTIMIZE="$OPTIMIZE" BOOST_TEST_ARGS="-t !@nooptions" "${REPODIR}/.circleci/soltest.sh"
STEP=$(($STEP + 1))
done
done
if (($STEP != $STEPS + 1))
then
echo "Step counter not properly adjusted!" >2
exit 1
fi

43
.github/workflows/buildpack-deps.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: buildpack-deps
on:
pull_request:
branches: [ develop ]
paths:
- 'scripts/docker/buildpack-deps/Dockerfile.emscripten'
- 'scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz'
- 'scripts/docker/buildpack-deps/Dockerfile.ubuntu1804'
- 'scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang'
- 'scripts/docker/buildpack-deps/Dockerfile.ubuntu2004'
jobs:
buildpack-deps:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKER_REPOSITORY: solbuildpackpusher/solidity-buildpack-deps
IMAGE_NAME: buildpack-deps
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
image_variant: [emscripten, ubuntu1604.clang.ossfuzz, ubuntu1804, ubuntu2004.clang, ubuntu2004]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Upgrade ${{ env.IMAGE_NAME }}-${{ matrix.image_variant }}
run: |
echo ${DOCKERHUB_TOKEN} | docker login -u solbuildpackpusher --password-stdin
scripts/ci/docker_upgrade.sh ${{ env.IMAGE_NAME }} ${{ matrix.image_variant }} ${{ env.DOCKER_REPOSITORY }}
docker logout
- name: comment PR
if: "env.DOCKER_IMAGE"
uses: aarlt/comment-on-pr@v1.2.0
with:
msg: "`${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_REPO_DIGEST }}`."

2
.gitignore vendored
View File

@ -32,7 +32,7 @@ prerelease.txt
# Build directory
build/
build*/
/build*/
emscripten_build/
docs/_build
__pycache__

View File

@ -47,6 +47,7 @@ env:
- SOLC_TESTS=On
- SOLC_STOREBYTECODE=Off
- SOLC_DOCKER=Off
- MAKEFLAGS="-j 4"
matrix:
include:
@ -112,7 +113,7 @@ matrix:
before_install:
- nvm install 10
- nvm use 10
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-2
- docker pull solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc
env:
- SOLC_EMSCRIPTEN=On
- SOLC_INSTALL_DEPS_TRAVIS=Off

View File

@ -31,16 +31,24 @@ Bugfixes:
### 0.6.12 (unreleased)
Language Features:
* Wasm backend: Add ``i32.ctz``, ``i64.ctz``, ``i32.popcnt``, and ``i64.popcnt``.
Compiler Features:
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
* Peephole Optimizer: Remove unnecessary masking of tags.
* Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only.
Bugfixes:
* SMTChecker: Fix internal error when using bitwise operators on fixed bytes type.
* Type Checker: Fix overload resolution in combination with ``{value: ...}``.
* Type Checker: Fix internal compiler error related to oversized types.
* Code Generator: Avoid double cleanup when copying to memory.
Compiler Features:
* Build System: Update internal dependency of jsoncpp to 1.9.3.
* Optimizer: Add rule to remove shifts inside the byte opcode.
* Peephole Optimizer: Add rule to remove swap after dup.
### 0.6.11 (2020-07-07)

View File

@ -174,16 +174,16 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics.
JUMPTO = 0xb0, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
BEGINDATA, ///< begin the data section -- not part of Instructions.cpp
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
PUTLOCAL, ///< pop top of stack to local variable -- not part of Instructions.cpp
GETLOCAL, ///< push local variable to top of stack -- not part of Instructions.cpp
EIP615_JUMPTO = 0xb0, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
EIP615_JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
EIP615_JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
EIP615_JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
EIP615_JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
EIP615_BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
EIP615_BEGINDATA, ///< begin the data section -- not part of Instructions.cpp
EIP615_RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
EIP615_PUTLOCAL, ///< pop top of stack to local variable -- not part of Instructions.cpp
EIP615_GETLOCAL, ///< push local variable to top of stack -- not part of Instructions.cpp
CREATE = 0xf0, ///< create a new account with associated code
CALL, ///< message-call into an account

View File

@ -205,6 +205,30 @@ struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
}
};
/// Remove swapN after dupN
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2>
{
static size_t applySimple(
AssemblyItem const& _dupN,
AssemblyItem const& _swapN,
std::back_insert_iterator<AssemblyItems> _out
)
{
if (
SemanticInformation::isDupInstruction(_dupN) &&
SemanticInformation::isSwapInstruction(_swapN) &&
getDupNumber(_dupN.instruction()) == getSwapNumber(_swapN.instruction())
)
{
*_out = _dupN;
return true;
}
else
return false;
}
};
struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4>
{
static size_t applySimple(
@ -266,9 +290,10 @@ struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
std::back_insert_iterator<AssemblyItems> _out
)
{
if (_and != Instruction::AND)
return false;
if (
_pushTag.type() == PushTag &&
_and == Instruction::AND &&
_pushConstant.type() == Push &&
(_pushConstant.data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF)
)
@ -276,6 +301,16 @@ struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
*_out = _pushTag;
return true;
}
else if (
// tag and constant are swapped
_pushConstant.type() == PushTag &&
_pushTag.type() == Push &&
(_pushTag.data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF)
)
{
*_out = _pushConstant;
return true;
}
else
return false;
}
@ -357,7 +392,7 @@ bool PeepholeOptimiser::optimise()
applyMethods(
state,
PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(),
DupSwap(), IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(),
TagConjunctions(), TruthyAnd(), Identity()
);
if (m_optimisedItems.size() < m_items.size() || (

View File

@ -59,10 +59,10 @@ class Expression
public:
explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {}
explicit Expression(std::shared_ptr<SortSort> _sort, std::string _name = ""): Expression(std::move(_name), {}, _sort) {}
Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {}
Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {}
Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {}
Expression(bigint const& _number): Expression(_number.str(), Kind::Int) {}
Expression(size_t _number): Expression(std::to_string(_number), {}, SortProvider::sintSort) {}
Expression(u256 const& _number): Expression(_number.str(), {}, SortProvider::sintSort) {}
Expression(s256 const& _number): Expression(_number.str(), {}, SortProvider::sintSort) {}
Expression(bigint const& _number): Expression(_number.str(), {}, SortProvider::sintSort) {}
Expression(Expression const&) = default;
Expression(Expression&&) = default;
@ -262,23 +262,28 @@ public:
}
friend Expression operator+(Expression _a, Expression _b)
{
return Expression("+", std::move(_a), std::move(_b), Kind::Int);
auto intSort = _a.sort;
return Expression("+", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator-(Expression _a, Expression _b)
{
return Expression("-", std::move(_a), std::move(_b), Kind::Int);
auto intSort = _a.sort;
return Expression("-", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator*(Expression _a, Expression _b)
{
return Expression("*", std::move(_a), std::move(_b), Kind::Int);
auto intSort = _a.sort;
return Expression("*", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator/(Expression _a, Expression _b)
{
return Expression("/", std::move(_a), std::move(_b), Kind::Int);
auto intSort = _a.sort;
return Expression("/", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator%(Expression _a, Expression _b)
{
return Expression("mod", std::move(_a), std::move(_b), Kind::Int);
auto intSort = _a.sort;
return Expression("mod", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator&(Expression _a, Expression _b)
{

View File

@ -60,6 +60,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
checkStorageSize(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@ -459,3 +460,20 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
);
}
void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
{
bigint size = 0;
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (!(variable->isConstant() || variable->immutable()))
{
size += variable->annotation().type->storageSizeUpperBound();
if (size >= bigint(1) << 256)
{
m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage.");
break;
}
}
}

View File

@ -83,6 +83,8 @@ private:
/// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);
/// Error if the contract requires too much storage
void checkStorageSize(ContractDefinition const& _contract);
OverrideChecker m_overrideChecker;
langutil::ErrorReporter& m_errorReporter;

View File

@ -92,19 +92,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
for (auto const& n: _contract.subNodes())
n->accept(*this);
bigint size = 0;
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_currentContract->annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (!(variable->isConstant() || variable->immutable()))
{
size += storageSizeUpperBound(*(variable->annotation().type));
if (size >= bigint(1) << 256)
{
m_errorReporter.typeError(7676_error, m_currentContract->location(), "Contract too large for storage.");
break;
}
}
m_currentContract = nullptr;
return false;
}
@ -353,7 +341,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (_function.libraryFunction())
m_errorReporter.typeError(7708_error, _function.location(), "Library functions cannot be payable.");
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(5587_error, _function.location(), "Internal functions cannot be payable.");
m_errorReporter.typeError(5587_error, _function.location(), "\"internal\" and \"private\" functions cannot be payable.");
}
vector<VariableDeclaration const*> internalParametersInConstructor;
@ -598,12 +586,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
" in small quantities per transaction.";
};
if (storageSizeUpperBound(*varType) >= bigint(1) << 64)
if (varType->storageSizeUpperBound() >= bigint(1) << 64)
{
if (_variable.isStateVariable())
m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true));
else
m_errorReporter.warning(2332_error, _variable.typeName()->location(), collisionMessage(varType->canonicalName(), false));
m_errorReporter.warning(
2332_error,
_variable.typeName() ? _variable.typeName()->location() : _variable.location(),
collisionMessage(varType->canonicalName(), false)
);
}
vector<Type const*> oversizedSubtypes = frontend::oversizedSubtypes(*varType);
for (Type const* subtype: oversizedSubtypes)

View File

@ -63,37 +63,6 @@ struct TypeComp
};
using TypeSet = std::set<Type const*, TypeComp>;
bigint storageSizeUpperBoundInner(
Type const& _type,
set<StructDefinition const*>& _structsSeen
)
{
switch (_type.category())
{
case Type::Category::Array:
{
auto const& t = dynamic_cast<ArrayType const&>(_type);
if (!t.isDynamicallySized())
return storageSizeUpperBoundInner(*t.baseType(), _structsSeen) * t.length();
break;
}
case Type::Category::Struct:
{
auto const& t = dynamic_cast<StructType const&>(_type);
solAssert(!_structsSeen.count(&t.structDefinition()), "Recursive struct.");
bigint size = 1;
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
size += storageSizeUpperBoundInner(*m.type, _structsSeen);
_structsSeen.erase(&t.structDefinition());
return size;
}
default:
break;
}
return bigint(1);
}
void oversizedSubtypesInner(
Type const& _type,
bool _includeType,
@ -106,7 +75,7 @@ void oversizedSubtypesInner(
case Type::Category::Array:
{
auto const& t = dynamic_cast<ArrayType const&>(_type);
if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64)
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes);
break;
@ -116,7 +85,7 @@ void oversizedSubtypesInner(
auto const& t = dynamic_cast<StructType const&>(_type);
if (_structsSeen.count(&t.structDefinition()))
return;
if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64)
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
@ -231,12 +200,6 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
}
bigint solidity::frontend::storageSizeUpperBound(frontend::Type const& _type)
{
set<StructDefinition const*> structsSeen;
return storageSizeUpperBoundInner(_type, structsSeen);
}
vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type)
{
set<StructDefinition const*> structsSeen;
@ -1867,7 +1830,7 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const
break;
}
case DataLocation::Storage:
if (storageSizeUpperBound(*this) >= bigint(1) << 256)
if (storageSizeUpperBound() >= bigint(1) << 256)
return BoolResult::err("Type too large for storage.");
break;
}
@ -1908,6 +1871,14 @@ bool ArrayType::isDynamicallyEncoded() const
return isDynamicallySized() || baseType()->isDynamicallyEncoded();
}
bigint ArrayType::storageSizeUpperBound() const
{
if (isDynamicallySized())
return 1;
else
return length() * baseType()->storageSizeUpperBound();
}
u256 ArrayType::storageSize() const
{
if (isDynamicallySized())
@ -2381,6 +2352,14 @@ u256 StructType::memoryDataSize() const
return size;
}
bigint StructType::storageSizeUpperBound() const
{
bigint size = 1;
for (auto const& member: members(nullptr))
size += member.type->storageSizeUpperBound();
return size;
}
u256 StructType::storageSize() const
{
return max<u256>(1, members(nullptr).storageSize());
@ -2558,7 +2537,7 @@ BoolResult StructType::validForLocation(DataLocation _loc) const
if (
_loc == DataLocation::Storage &&
storageSizeUpperBound(*this) >= bigint(1) << 256
storageSizeUpperBound() >= bigint(1) << 256
)
return BoolResult::err("Type too large for storage.");

View File

@ -59,7 +59,6 @@ using BoolResult = util::Result<bool>;
namespace solidity::frontend
{
bigint storageSizeUpperBound(frontend::Type const& _type);
std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type);
inline rational makeRational(bigint const& _numerator, bigint const& _denominator)
@ -251,6 +250,10 @@ public:
/// @returns the number of storage slots required to hold this value in storage.
/// For dynamically "allocated" types, it returns the size of the statically allocated head,
virtual u256 storageSize() const { return 1; }
/// @returns an upper bound on the total storage size required by this type, descending
/// into structs and statically-sized arrays. This is mainly to ensure that the storage
/// slot allocation algorithm does not overflow, it is not a protection against collisions.
virtual bigint storageSizeUpperBound() const { return 1; }
/// Multiple small types can be packed into a single storage slot. If such a packing is possible
/// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if
/// it does not fit.
@ -790,6 +793,7 @@ public:
unsigned calldataEncodedTailSize() const override;
bool isDynamicallySized() const override { return m_hasDynamicLength; }
bool isDynamicallyEncoded() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override;
bool containsNestedMapping() const override { return m_baseType->containsNestedMapping(); }
bool nameable() const override { return true; }
@ -954,6 +958,7 @@ public:
unsigned calldataEncodedTailSize() const override;
bool isDynamicallyEncoded() const override;
u256 memoryDataSize() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override;
bool containsNestedMapping() const override;
bool nameable() const override { return true; }

View File

@ -180,7 +180,7 @@ void CompilerUtils::storeInMemory(unsigned _offset)
m_context << u256(_offset) << Instruction::MSTORE;
}
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries)
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries, bool _cleanup)
{
// process special types (Reference, StringLiteral, Function)
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
@ -189,7 +189,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
ref->location() == DataLocation::Memory,
"Only in-memory reference type can be stored."
);
storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries);
storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries, _cleanup);
}
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
{
@ -212,7 +212,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
}
else if (_type.isValueType())
{
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries, _cleanup);
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(numBytes) << Instruction::ADD;
}
@ -463,6 +463,7 @@ void CompilerUtils::encodeToMemory(
}
else
{
bool needCleanup = true;
copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->sizeOnStack());
solAssert(!!targetType, "Externalable type expected.");
TypePointer type = targetType;
@ -481,7 +482,11 @@ void CompilerUtils::encodeToMemory(
)
type = _givenTypes[i]; // delay conversion
else
{
convertType(*_givenTypes[i], *targetType, true);
needCleanup = false;
}
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries);
else if (auto arraySliceType = dynamic_cast<ArraySliceType const*>(type))
@ -495,7 +500,7 @@ void CompilerUtils::encodeToMemory(
ArrayUtils(m_context).copyArrayToMemory(arraySliceType->arrayType(), _padToWordBoundaries);
}
else
storeInMemoryDynamic(*type, _padToWordBoundaries);
storeInMemoryDynamic(*type, _padToWordBoundaries, needCleanup);
}
stackPos += _givenTypes[i]->sizeOnStack();
}
@ -1498,7 +1503,7 @@ void CompilerUtils::rightShiftNumberOnStack(unsigned _bits)
m_context << (u256(1) << _bits) << Instruction::SWAP1 << Instruction::DIV;
}
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords, bool _cleanup)
{
solAssert(
_type.sizeOnStack() == 1,
@ -1521,7 +1526,9 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
bool leftAligned = _type.category() == Type::Category::FixedBytes;
convertType(_type, _type, true);
if (_cleanup)
convertType(_type, _type, true);
if (numBytes != 32 && !leftAligned && !_padToWords)
// shift the value accordingly before storing
leftShiftNumberOnStack((32 - numBytes) * 8);

View File

@ -107,10 +107,11 @@ public:
/// and also updates that. For reference types, only copies the data pointer. Fails for
/// non-memory-references.
/// @param _padToWords if true, adds zeros to pad to multiple of 32 bytes. Array elements
/// @param _cleanup if true, adds code to cleanup the value before storing it.
/// are always padded (except for byte arrays), regardless of this parameter.
/// Stack pre: memory_offset value...
/// Stack post: (memory_offset+length)
void storeInMemoryDynamic(Type const& _type, bool _padToWords = true);
void storeInMemoryDynamic(Type const& _type, bool _padToWords = true, bool _cleanup = true);
/// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data.
@ -309,7 +310,8 @@ private:
void cleanHigherOrderBits(IntegerType const& _typeOnStack);
/// Prepares the given type for storing in memory by shifting it if necessary.
unsigned prepareMemoryStore(Type const& _type, bool _padToWords);
/// @param _cleanup if true, also cleanup the value when preparing to store it in memory
unsigned prepareMemoryStore(Type const& _type, bool _padToWords, bool _cleanup = true);
/// Loads type from memory assuming memory offset is on stack top.
unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords);

View File

@ -1376,9 +1376,11 @@ void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
bvSize = fixedType->numBits();
isSigned = fixedType->isSigned();
}
else if (auto const* fixedBytesType = dynamic_cast<FixedBytesType const*>(commonType))
bvSize = fixedBytesType->numBytes() * 8;
auto bvLeft = smtutil::Expression::int2bv(expr(_op.leftExpression()), bvSize);
auto bvRight = smtutil::Expression::int2bv(expr(_op.rightExpression()), bvSize);
auto bvLeft = smtutil::Expression::int2bv(expr(_op.leftExpression(), commonType), bvSize);
auto bvRight = smtutil::Expression::int2bv(expr(_op.rightExpression(), commonType), bvSize);
optional<smtutil::Expression> result;
if (_op.getOperator() == Token::BitAnd)

View File

@ -441,7 +441,11 @@ optional<smtutil::Expression> symbolicTypeConversion(TypePointer _from, TypePoin
// case they'd need to be encoded as numbers.
if (auto strType = dynamic_cast<StringLiteralType const*>(_from))
if (_to->category() == frontend::Type::Category::FixedBytes)
{
if (strType->value().empty())
return smtutil::Expression(size_t(0));
return smtutil::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
}
return std::nullopt;
}

View File

@ -102,7 +102,7 @@ void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _
{
if (m_evm15)
{
m_bytecode.push_back(uint8_t(evmasm::Instruction::JUMPTO));
m_bytecode.push_back(uint8_t(evmasm::Instruction::EIP615_JUMPTO));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _stackDiffAfter;
}
@ -117,7 +117,7 @@ void EVMAssembly::appendJumpToIf(LabelID _labelId, JumpType)
{
if (m_evm15)
{
m_bytecode.push_back(uint8_t(evmasm::Instruction::JUMPIF));
m_bytecode.push_back(uint8_t(evmasm::Instruction::EIP615_JUMPIF));
appendLabelReferenceInternal(_labelId);
m_stackHeight--;
}
@ -133,7 +133,7 @@ void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments)
yulAssert(m_evm15, "BEGINSUB used for EVM 1.0");
yulAssert(_arguments >= 0, "");
setLabelToCurrentPosition(_labelId);
m_bytecode.push_back(uint8_t(evmasm::Instruction::BEGINSUB));
m_bytecode.push_back(uint8_t(evmasm::Instruction::EIP615_BEGINSUB));
m_stackHeight += _arguments;
}
@ -141,7 +141,7 @@ void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns)
{
yulAssert(m_evm15, "JUMPSUB used for EVM 1.0");
yulAssert(_arguments >= 0 && _returns >= 0, "");
m_bytecode.push_back(uint8_t(evmasm::Instruction::JUMPSUB));
m_bytecode.push_back(uint8_t(evmasm::Instruction::EIP615_JUMPSUB));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _returns - _arguments;
}
@ -150,7 +150,7 @@ void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter)
{
yulAssert(m_evm15, "RETURNSUB used for EVM 1.0");
yulAssert(_returns >= 0, "");
m_bytecode.push_back(uint8_t(evmasm::Instruction::RETURNSUB));
m_bytecode.push_back(uint8_t(evmasm::Instruction::EIP615_RETURNSUB));
m_stackHeight += _stackDiffAfter - _returns;
}

View File

@ -141,7 +141,7 @@ bool CodeTransform::unreferenced(Scope::Variable const& _var) const
return !m_context->variableReferences.count(&_var) || m_context->variableReferences[&_var] == 0;
}
void CodeTransform::freeUnusedVariables()
void CodeTransform::freeUnusedVariables(bool _popUnusedSlotsAtStackTop)
{
if (!m_allowStackOpt)
return;
@ -154,11 +154,12 @@ void CodeTransform::freeUnusedVariables()
deleteVariable(var);
}
while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1))
{
yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), "");
m_assembly.appendInstruction(evmasm::Instruction::POP);
}
if (_popUnusedSlotsAtStackTop)
while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1))
{
yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), "");
m_assembly.appendInstruction(evmasm::Instruction::POP);
}
}
void CodeTransform::deleteVariable(Scope::Variable const& _var)
@ -181,6 +182,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
std::visit(*this, *_varDecl.value);
expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart));
freeUnusedVariables(false);
}
else
{

View File

@ -161,8 +161,9 @@ protected:
bool unreferenced(Scope::Variable const& _var) const;
/// Marks slots of variables that are not used anymore
/// and were defined in the current scope for reuse.
/// Also POPs unused topmost stack slots.
void freeUnusedVariables();
/// Also POPs unused topmost stack slots,
/// unless @a _popUnusedSlotsAtStackTop is set to false.
void freeUnusedVariables(bool _popUnusedSlotsAtStackTop = true);
/// Marks the stack slot of @a _var to be reused.
void deleteVariable(Scope::Variable const& _var);

View File

@ -38,13 +38,18 @@ WasmDialect::WasmDialect()
"add",
"sub",
"mul",
// TODO: div_s
"div_u",
// TODO: rem_s
"rem_u",
"and",
"or",
"xor",
"shl",
// TODO: shr_s
"shr_u",
// TODO: rotl
// TODO: rotr
})
addFunction(t.str() + "." + name, {t, t}, {t});
@ -52,9 +57,13 @@ WasmDialect::WasmDialect()
for (auto const& name: {
"eq",
"ne",
// TODO: lt_s
"lt_u",
// TODO: gt_s
"gt_u",
// TODO: le_s
"le_u",
// TODO: ge_s
"ge_u"
})
addFunction(t.str() + "." + name, {t, t}, {i32});
@ -62,8 +71,13 @@ WasmDialect::WasmDialect()
addFunction("i32.eqz", {i32}, {i32});
addFunction("i64.eqz", {i64}, {i32});
addFunction("i32.clz", {i32}, {i32});
addFunction("i64.clz", {i64}, {i64});
for (auto t: types)
for (auto const& name: {
"clz",
"ctz",
"popcnt",
})
addFunction(t.str() + "." + name, {t}, {t});
addFunction("i32.wrap_i64", {i64}, {i32});
@ -73,6 +87,7 @@ WasmDialect::WasmDialect()
m_functions["i32.store"_yulstring].sideEffects.invalidatesStorage = false;
addFunction("i64.store", {i32, i64}, {}, false);
m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false;
// TODO: add i32.store16, i64.store8, i64.store16, i64.store32
addFunction("i32.store8", {i32, i32}, {}, false);
m_functions["i32.store8"_yulstring].sideEffects.invalidatesStorage = false;
@ -89,6 +104,7 @@ WasmDialect::WasmDialect()
m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
// TODO: add i32.load8, i32.load16, i64.load8, i64.load16, i64.load32
// Drop is actually overloaded for all types, but Yul does not support that.
// Because of that, we introduce "i32.drop" and "i64.drop".

View File

@ -30,6 +30,47 @@ using namespace solidity;
using namespace solidity::yul;
using namespace solidity::util;
namespace
{
// TODO: This algorithm is non-optimal.
struct CallGraphCycleFinder
{
CallGraph const& callGraph;
set<YulString> containedInCycle{};
set<YulString> visited{};
vector<YulString> currentPath{};
void visit(YulString _function)
{
if (visited.count(_function))
return;
if (
auto it = find(currentPath.begin(), currentPath.end(), _function);
it != currentPath.end()
)
containedInCycle.insert(it, currentPath.end());
else
{
currentPath.emplace_back(_function);
if (callGraph.functionCalls.count(_function))
for (auto const& child: callGraph.functionCalls.at(_function))
visit(child);
currentPath.pop_back();
visited.insert(_function);
}
}
};
}
set<YulString> CallGraph::recursiveFunctions() const
{
CallGraphCycleFinder cycleFinder{*this};
// Visiting the root only is not enough, since there may be disconnected recursive functions.
for (auto const& call: functionCalls)
cycleFinder.visit(call.first);
return cycleFinder.containedInCycle;
}
CallGraph CallGraphGenerator::callGraph(Block const& _ast)
{
CallGraphGenerator gen;

View File

@ -35,6 +35,10 @@ struct CallGraph
{
std::map<YulString, std::set<YulString>> functionCalls;
std::set<YulString> functionsWithLoops;
/// @returns the set of functions contained in cycles in the call graph, i.e.
/// functions that are part of a (mutual) recursion.
/// Note that this does not include functions that merely call recursive functions.
std::set<YulString> recursiveFunctions() const;
};
/**

View File

@ -102,33 +102,13 @@ map<YulString, SideEffects> SideEffectsPropagator::sideEffects(
// is actually a bit different from "not movable".
map<YulString, SideEffects> ret;
for (auto const& function: _directCallGraph.functionsWithLoops)
for (auto const& function: _directCallGraph.functionsWithLoops + _directCallGraph.recursiveFunctions())
{
ret[function].movable = false;
ret[function].sideEffectFree = false;
ret[function].sideEffectFreeIfNoMSize = false;
}
// Detect recursive functions.
for (auto const& call: _directCallGraph.functionCalls)
{
// TODO we could shortcut the search as soon as we find a
// function that has as bad side-effects as we can
// ever achieve via recursion.
auto search = [&](YulString const& _functionName, util::CycleDetector<YulString>& _cycleDetector, size_t) {
for (auto const& callee: _directCallGraph.functionCalls.at(_functionName))
if (!_dialect.builtin(callee))
if (_cycleDetector.run(callee))
return;
};
if (util::CycleDetector<YulString>(search).run(call.first))
{
ret[call.first].movable = false;
ret[call.first].sideEffectFree = false;
ret[call.first].sideEffectFreeIfNoMSize = false;
}
}
for (auto const& call: _directCallGraph.functionCalls)
{
YulString funName = call.first;

View File

@ -34,5 +34,6 @@ else
BUILD_DIR="$1"
fi
docker run -v $(pwd):/root/project -w /root/project ethereum/solidity-buildpack-deps:emsdk-1.39.15-2 \
docker run -v $(pwd):/root/project -w /root/project \
solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc \
./scripts/travis-emscripten/build_emscripten.sh $BUILD_DIR

23
scripts/ci/build.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -ex
ROOTDIR="$(dirname "$0")/../.."
cd "${ROOTDIR}"
# shellcheck disable=SC2166
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" -o -n "$FORCE_RELEASE" ]; then echo -n >prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" >prerelease.txt; fi
if [ -n "$CIRCLE_SHA1" ]
then
echo -n "$CIRCLE_SHA1" >commit_hash.txt
fi
mkdir -p build
cd build
# shellcheck disable=SC2166
[ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON"
# shellcheck disable=SC2086
cmake .. -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" $CMAKE_OPTIONS -G "Unix Makefiles"
make

15
scripts/ci/build_ossfuzz.sh Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
ROOTDIR="$(dirname "$0")/../.."
BUILDDIR="${ROOTDIR}/build"
mkdir -p "${BUILDDIR}"
cd "${BUILDDIR}"
protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
protoc --proto_path=../test/tools/ossfuzz abiV2Proto.proto --cpp_out=../test/tools/ossfuzz
protoc --proto_path=../test/tools/ossfuzz solProto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/libfuzzer.cmake
make ossfuzz ossfuzz_proto ossfuzz_abiv2 -j 4

View File

@ -0,0 +1 @@
../../scripts/travis-emscripten/build_emscripten.sh

View File

@ -0,0 +1 @@
build_ossfuzz.sh

View File

@ -0,0 +1 @@
build.sh

View File

@ -0,0 +1 @@
build.sh

View File

@ -0,0 +1 @@
build.sh

65
scripts/ci/docker_upgrade.sh Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -e
function error() {
echo >&2 "ERROR: ${1} Aborting." && false
}
function warning() {
echo >&2 "WARNING: ${1}"
}
[[ $# == 3 ]] || error "Expected exactly 3 parameters: '${0} <IMAGE_NAME> <IMAGE_VARIANT> <DOCKER_REPOSITORY>'."
IMAGE_NAME="${1}"
IMAGE_VARIANT="${2}"
DOCKER_REPOSITORY="${3}"
DOCKERFILE="scripts/docker/${IMAGE_NAME}/Dockerfile.${IMAGE_VARIANT}"
echo "-- check_dockerfile_was_changed"
# exit, if the dockerfile was not changed.
if git diff --quiet origin/develop HEAD -- "${DOCKERFILE}"; then
echo "${DOCKERFILE} was not changed. Nothing to do."
exit 0
fi
echo "-- check_version"
PREV_VERSION=$(git diff origin/develop HEAD -- "${DOCKERFILE}" | grep -e '^\s*-LABEL\s\+version=".*"\s*$' | awk -F'"' '{ print $2 }')
NEXT_VERSION=$(git diff origin/develop HEAD -- "${DOCKERFILE}" | grep -e '^\s*+LABEL\s\+version=".*"\s*$' | awk -F'"' '{ print $2 }')
[[ $NEXT_VERSION != "" ]] || error "No version label defined in Dockerfile. You may need to add 'LABEL version' in '${DOCKERFILE}'."
[[ $PREV_VERSION != "" ]] || {
warning "no previous version found. Will set \$PREV_VERSION = 0."
PREV_VERSION=0
}
if [[ $((PREV_VERSION + 1)) != $((NEXT_VERSION)) ]]; then
error "Version label in Dockerfile was not incremented. You may need to change 'LABEL version' in '${DOCKERFILE}'."
fi
echo "-- build_docker"
# This is a workaround: we run `docker build` twice to prevent the `layer does not exist` problem.
# See https://github.com/moby/moby/issues/37965.
docker build "scripts/docker/${IMAGE_NAME}" --file "scripts/docker/${IMAGE_NAME}/Dockerfile.${IMAGE_VARIANT}" --tag "${IMAGE_NAME}" ||
docker build "scripts/docker/${IMAGE_NAME}" --file "scripts/docker/${IMAGE_NAME}/Dockerfile.${IMAGE_VARIANT}" --tag "${IMAGE_NAME}"
echo "-- test_docker @ '${PWD}'"
docker run --rm --volume "${PWD}:/root/project" "${IMAGE_NAME}" "/root/project/scripts/ci/${IMAGE_NAME}_test_${IMAGE_VARIANT}.sh"
echo "-- push_docker"
VERSION=$(docker inspect --format='{{.Config.Labels.version}}' "${IMAGE_NAME}")
DOCKER_IMAGE_ID="${DOCKER_REPOSITORY}:${IMAGE_VARIANT}"
docker tag "${IMAGE_NAME}" "${DOCKER_IMAGE_ID}-${VERSION}"
docker push "${DOCKER_IMAGE_ID}-${VERSION}"
REPO_DIGEST=$(docker inspect --format='{{.RepoDigests}}' "${DOCKER_IMAGE_ID}-${VERSION}")
echo "::set-env name=DOCKER_IMAGE::${DOCKER_IMAGE_ID}-${VERSION}"
echo "::set-env name=DOCKER_REPO_DIGEST::${REPO_DIGEST}"

View File

@ -29,6 +29,7 @@
# make version=1.39.15 build
#
FROM emscripten/emsdk:1.39.15 AS base
LABEL version="1"
ADD emscripten.jam /usr/src
RUN set -ex; \
@ -63,3 +64,4 @@ RUN set -ex; \
cxxflags="-s DISABLE_EXCEPTION_CATCHING=0 -Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \
--prefix=/emsdk/emscripten/sdk/system install; \
rm -r /usr/src/boost_1_73_0

View File

@ -22,6 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM gcr.io/oss-fuzz-base/base-clang as base
LABEL version="1"
ARG DEBIAN_FRONTEND=noninteractive
@ -99,3 +100,4 @@ FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
COPY --from=libraries /usr/include /usr/include

View File

@ -22,6 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM buildpack-deps:bionic AS base
LABEL version="1"
ARG DEBIAN_FRONTEND=noninteractive
@ -91,3 +92,4 @@ FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
COPY --from=libraries /usr/include /usr/include

View File

@ -22,6 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM buildpack-deps:focal AS base
LABEL version="1"
ARG DEBIAN_FRONTEND=noninteractive
@ -60,3 +61,4 @@ FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
COPY --from=libraries /usr/include /usr/include

View File

@ -22,6 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM buildpack-deps:focal AS base
LABEL version="1"
ARG DEBIAN_FRONTEND=noninteractive
@ -62,3 +63,4 @@ FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
COPY --from=libraries /usr/include /usr/include

View File

@ -0,0 +1,35 @@
# buildpack-deps docker images
The `buildpack-deps` docker images are used to compile and test solidity within our CI.
## GitHub Workflow
The creation of the images are triggered by a single workflow, defined in `.github/workflows/buildpack-deps.yml`.
For each resulting `buildpack-deps` docker image a strategy is defined in the workflow file - the image variant.
The workflow gets triggered, if any Dockerfile defined in `scripts/docker/buildpack-deps/Dockerfile.*` were changed
within the PR.
### Versioning
The version of the docker images can be defined within the Dockerfile with `LABEL version`. A new docker image
will only be created and pushed, if the new version is incremented by `1` compared with the version of the Dockerfile
located in `develop`.
### Build, Test & Push
Note that the whole workflow - including all defined strategies (image variants) - will be triggered,
even if only a single Dockerfile was change. The full workflow will only gets executed, if the corresponding
Dockerfile was changed. The execution of workflows of unchanged Dockerfiles will not continue and just return success.
See `scripts/ci/docker_upgrade.sh`.
If the version check was successful, the docker image will be built using the Dockerfile located in
`scripts/docker/buildpack-deps/Dockerfile.*`.
The resulting docker image will be tested by executing
the corresponding `scripts/ci/buildpack-deps_test_*` scripts. These scripts are normally symlinked to `scripts/ci/build.sh`,
except for the `buildpack-deps-ubuntu1604.clang.ossfuzz` docker image, that is symlinked to `scripts/ci/build_ossfuzz.sh`.
These scripts `scripts/ci/build.sh` and `scripts/ci/build_ossfuzz.sh` are also used by CircleCI, see `.circleci/config.yml`.
If the tests passed successfully, the docker image will get tagged by the version defined within the corresponding `Dockerfile`.
Finally, a comment will be added to the PR that contains the full repository, version and repository digest
of the freshly created docker image.

View File

@ -53,7 +53,7 @@ cmake \
-DBoost_USE_STATIC_RUNTIME=1 \
-DTESTS=0 \
..
make -j 4 soljson
make soljson
# Patch soljson.js for backwards compatibility.
# TODO: remove this with 0.7.
# "viiiii" encodes the signature of the callback function.

View File

@ -0,0 +1 @@
--asm

View File

@ -0,0 +1,5 @@
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
--> dup_opt_peephole/input.sol
Warning: Source file does not specify required compiler version!
--> dup_opt_peephole/input.sol

View File

@ -0,0 +1,9 @@
contract C {
fallback() external {
assembly {
let x := calldataload(0)
x := x
sstore(0, x)
}
}
}

View File

@ -0,0 +1,54 @@
======= dup_opt_peephole/input.sol:C =======
EVM assembly:
/* "dup_opt_peephole/input.sol":0:111 contract C {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_1
jumpi
0x00
dup1
revert
tag_1:
pop
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x00
codecopy
0x00
return
stop
sub_0: assembly {
/* "dup_opt_peephole/input.sol":0:111 contract C {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_3
jumpi
0x00
dup1
revert
tag_3:
pop
/* "dup_opt_peephole/input.sol":74:75 0 */
0x00
/* "dup_opt_peephole/input.sol":61:76 calldataload(0) */
calldataload
/* "dup_opt_peephole/input.sol":100:101 x */
dup1
/* "dup_opt_peephole/input.sol":97:98 0 */
0x00
/* "dup_opt_peephole/input.sol":90:102 sstore(0, x) */
sstore
/* "dup_opt_peephole/input.sol":47:106 {... */
pop
/* "dup_opt_peephole/input.sol":0:111 contract C {... */
stop
auxdata: AUXDATA REMOVED
}

View File

@ -708,6 +708,19 @@ BOOST_AUTO_TEST_CASE(shift_optimizer_bug)
compareVersions("g(uint256)", u256(-1));
}
BOOST_AUTO_TEST_CASE(avoid_double_cleanup)
{
char const* sourceCode = R"(
contract C {
receive() external payable {
abi.encodePacked(uint200(0));
}
}
)";
compileBothVersions(sourceCode, 0, "C", 50);
// Check that there is no double AND instruction in the resulting code
BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::AND), 1);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,22 @@
contract C {
function oneByteUTF8() public pure returns (string memory) {
return "aaa\u0024aaa"; // usdollar
}
function twoBytesUTF8() public pure returns (string memory) {
return "aaa\u00A2aaa"; // cent
}
function threeBytesUTF8() public pure returns (string memory) {
return "aaa\u20ACaaa"; // euro
}
function combined() public pure returns (string memory) {
return "\u0024\u00A2\u20AC";
}
}
// ----
// oneByteUTF8() -> 0x20, 7, "aaa$aaa"
// twoBytesUTF8() -> 0x20, 8, "aaa\xc2\xa2aaa"
// threeBytesUTF8() -> 0x20, 9, "aaa\xe2\x82\xacaaa"
// combined() -> 0x20, 6, "$\xc2\xa2\xe2\x82\xac"

View File

@ -0,0 +1,7 @@
contract C {
function f() public pure returns (string memory) {
return "😃, 😭, and 😈";
}
}
// ----
// f() -> 0x20, 0x14, "\xf0\x9f\x98\x83, \xf0\x9f\x98\xad, and \xf0\x9f\x98\x88"

View File

@ -0,0 +1,8 @@
pragma experimental SMTChecker;
contract C {
function f() public pure returns (byte) {
return (byte("") & (""));
}
}
// ----
// Warning 5084: (101-109): Type conversion is not yet fully supported and might yield false positives.

View File

@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract Simp {
function f3() public pure returns (byte) {
bytes memory y = "def";
return y[0] ^ "e";
}
}
// ----
// Warning 1093: (142-152): Assertion checker does not yet implement this bitwise operator.

View File

@ -0,0 +1,15 @@
contract b {
struct c {
uint [2 ** 253] a;
}
c d;
function e() public {
var d = d;
}
}
// ----
// Warning 2519: (105-110): This declaration shadows an existing declaration.
// Warning 3408: (66-69): Variable "d" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 2332: (105-110): Type "b.c" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// SyntaxError 1719: (105-114): Use of the "var" keyword is disallowed. Use explicit declaration `struct b.c storage pointer d = ...´ instead.

View File

@ -4,5 +4,5 @@ contract C {
uint[2**255][2] a;
}
// ----
// TypeError 1534: (77-94): Type too large for storage.
// TypeError 7676: (60-97): Contract too large for storage.
// TypeError 1534: (77-94): Type too large for storage.

View File

@ -5,6 +5,6 @@ contract C {
uint[2**255] b;
}
// ----
// TypeError 7676: (60-114): Contract too large for storage.
// Warning 3408: (77-91): Variable "a" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (97-111): Variable "b" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// TypeError 7676: (60-114): Contract too large for storage.

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.0;
contract C {
uint[2**255] a;
}
contract D is C {
uint[2**255] b;
}
// ----
// TypeError 7676: (95-134): Contract too large for storage.
// Warning 3408: (77-91): Variable "a" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (117-131): Variable "b" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.

View File

@ -8,5 +8,5 @@ contract C {
S s;
}
// ----
// TypeError 1534: (146-149): Type too large for storage.
// TypeError 7676: (60-152): Contract too large for storage.
// TypeError 1534: (146-149): Type too large for storage.

View File

@ -2,4 +2,4 @@ contract test {
function f() payable internal {}
}
// ----
// TypeError 5587: (20-52): Internal functions cannot be payable.
// TypeError 5587: (20-52): "internal" and "private" functions cannot be payable.

View File

@ -2,4 +2,4 @@ contract test {
function f() payable private {}
}
// ----
// TypeError 5587: (20-51): Internal functions cannot be payable.
// TypeError 5587: (20-51): "internal" and "private" functions cannot be payable.

View File

@ -0,0 +1,5 @@
contract C {
string s = hex"a000";
}
// ----
// TypeError 7407: (28-37): Type literal_string (contains invalid UTF-8 sequence at position 0) is not implicitly convertible to expected type string storage ref.

View File

@ -0,0 +1,6 @@
contract C {
bytes b1 = "\xa0\x00";
bytes32 b2 = "\xa0\x00";
bytes b3 = hex"a000";
}
// ----

View File

@ -0,0 +1,6 @@
contract test {
function f() public pure returns (string memory) {
return "😃, 😭, and 😈";
}
}
// ----

View File

@ -1,5 +1,4 @@
contract test {
function oneByteUTF8() public pure returns (bytes32) {
bytes32 usdollar = "aaa\u0024aaa";
return usdollar;
@ -15,17 +14,8 @@ contract test {
return eur;
}
function together() public pure returns (bytes32) {
function combined() public pure returns (bytes32) {
bytes32 res = "\u0024\u00A2\u20AC";
return res;
}
// this function returns an invalid unicode character
function invalidLiteral() public pure returns(bytes32) {
bytes32 invalid = "\u00xx";
return invalid;
}
}
// ----
// ParserError 8936: (678-681): Invalid escape sequence.

View File

@ -0,0 +1,7 @@
contract test {
function f() public pure returns (string memory) {
return "\xc1";
}
}
// ----
// TypeError 6359: (86-92): Return argument type literal_string (contains invalid UTF-8 sequence at position 0) is not implicitly convertible to expected type (type of first return variable) string memory.

View File

@ -0,0 +1,10 @@
contract test {
// this function returns an invalid unicode character
function invalidLiteral() public pure returns (bytes32) {
bytes32 invalid = "\u00xx";
return invalid;
}
}
// ----
// ParserError 8936: (162-165): Invalid escape sequence.

View File

@ -22,6 +22,7 @@
#include <liblangutil/Common.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string.hpp>
@ -198,8 +199,7 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff)
if (isprint(v))
os << v;
else
os << "\\x" << setw(2) << setfill('0') << hex << v;
os << "\\x" << toHex(v);
}
}
os << "\"";

View File

@ -99,11 +99,9 @@ string EwasmTranslationTest::interpret()
InterpreterState state;
state.maxTraceSize = 10000;
state.maxSteps = 100000;
WasmDialect dialect;
Interpreter interpreter(state, dialect);
try
{
interpreter(*m_object->code);
Interpreter::run(state, WasmDialect{}, *m_object->code);
}
catch (InterpreterTerminatedGeneric const&)
{

View File

@ -345,6 +345,69 @@ BOOST_AUTO_TEST_CASE(reuse_slots_function_with_gaps)
);
}
BOOST_AUTO_TEST_CASE(reuse_on_decl_assign_to_last_used)
{
string in = R"({
let x := 5
let y := x // y should reuse the stack slot of x
sstore(y, y)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 "
"DUP1 SWAP1 POP "
"DUP1 DUP2 SSTORE "
"POP "
);
}
BOOST_AUTO_TEST_CASE(reuse_on_decl_assign_to_last_used_expr)
{
string in = R"({
let x := 5
let y := add(x, 2) // y should reuse the stack slot of x
sstore(y, y)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 "
"PUSH1 0x2 DUP2 ADD "
"SWAP1 POP "
"DUP1 DUP2 SSTORE "
"POP "
);
}
BOOST_AUTO_TEST_CASE(reuse_on_decl_assign_to_not_last_used)
{
string in = R"({
let x := 5
let y := x // y should not reuse the stack slot of x, since x is still used below
sstore(y, x)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 "
"DUP1 "
"DUP2 DUP2 SSTORE "
"POP POP "
);
}
BOOST_AUTO_TEST_CASE(reuse_on_decl_assign_not_same_scope)
{
string in = R"({
let x := 5
{
let y := x // y should not reuse the stack slot of x, since x is not in the same scope
sstore(y, y)
}
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 "
"DUP1 "
"DUP1 DUP2 SSTORE "
"POP POP "
);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -88,10 +88,9 @@ string YulInterpreterTest::interpret()
InterpreterState state;
state.maxTraceSize = 10000;
state.maxSteps = 10000;
Interpreter interpreter(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
try
{
interpreter(*m_ast);
Interpreter::run(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast);
}
catch (InterpreterTerminatedGeneric const&)
{

View File

@ -44,12 +44,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
};
Interpreter interpreter(state, _dialect);
TerminationReason reason = TerminationReason::None;
try
{
interpreter(*_ast);
Interpreter::run(state, _dialect, *_ast);
}
catch (StepLimitReached const&)
{

View File

@ -409,17 +409,17 @@ u256 EVMInstructionInterpreter::eval(
case Instruction::SWAP14:
case Instruction::SWAP15:
case Instruction::SWAP16:
// --------------- EVM 2.0 ---------------
case Instruction::JUMPTO:
case Instruction::JUMPIF:
case Instruction::JUMPV:
case Instruction::JUMPSUB:
case Instruction::JUMPSUBV:
case Instruction::BEGINSUB:
case Instruction::BEGINDATA:
case Instruction::RETURNSUB:
case Instruction::PUTLOCAL:
case Instruction::GETLOCAL:
// --------------- EIP-615 ---------------
case Instruction::EIP615_JUMPTO:
case Instruction::EIP615_JUMPIF:
case Instruction::EIP615_JUMPV:
case Instruction::EIP615_JUMPSUB:
case Instruction::EIP615_JUMPSUBV:
case Instruction::EIP615_BEGINSUB:
case Instruction::EIP615_BEGINDATA:
case Instruction::EIP615_RETURNSUB:
case Instruction::EIP615_PUTLOCAL:
case Instruction::EIP615_GETLOCAL:
{
yulAssert(false, "");
return 0;

View File

@ -67,6 +67,50 @@ uint64_t clz64(uint64_t _v)
return r;
}
/// Count trailing zeros for uint32. Following WebAssembly rules, it returns 32 for @a _v being zero.
/// NOTE: the ctz builtin of the compiler may or may not do this
uint32_t ctz32(uint32_t _v)
{
if (_v == 0)
return 32;
uint32_t r = 0;
while (!(_v & 1))
{
r++;
_v >>= 1;
}
return r;
}
/// Count trailing zeros for uint64. Following WebAssembly rules, it returns 64 for @a _v being zero.
/// NOTE: the ctz builtin of the compiler may or may not do this
uint64_t ctz64(uint64_t _v)
{
if (_v == 0)
return 64;
uint64_t r = 0;
while (!(_v & 1))
{
r++;
_v >>= 1;
}
return r;
}
/// Count number of bits set for uint64
uint64_t popcnt(uint64_t _v)
{
uint64_t r = 0;
while (_v)
{
r += (_v & 1);
_v >>= 1;
}
return r;
}
}
using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
@ -77,11 +121,12 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
for (u256 const& a: _arguments)
arg.emplace_back(uint64_t(a & uint64_t(-1)));
if (_fun == "datasize"_yulstring)
string fun = _fun.str();
if (fun == "datasize")
return u256(keccak256(h256(_arguments.at(0)))) & 0xfff;
else if (_fun == "dataoffset"_yulstring)
else if (fun == "dataoffset")
return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff;
else if (_fun == "datacopy"_yulstring)
else if (fun == "datacopy")
{
// This is identical to codecopy.
if (accessMemory(_arguments.at(0), _arguments.at(2)))
@ -94,42 +139,42 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
);
return 0;
}
else if (_fun == "i32.drop"_yulstring || _fun == "i64.drop"_yulstring || _fun == "nop"_yulstring)
else if (fun == "i32.drop" || fun == "i64.drop" || fun == "nop")
return {};
else if (_fun == "i32.wrap_i64"_yulstring)
else if (fun == "i32.wrap_i64")
return arg.at(0) & uint32_t(-1);
else if (_fun == "i64.extend_i32_u"_yulstring)
else if (fun == "i64.extend_i32_u")
// Return the same as above because everything is u256 anyway.
return arg.at(0) & uint32_t(-1);
else if (_fun == "unreachable"_yulstring)
else if (fun == "unreachable")
{
logTrace(evmasm::Instruction::INVALID, {});
throw ExplicitlyTerminated();
}
else if (_fun == "i64.store"_yulstring)
else if (fun == "i64.store")
{
accessMemory(arg[0], 8);
writeMemoryWord(arg[0], arg[1]);
return 0;
}
else if (_fun == "i64.store8"_yulstring || _fun == "i32.store8"_yulstring)
else if (fun == "i64.store8" || fun == "i32.store8")
{
accessMemory(arg[0], 1);
writeMemoryByte(arg[0], static_cast<uint8_t>(arg[1] & 0xff));
return 0;
}
else if (_fun == "i64.load"_yulstring)
else if (fun == "i64.load")
{
accessMemory(arg[0], 8);
return readMemoryWord(arg[0]);
}
else if (_fun == "i32.store"_yulstring)
else if (fun == "i32.store")
{
accessMemory(arg[0], 4);
writeMemoryHalfWord(arg[0], arg[1]);
return 0;
}
else if (_fun == "i32.load"_yulstring)
else if (fun == "i32.load")
{
accessMemory(arg[0], 4);
return readMemoryHalfWord(arg[0]);
@ -139,8 +184,12 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
return clz64(arg[0] & uint32_t(-1)) - 32;
else if (_fun == "i64.clz"_yulstring)
return clz64(arg[0]);
else if (_fun == "i32.ctz"_yulstring)
return ctz32(uint32_t(arg[0] & uint32_t(-1)));
else if (_fun == "i64.ctz"_yulstring)
return ctz64(arg[0]);
string prefix = _fun.str();
string prefix = fun;
string suffix;
auto dot = prefix.find(".");
if (dot != string::npos)
@ -207,6 +256,8 @@ u256 EwasmBuiltinInterpreter::evalWasmBuiltin(string const& _fun, vector<Word> c
return arg[0] != arg[1] ? 1 : 0;
else if (_fun == "eqz")
return arg[0] == 0 ? 1 : 0;
else if (_fun == "popcnt")
return popcnt(arg[0]);
else if (_fun == "lt_u")
return arg[0] < arg[1] ? 1 : 0;
else if (_fun == "gt_u")

View File

@ -64,6 +64,12 @@ void InterpreterState::dumpTraceAndState(ostream& _out) const
_out << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
}
void Interpreter::run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast)
{
Scope scope;
Interpreter{_state, _dialect, scope}(_ast);
}
void Interpreter::operator()(ExpressionStatement const& _expressionStatement)
{
evaluateMulti(_expressionStatement.expression);
@ -94,8 +100,7 @@ void Interpreter::operator()(VariableDeclaration const& _declaration)
YulString varName = _declaration.variables.at(i).name;
solAssert(!m_variables.count(varName), "");
m_variables[varName] = values.at(i);
solAssert(!m_scopes.back().count(varName), "");
m_scopes.back().emplace(varName, nullptr);
m_scope->names.emplace(varName, nullptr);
}
}
@ -128,8 +133,8 @@ void Interpreter::operator()(ForLoop const& _forLoop)
{
solAssert(_forLoop.condition, "");
openScope();
ScopeGuard g([this]{ closeScope(); });
enterScope(_forLoop.pre);
ScopeGuard g([this]{ leaveScope(); });
for (auto const& statement: _forLoop.pre.statements)
{
@ -176,14 +181,13 @@ void Interpreter::operator()(Block const& _block)
m_state.trace.emplace_back("Interpreter execution step limit reached.");
throw StepLimitReached();
}
openScope();
enterScope(_block);
// Register functions.
for (auto const& statement: _block.statements)
if (holds_alternative<FunctionDefinition>(statement))
{
FunctionDefinition const& funDef = std::get<FunctionDefinition>(statement);
solAssert(!m_scopes.back().count(funDef.name), "");
m_scopes.back().emplace(funDef.name, &funDef);
m_scope->names.emplace(funDef.name, &funDef);
}
for (auto const& statement: _block.statements)
@ -193,29 +197,41 @@ void Interpreter::operator()(Block const& _block)
break;
}
closeScope();
leaveScope();
}
u256 Interpreter::evaluate(Expression const& _expression)
{
ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes);
ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables);
ev.visit(_expression);
return ev.value();
}
vector<u256> Interpreter::evaluateMulti(Expression const& _expression)
{
ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes);
ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables);
ev.visit(_expression);
return ev.values();
}
void Interpreter::closeScope()
void Interpreter::enterScope(Block const& _block)
{
for (auto const& [var, funDeclaration]: m_scopes.back())
if (!m_scope->subScopes.count(&_block))
m_scope->subScopes[&_block] = make_unique<Scope>(Scope{
{},
{},
m_scope
});
m_scope = m_scope->subScopes[&_block].get();
}
void Interpreter::leaveScope()
{
for (auto const& [var, funDeclaration]: m_scope->names)
if (!funDeclaration)
solAssert(m_variables.erase(var) == 1, "");
m_scopes.pop_back();
m_variables.erase(var);
m_scope = m_scope->parent;
yulAssert(m_scope, "");
}
void ExpressionEvaluator::operator()(Literal const& _literal)
@ -253,10 +269,15 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
return;
}
auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name);
Scope* scope = &m_scope;
for (; scope; scope = scope->parent)
if (scope->names.count(_funCall.functionName.name))
break;
yulAssert(scope, "");
solAssert(fun, "Function not found.");
solAssert(m_values.size() == fun->parameters.size(), "");
FunctionDefinition const* fun = scope->names.at(_funCall.functionName.name);
yulAssert(fun, "Function not found.");
yulAssert(m_values.size() == fun->parameters.size(), "");
map<YulString, u256> variables;
for (size_t i = 0; i < fun->parameters.size(); ++i)
variables[fun->parameters.at(i).name] = m_values.at(i);
@ -264,7 +285,7 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
variables[fun->returnVariables.at(i).name] = 0;
m_state.controlFlowState = ControlFlowState::Default;
Interpreter interpreter(m_state, m_dialect, variables, functionScopes);
Interpreter interpreter(m_state, m_dialect, *scope, std::move(variables));
interpreter(fun->body);
m_state.controlFlowState = ControlFlowState::Default;
@ -297,27 +318,3 @@ void ExpressionEvaluator::evaluateArgs(vector<Expression> const& _expr)
m_values = std::move(values);
std::reverse(m_values.begin(), m_values.end());
}
pair<
vector<map<YulString, FunctionDefinition const*>>,
FunctionDefinition const*
> ExpressionEvaluator::findFunctionAndScope(YulString _functionName) const
{
FunctionDefinition const* fun = nullptr;
std::vector<std::map<YulString, FunctionDefinition const*>> newScopes;
for (auto const& scope: m_scopes)
{
// Copy over all functions.
newScopes.emplace_back();
for (auto const& [name, funDef]: scope)
if (funDef)
newScopes.back().emplace(name, funDef);
// Stop at the called function.
if (scope.count(_functionName))
{
fun = scope.at(_functionName);
break;
}
}
return {move(newScopes), fun};
}

View File

@ -96,23 +96,37 @@ struct InterpreterState
void dumpTraceAndState(std::ostream& _out) const;
};
/**
* Scope structure built and maintained during execution.
*/
struct Scope
{
/// Used for variables and functions. Value is nullptr for variables.
std::map<YulString, FunctionDefinition const*> names;
std::map<Block const*, std::unique_ptr<Scope>> subScopes;
Scope* parent = nullptr;
};
/**
* Yul interpreter.
*/
class Interpreter: public ASTWalker
{
public:
static void run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast);
Interpreter(
InterpreterState& _state,
Dialect const& _dialect,
std::map<YulString, u256> _variables = {},
std::vector<std::map<YulString, FunctionDefinition const*>> _scopes = {}
Scope& _scope,
std::map<YulString, u256> _variables = {}
):
m_dialect(_dialect),
m_state(_state),
m_variables(std::move(_variables)),
m_scopes(std::move(_scopes))
{}
m_scope(&_scope)
{
}
void operator()(ExpressionStatement const& _statement) override;
void operator()(Assignment const& _assignment) override;
@ -136,18 +150,14 @@ private:
/// Evaluates the expression and returns its value.
std::vector<u256> evaluateMulti(Expression const& _expression);
void openScope() { m_scopes.emplace_back(); }
/// Unregisters variables and functions.
void closeScope();
void enterScope(Block const& _block);
void leaveScope();
Dialect const& m_dialect;
InterpreterState& m_state;
/// Values of variables.
std::map<YulString, u256> m_variables;
/// Scopes of variables and functions. Used for lookup, clearing at end of blocks
/// and passing over the visible functions across function calls.
/// The pointer is nullptr if and only if the key is a variable.
std::vector<std::map<YulString, FunctionDefinition const*>> m_scopes;
Scope* m_scope;
};
/**
@ -159,13 +169,13 @@ public:
ExpressionEvaluator(
InterpreterState& _state,
Dialect const& _dialect,
std::map<YulString, u256> const& _variables,
std::vector<std::map<YulString, FunctionDefinition const*>> const& _scopes
Scope& _scope,
std::map<YulString, u256> const& _variables
):
m_state(_state),
m_dialect(_dialect),
m_variables(_variables),
m_scopes(_scopes)
m_scope(_scope)
{}
void operator()(Literal const&) override;
@ -184,19 +194,11 @@ private:
/// stores it in m_value.
void evaluateArgs(std::vector<Expression> const& _expr);
/// Finds the function called @a _functionName in the current scope stack and returns
/// the function's scope stack (with variables removed) and definition.
std::pair<
std::vector<std::map<YulString, FunctionDefinition const*>>,
FunctionDefinition const*
> findFunctionAndScope(YulString _functionName) const;
InterpreterState& m_state;
Dialect const& m_dialect;
/// Values of variables.
std::map<YulString, u256> const& m_variables;
/// Stack of scopes in the current context.
std::vector<std::map<YulString, FunctionDefinition const*>> const& m_scopes;
Scope& m_scope;
/// Current value of the expression
std::vector<u256> m_values;
};

View File

@ -88,11 +88,10 @@ void interpret(string const& _source)
InterpreterState state;
state.maxTraceSize = 10000;
Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
Interpreter interpreter(state, dialect);
try
{
interpreter(*ast);
Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
Interpreter::run(state, dialect, *ast);
}
catch (InterpreterTerminatedGeneric const&)
{