Merge pull request #9862 from ethereum/develop

Merge develop into breaking
This commit is contained in:
chriseth 2020-09-23 12:22:32 +02:00 committed by GitHub
commit 0c6dc1dce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
126 changed files with 2962 additions and 320 deletions

View File

@ -1,3 +1,5 @@
$ErrorActionPreference = "Stop"
cd "$PSScriptRoot\.."
if ("$Env:FORCE_RELEASE") {
@ -9,5 +11,8 @@ mkdir build
cd build
$boost_dir=(Resolve-Path $PSScriptRoot\..\deps\boost\lib\cmake\Boost-*)
..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" ..
if ( -not $? ) { throw "CMake configure failed." }
msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal
if ( -not $? ) { throw "Build failed." }
..\deps\cmake\bin\cmake --build . -j 5 --target install --config Release
if ( -not $? ) { throw "Install target failed." }

View File

@ -250,6 +250,16 @@ defaults:
requires:
- b_ubu_ossfuzz
- workflow_win: &workflow_win
<<: *workflow_trigger_on_tags
requires:
- b_win
- workflow_win_release: &workflow_win_release
<<: *workflow_trigger_on_tags
requires:
- b_win_release
# --------------------------------------------------------------------------
# Notification Templates
- gitter_notify_failure: &gitter_notify_failure
@ -903,6 +913,9 @@ jobs:
- run:
name: "Building solidity"
command: .circleci/build_win.ps1
- run:
name: "Run solc.exe to make sure build was successful."
command: .\build\solc\Release\solc.exe --version
- store_artifacts: *artifact_solc_windows
- persist_to_workspace: *artifacts_build_dir
@ -911,6 +924,24 @@ jobs:
environment:
FORCE_RELEASE: ON
t_win: &t_win
executor:
name: win/default
shell: powershell.exe
steps:
- checkout
- attach_workspace:
at: build
- run:
name: "Install evmone"
command: scripts/install_evmone.ps1
- run:
name: "Run soltest"
command: .circleci/soltest.ps1
- store_artifacts: *artifacts_test_results
t_win_release:
<<: *t_win
workflows:
version: 2
@ -966,6 +997,8 @@ workflows:
# Windows build and tests
- b_win: *workflow_trigger_on_tags
- b_win_release: *workflow_trigger_on_tags
- t_win: *workflow_win
- t_win_release: *workflow_win_release
nightly:

12
.circleci/soltest.ps1 Executable file
View File

@ -0,0 +1,12 @@
$ErrorActionPreference = "Stop"
cd "$PSScriptRoot\.."
.\build\solc\Release\solc.exe --version
if ( -not $? ) { throw "Cannot execute solc --version." }
mkdir test_results
.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result.xml -- --no-smt
if ( -not $? ) { throw "Unoptimized soltest run failed." }
.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result_opt.xml -- --optimize --no-smt
if ( -not $? ) { throw "Optimized soltest run failed." }

View File

@ -16,15 +16,18 @@ Compiler Features:
* SMTChecker: Support shifts.
* SMTChecker: Support structs.
* SMTChecker: Support ``type(T).min``, ``type(T).max``, and ``type(I).interfaceId``.
* SMTChecker: Support ``address`` type conversion with literals, e.g. ``address(0)``.
* Yul Optimizer: Prune unused parameters in functions.
* Yul Optimizer: Inline into functions further down in the call graph first.
* Yul Optimizer: Try to simplify function names.
* Yul IR Generator: Report source locations related to unimplemented features.
* Optimizer: Optimize ``exp`` when base is 0, 1 or 2.
Bugfixes:
* Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``.
* Type Checker: Add missing check against nested dynamic arrays in ABI encoding functions when ABIEncoderV2 is disabled.
* Type Checker: Disallow ``virtual`` for modifiers in libraries.
* Type Checker: Correct the error message for invalid named parameter in a call to refer to the right argument.
* Type Checker: Correct the warning for homonymous, but not shadowing declarations.
* ViewPureChecker: Prevent visibility check on constructors.
* Type system: Fix internal error on implicit conversion of contract instance to the type of its ``super``.

View File

@ -31,6 +31,7 @@
- [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page.
### Homebrew and MacOS
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/Homebrew/homebrew-core/blob/master/Formula/solidity.rb
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb
- [ ] Take the binary from the ``b_osx`` run of the released commit in circle-ci and add it to the release page as ``solc-macos``.

View File

@ -105,8 +105,12 @@ otherwise, the ``value`` option would not be available.
parentheses at the end perform the actual call. So in this case, the
function is not called and the ``value`` and ``gas`` settings are lost.
Function calls cause exceptions if the called contract does not exist (in the
sense that the account does not contain code) or if the called contract itself
Due to the fact that the EVM considers a call to a non-existing contract to
always succeed, Solidity uses the ``extcodesize`` opcode to check that
the contract that is about to be called actually exists (it contains code)
and causes an exception if it does not.
Function calls also cause exceptions if the called contract itself
throws an exception or goes out of gas.
.. warning::

View File

@ -952,6 +952,26 @@ option.
See :ref:`Using the Commandline Compiler <commandline-compiler>` for details about the Solidity linker.
memoryguard
^^^^^^^^^^^
This function is available in the EVM dialect with objects. The caller of
``let ptr := memoryguard(size)`` (where ``size`` has to be a literal number)
promises that they only use memory in either the range ``[0, size)`` or the
unbounded range starting at ``ptr``.
Since the presence of a ``memoryguard`` call indicates that all memory access
adheres to this restriction, it allows the optimizer to perform additional
optimization steps, for example the stack limit evader, which attempts to move
stack variables that would otherwise be unreachable to memory.
The Yul optimizer promises to only use the memory range ``[size, ptr)`` for its purposes.
If the optimizer does not need to reserve any memory, it holds that ``ptr == size``.
``memoryguard`` can be called multiple times, but needs to have the same literal as argument
within one Yul subobject. If at least one ``memoryguard`` call is found in a subobject,
the additional optimiser steps will be run on it.
.. _yul-object:

View File

@ -615,12 +615,13 @@ std::vector<SimplificationRule<Pattern>> evmRuleList(
Pattern,
Pattern,
Pattern,
Pattern,
Pattern X,
Pattern,
Pattern
)
{
using Builtins = typename Pattern::Builtins;
using Word = typename Pattern::Word;
std::vector<SimplificationRule<Pattern>> rules;
if (_evmVersion.hasSelfBalance())
@ -629,6 +630,20 @@ std::vector<SimplificationRule<Pattern>> evmRuleList(
[]() -> Pattern { return Instruction::SELFBALANCE; }
});
rules.emplace_back(
Builtins::EXP(0, X),
[=]() -> Pattern { return Builtins::ISZERO(X); }
);
rules.emplace_back(
Builtins::EXP(1, X),
[=]() -> Pattern { return Word(1); }
);
if (_evmVersion.hasBitwiseShifting())
rules.emplace_back(
Builtins::EXP(2, X),
[=]() -> Pattern { return Builtins::SHL(X, 1); }
);
return rules;
}

View File

@ -51,6 +51,8 @@ bool hasEqualNameAndParameters(T const& _a, B const& _b)
bool ContractLevelChecker::check(ContractDefinition const& _contract)
{
_contract.annotation().unimplementedDeclarations = std::vector<Declaration const*>();
checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract);
m_overrideChecker.check(_contract);
@ -210,9 +212,10 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedDeclarations` has already been
// pre-filled by `checkBaseConstructorArguments`.
//
for (auto const& proxy: proxies)
if (proxy.unimplemented())
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
_contract.annotation().unimplementedDeclarations->push_back(proxy.declaration());
if (_contract.abstract())
{
@ -229,17 +232,17 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
if (
_contract.contractKind() == ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedDeclarations.empty()
!_contract.annotation().unimplementedDeclarations->empty()
)
{
SecondarySourceLocation ssl;
for (auto declaration: _contract.annotation().unimplementedDeclarations)
for (auto declaration: *_contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(
3656_error,
_contract.location(),
ssl,
"Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."
"Contract \"" + *_contract.annotation().canonicalName + "\" should be marked as abstract."
);
}
}
@ -289,7 +292,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
if (!_contract.annotation().baseConstructorArguments.count(constructor))
_contract.annotation().unimplementedDeclarations.push_back(constructor);
_contract.annotation().unimplementedDeclarations->push_back(constructor);
}
void ContractLevelChecker::annotateBaseConstructorArguments(
@ -465,7 +468,6 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition
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()))
@ -473,7 +475,7 @@ void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
size += variable->annotation().type->storageSizeUpperBound();
if (size >= bigint(1) << 256)
{
m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage.");
m_errorReporter.typeError(7676_error, _contract.location(), "Contract requires too much storage.");
break;
}
}

View File

@ -167,7 +167,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
// If this is not an ordinary assignment, we write and read at the same time.
bool write = _expression.annotation().willBeWrittenTo;
bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment;
bool read = !_expression.annotation().willBeWrittenTo || !*_expression.annotation().lValueOfOrdinaryAssignment;
if (write)
{
if (!m_currentConstructor)

View File

@ -74,7 +74,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& node: _sourceUnit.nodes())
if (auto imp = dynamic_cast<ImportDirective const*>(node.get()))
{
string const& path = imp->annotation().absolutePath;
string const& path = *imp->annotation().absolutePath;
if (!_sourceUnits.count(path))
{
m_errorReporter.declarationError(

View File

@ -158,17 +158,16 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
}
if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage)
{
TypePointer varType = _variable.annotation().type;
for (Type const* subtype: frontend::oversizedSubtypes(*varType))
{
string message = "Type " + subtype->toString(true) +
" 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.";
m_errorReporter.warning(7325_error, _variable.typeName().location(), message);
}
}
if (auto varType = dynamic_cast<CompositeType const*>(_variable.annotation().type))
for (Type const* type: varType->fullDecomposition())
if (type->storageSizeUpperBound() >= (bigint(1) << 64))
{
string message = "Type " + type->toString(true) +
" 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.";
m_errorReporter.warning(7325_error, _variable.typeName().location(), message);
}
return true;
}
@ -186,7 +185,7 @@ bool StaticAnalyzer::visit(Return const& _return)
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
if (*_statement.expression().annotation().isPure)
m_errorReporter.warning(
6133_error,
_statement.location(),
@ -288,7 +287,7 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
bool StaticAnalyzer::visit(BinaryOperation const& _operation)
{
if (
_operation.rightExpression().annotation().isPure &&
*_operation.rightExpression().annotation().isPure &&
(_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
)
if (auto rhs = dynamic_cast<RationalNumberType const*>(
@ -313,7 +312,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
{
solAssert(_functionCall.arguments().size() == 3, "");
if (_functionCall.arguments()[2]->annotation().isPure)
if (*_functionCall.arguments()[2]->annotation().isPure)
if (auto lastArg = dynamic_cast<RationalNumberType const*>(
ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
))

View File

@ -538,7 +538,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!_variable.value())
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
else if (!_variable.value()->annotation().isPure)
else if (!*_variable.value()->annotation().isPure)
m_errorReporter.typeError(
8349_error,
_variable.value()->location(),
@ -1296,11 +1296,14 @@ bool TypeChecker::visit(Conditional const& _conditional)
}
}
_conditional.annotation().isConstant = false;
_conditional.annotation().type = commonType;
_conditional.annotation().isPure =
_conditional.condition().annotation().isPure &&
_conditional.trueExpression().annotation().isPure &&
_conditional.falseExpression().annotation().isPure;
*_conditional.condition().annotation().isPure &&
*_conditional.trueExpression().annotation().isPure &&
*_conditional.falseExpression().annotation().isPure;
_conditional.annotation().isLValue = false;
if (_conditional.annotation().willBeWrittenTo)
m_errorReporter.typeError(
@ -1354,6 +1357,9 @@ bool TypeChecker::visit(Assignment const& _assignment)
);
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
_assignment.annotation().isPure = false;
_assignment.annotation().isLValue = false;
_assignment.annotation().isConstant = false;
checkExpressionAssignment(*t, _assignment.leftHandSide());
@ -1401,6 +1407,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
bool TypeChecker::visit(TupleExpression const& _tuple)
{
_tuple.annotation().isConstant = false;
vector<ASTPointer<Expression>> const& components = _tuple.components();
TypePointers types;
@ -1413,7 +1420,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
requireLValue(
*component,
_tuple.annotation().lValueOfOrdinaryAssignment
*_tuple.annotation().lValueOfOrdinaryAssignment
);
types.push_back(type(*component));
}
@ -1425,6 +1432,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().type = TypeProvider::tuple(move(types));
// If some of the components are not LValues, the error is reported above.
_tuple.annotation().isLValue = true;
_tuple.annotation().isPure = false;
}
else
{
@ -1464,7 +1472,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
}
if (!components[i]->annotation().isPure)
if (!*components[i]->annotation().isPure)
isPure = false;
}
_tuple.annotation().isPure = isPure;
@ -1495,6 +1503,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().type = TypeProvider::tuple(move(types));
}
_tuple.annotation().isLValue = false;
}
return false;
}
@ -1522,7 +1531,9 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
t = subExprType;
}
_operation.annotation().type = t;
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
_operation.annotation().isConstant = false;
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
_operation.annotation().isLValue = false;
return false;
}
@ -1553,8 +1564,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
TypeProvider::boolean() :
commonType;
_operation.annotation().isPure =
_operation.leftExpression().annotation().isPure &&
_operation.rightExpression().annotation().isPure;
*_operation.leftExpression().annotation().isPure &&
*_operation.rightExpression().annotation().isPure;
_operation.annotation().isLValue = false;
_operation.annotation().isConstant = false;
if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
{
@ -2091,18 +2104,17 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
{
bool not_all_mapped = false;
for (size_t i = 0; i < paramArgMap.size(); i++)
for (size_t i = 0; i < argumentNames.size(); i++)
{
size_t j;
for (j = 0; j < argumentNames.size(); j++)
if (parameterNames[i] == *argumentNames[j])
for (j = 0; j < parameterNames.size(); j++)
if (parameterNames[j] == *argumentNames[i])
break;
if (j < argumentNames.size())
paramArgMap[i] = arguments[j].get();
if (j < parameterNames.size())
paramArgMap[j] = arguments[i].get();
else
{
paramArgMap[i] = nullptr;
not_all_mapped = true;
m_errorReporter.typeError(
4974_error,
@ -2175,7 +2187,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (ASTPointer<Expression const> const& argument: arguments)
{
argument->accept(*this);
if (!argument->annotation().isPure)
if (!*argument->annotation().isPure)
argumentsArePure = false;
}
@ -2198,6 +2210,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// Determine function call kind and function type for this FunctionCall node
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
FunctionTypePointer functionType = nullptr;
funcCallAnno.isConstant = false;
bool isLValue = false;
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
switch (expressionType->category())
@ -2209,7 +2224,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// Purity for function calls also depends upon the callee and its FunctionType
funcCallAnno.isPure =
argumentsArePure &&
_functionCall.expression().annotation().isPure &&
*_functionCall.expression().annotation().isPure &&
functionType &&
functionType->isPure();
@ -2217,7 +2232,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
funcCallAnno.isLValue = functionType->parameterTypes().empty();
isLValue = functionType->parameterTypes().empty();
break;
@ -2237,13 +2252,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
);
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
funcCallAnno.isPure = argumentsArePure;
}
else
{
funcCallAnno.kind = FunctionCallKind::TypeConversion;
funcCallAnno.isPure = argumentsArePure;
}
funcCallAnno.isPure = argumentsArePure;
break;
}
@ -2256,6 +2269,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
break;
}
funcCallAnno.isLValue = isLValue;
// Determine return types
switch (*funcCallAnno.kind)
{
@ -2326,6 +2341,9 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
_functionCallOptions.expression().accept(*this);
_functionCallOptions.annotation().isPure = false;
_functionCallOptions.annotation().isConstant = false;
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
if (!expressionFunctionType)
{
@ -2456,6 +2474,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
TypePointer type = _newExpression.typeName().annotation().type;
solAssert(!!type, "Type name not resolved.");
_newExpression.annotation().isConstant = false;
if (auto contractName = dynamic_cast<UserDefinedTypeName const*>(&_newExpression.typeName()))
{
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
@ -2486,6 +2506,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
}
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
_newExpression.annotation().isPure = false;
}
else if (type->category() == Type::Category::Array)
{
@ -2542,6 +2563,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
auto& annotation = _memberAccess.annotation();
annotation.isConstant = false;
if (possibleMembers.empty())
{
if (initialMemberCount == 0 && !dynamic_cast<ArraySliceType const*>(exprType))
@ -2674,11 +2697,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
functionType &&
functionType->kind() == FunctionType::Kind::Declaration
)
annotation.isPure = _memberAccess.expression().annotation().isPure;
annotation.isPure = *_memberAccess.expression().annotation().isPure;
}
}
else if (exprType->category() == Type::Category::Module)
annotation.isPure = _memberAccess.expression().annotation().isPure;
{
annotation.isPure = *_memberAccess.expression().annotation().isPure;
annotation.isLValue = false;
}
else
annotation.isLValue = false;
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this limited for now.
@ -2694,11 +2722,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
)
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
annotation.isPure = parentAccess->expression().annotation().isPure;
bool isPure = *parentAccess->expression().annotation().isPure;
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
annotation.isPure = true;
isPure = true;
annotation.isPure = isPure;
}
if (auto magicType = dynamic_cast<MagicType const*>(exprType))
{
if (magicType->kind() == MagicType::Kind::ABI)
@ -2746,16 +2777,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.isPure = true;
}
if (!annotation.isPure.set())
annotation.isPure = false;
return false;
}
bool TypeChecker::visit(IndexAccess const& _access)
{
_access.annotation().isConstant = false;
_access.baseExpression().accept(*this);
TypePointer baseType = type(_access.baseExpression());
TypePointer resultType = nullptr;
bool isLValue = false;
bool isPure = _access.baseExpression().annotation().isPure;
bool isPure = *_access.baseExpression().annotation().isPure;
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
@ -2857,7 +2892,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
}
_access.annotation().type = resultType;
_access.annotation().isLValue = isLValue;
if (index && !index->annotation().isPure)
if (index && !*index->annotation().isPure)
isPure = false;
_access.annotation().isPure = isPure;
@ -2866,21 +2901,22 @@ bool TypeChecker::visit(IndexAccess const& _access)
bool TypeChecker::visit(IndexRangeAccess const& _access)
{
_access.annotation().isConstant = false;
_access.baseExpression().accept(*this);
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
bool isPure = _access.baseExpression().annotation().isPure;
bool isPure = *_access.baseExpression().annotation().isPure;
if (Expression const* start = _access.startExpression())
{
expectType(*start, *TypeProvider::uint256());
if (!start->annotation().isPure)
if (!*start->annotation().isPure)
isPure = false;
}
if (Expression const* end = _access.endExpression())
{
expectType(*end, *TypeProvider::uint256());
if (!end->annotation().isPure)
if (!*end->annotation().isPure)
isPure = false;
}
@ -3023,20 +3059,22 @@ bool TypeChecker::visit(Identifier const& _identifier)
!!annotation.referencedDeclaration,
"Referenced declaration is null after overload resolution."
);
bool isConstant = false;
annotation.isLValue = annotation.referencedDeclaration->isLValue();
annotation.type = annotation.referencedDeclaration->type();
solAssert(annotation.type, "Declaration referenced before type could be determined.");
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
annotation.isPure = isConstant = variableDeclaration->isConstant();
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
{
if (dynamic_cast<FunctionType const*>(annotation.type))
annotation.isPure = true;
}
annotation.isPure = dynamic_cast<FunctionType const*>(annotation.type);
else if (dynamic_cast<TypeType const*>(annotation.type))
annotation.isPure = true;
else if (dynamic_cast<ModuleType const*>(annotation.type))
annotation.isPure = true;
else
annotation.isPure = false;
annotation.isConstant = isConstant;
// Check for deprecated function names.
// The check is done here for the case without an actual function call.
@ -3077,6 +3115,8 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
_expr.annotation().isPure = true;
_expr.annotation().isLValue = false;
_expr.annotation().isConstant = false;
}
void TypeChecker::endVisit(Literal const& _literal)
@ -3132,6 +3172,8 @@ void TypeChecker::endVisit(Literal const& _literal)
m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value.");
_literal.annotation().isPure = true;
_literal.annotation().isLValue = false;
_literal.annotation().isConstant = false;
}
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
@ -3216,11 +3258,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);
if (_expression.annotation().isLValue)
if (*_expression.annotation().isLValue)
return;
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
if (_expression.annotation().isConstant)
if (*_expression.annotation().isConstant)
return { 6520_error, "Cannot assign to a constant variable." };
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))

View File

@ -492,7 +492,7 @@ CallableDeclaration const* Scopable::functionOrModifierDefinition() const
string Scopable::sourceUnitName() const
{
return sourceUnit().annotation().path;
return *sourceUnit().annotation().path;
}
DeclarationAnnotation& Declaration::annotation() const

View File

@ -47,6 +47,7 @@ namespace solidity::frontend
class Type;
using TypePointer = Type const*;
using namespace util;
struct ASTAnnotation
{
@ -88,9 +89,9 @@ struct StructurallyDocumentedAnnotation
struct SourceUnitAnnotation: ASTAnnotation
{
/// The "absolute" (in the compiler sense) path of this source unit.
std::string path;
SetOnce<std::string> path;
/// The exported symbols (all global symbols).
std::map<ASTString, std::vector<Declaration const*>> exportedSymbols;
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
/// Experimental features.
std::set<ExperimentalFeature> experimentalFeatures;
};
@ -122,7 +123,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
struct ImportAnnotation: DeclarationAnnotation
{
/// The absolute path of the source unit to import.
std::string absolutePath;
SetOnce<std::string> absolutePath;
/// The actual source unit.
SourceUnit const* sourceUnit = nullptr;
};
@ -130,7 +131,7 @@ struct ImportAnnotation: DeclarationAnnotation
struct TypeDeclarationAnnotation: DeclarationAnnotation
{
/// The name of this type, prefixed by proper namespaces if globally accessible.
std::string canonicalName;
SetOnce<std::string> canonicalName;
};
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
@ -149,7 +150,7 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// List of functions and modifiers without a body. Can also contain functions from base classes.
std::vector<Declaration const*> unimplementedDeclarations;
std::optional<std::vector<Declaration const*>> unimplementedDeclarations;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
@ -243,16 +244,16 @@ struct ExpressionAnnotation: ASTAnnotation
/// Inferred type of the expression.
TypePointer type = nullptr;
/// Whether the expression is a constant variable
bool isConstant = false;
SetOnce<bool> isConstant;
/// Whether the expression is pure, i.e. compile-time constant.
bool isPure = false;
SetOnce<bool> isPure;
/// Whether it is an LValue (i.e. something that can be assigned to).
bool isLValue = false;
SetOnce<bool> isLValue;
/// Whether the expression is used in a context where the LValue is actually required.
bool willBeWrittenTo = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
SetOnce<bool> lValueOfOrdinaryAssignment;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resolution

View File

@ -39,10 +39,33 @@
#include <vector>
#include <algorithm>
#include <limits>
#include <type_traits>
using namespace std;
using namespace solidity::langutil;
namespace
{
template<typename V, template<typename> typename C>
void addIfSet(std::vector<pair<string, Json::Value>>& _attributes, string const& _name, C<V> const& _value)
{
if constexpr (std::is_same_v<C<V>, solidity::util::SetOnce<V>>)
{
if (!_value.set())
return;
}
else if constexpr (std::is_same_v<C<V>, optional<V>>)
{
if (!_value.has_value())
return;
}
_attributes.emplace_back(_name, *_value);
}
}
namespace solidity::frontend
{
@ -181,12 +204,14 @@ void ASTJsonConverter::appendExpressionAttributes(
{
std::vector<pair<string, Json::Value>> exprAttributes = {
make_pair("typeDescriptions", typePointerToJson(_annotation.type)),
make_pair("isConstant", _annotation.isConstant),
make_pair("isPure", _annotation.isPure),
make_pair("isLValue", _annotation.isLValue),
make_pair("lValueRequested", _annotation.willBeWrittenTo),
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
};
addIfSet(exprAttributes, "isLValue", _annotation.isLValue);
addIfSet(exprAttributes, "isPure", _annotation.isPure);
addIfSet(exprAttributes, "isConstant", _annotation.isConstant);
_attributes += exprAttributes;
}
@ -214,23 +239,27 @@ Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
bool ASTJsonConverter::visit(SourceUnit const& _node)
{
Json::Value exportedSymbols = Json::objectValue;
for (auto const& sym: _node.annotation().exportedSymbols)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
make_pair("nodes", toJson(_node.nodes()))
};
if (_node.annotation().exportedSymbols.set())
{
exportedSymbols[sym.first] = Json::arrayValue;
for (Declaration const* overload: sym.second)
exportedSymbols[sym.first].append(nodeId(*overload));
}
setJsonNode(
_node,
"SourceUnit",
Json::Value exportedSymbols = Json::objectValue;
for (auto const& sym: *_node.annotation().exportedSymbols)
{
make_pair("absolutePath", _node.annotation().path),
make_pair("exportedSymbols", move(exportedSymbols)),
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
make_pair("nodes", toJson(_node.nodes()))
exportedSymbols[sym.first] = Json::arrayValue;
for (Declaration const* overload: sym.second)
exportedSymbols[sym.first].append(nodeId(*overload));
}
);
attributes.emplace_back("exportedSymbols", exportedSymbols);
};
addIfSet(attributes, "absolutePath", _node.annotation().path);
setJsonNode(_node, "SourceUnit", std::move(attributes));
return false;
}
@ -249,10 +278,12 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("file", _node.path()),
make_pair("absolutePath", _node.annotation().absolutePath),
make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)),
make_pair("scope", idOrNull(_node.scope()))
};
addIfSet(attributes, "absolutePath", _node.annotation().absolutePath);
attributes.emplace_back("unitAlias", _node.name());
Json::Value symbolAliases(Json::arrayValue);
for (auto const& symbolAlias: _node.symbolAliases())
@ -270,18 +301,23 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
setJsonNode(_node, "ContractDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
make_pair("nodes", toJson(_node.subNodes())),
make_pair("scope", idOrNull(_node.scope()))
});
};
if (_node.annotation().unimplementedDeclarations.has_value())
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
if (!_node.annotation().linearizedBaseContracts.empty())
attributes.emplace_back("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts));
setJsonNode(_node, "ContractDefinition", std::move(attributes));
return false;
}
@ -305,23 +341,31 @@ bool ASTJsonConverter::visit(UsingForDirective const& _node)
bool ASTJsonConverter::visit(StructDefinition const& _node)
{
setJsonNode(_node, "StructDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("canonicalName", _node.annotation().canonicalName),
make_pair("members", toJson(_node.members())),
make_pair("scope", idOrNull(_node.scope()))
});
};
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
setJsonNode(_node, "StructDefinition", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(EnumDefinition const& _node)
{
setJsonNode(_node, "EnumDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("canonicalName", _node.annotation().canonicalName),
make_pair("members", toJson(_node.members()))
});
};
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
setJsonNode(_node, "EnumDefinition", std::move(attributes));
return false;
}

View File

@ -44,6 +44,7 @@
#include <boost/range/algorithm/copy.hpp>
#include <limits>
#include <unordered_set>
#include <utility>
using namespace std;
@ -54,57 +55,6 @@ using namespace solidity::frontend;
namespace
{
struct TypeComp
{
bool operator()(Type const* lhs, Type const* rhs) const
{
solAssert(lhs && rhs, "");
return lhs->richIdentifier() < rhs->richIdentifier();
}
};
using TypeSet = std::set<Type const*, TypeComp>;
void oversizedSubtypesInner(
Type const& _type,
bool _includeType,
set<StructDefinition const*>& _structsSeen,
TypeSet& _oversizedSubtypes
)
{
switch (_type.category())
{
case Type::Category::Array:
{
auto const& t = dynamic_cast<ArrayType const&>(_type);
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes);
break;
}
case Type::Category::Struct:
{
auto const& t = dynamic_cast<StructType const&>(_type);
if (_structsSeen.count(&t.structDefinition()))
return;
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
oversizedSubtypesInner(*m.type, false, _structsSeen, _oversizedSubtypes);
_structsSeen.erase(&t.structDefinition());
break;
}
case Type::Category::Mapping:
{
auto const* valueType = dynamic_cast<MappingType const&>(_type).valueType();
oversizedSubtypesInner(*valueType, true, _structsSeen, _oversizedSubtypes);
break;
}
default:
break;
}
}
/// Check whether (_base ** _exp) fits into 4096 bits.
bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
{
@ -201,16 +151,6 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
}
vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type)
{
set<StructDefinition const*> structsSeen;
TypeSet oversized;
oversizedSubtypesInner(_type, true, structsSeen, oversized);
vector<frontend::Type const*> res;
copy(oversized.cbegin(), oversized.cend(), back_inserter(res));
return res;
}
void Type::clearCache() const
{
m_members.clear();
@ -404,10 +344,16 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
return encodingType;
TypePointer baseType = encodingType;
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType))
{
baseType = arrayType->baseType();
if (dynamic_cast<StructType const*>(baseType))
if (!_encoderV2)
auto const* baseArrayType = dynamic_cast<ArrayType const*>(baseType);
if (!_encoderV2 && baseArrayType && baseArrayType->isDynamicallySized())
return nullptr;
}
if (!_encoderV2 && dynamic_cast<StructType const*>(baseType))
return nullptr;
return encodingType;
}
@ -1613,6 +1559,21 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const
return nullptr;
}
vector<Type const*> CompositeType::fullDecomposition() const
{
vector<Type const*> res = {this};
unordered_set<string> seen = {richIdentifier()};
for (size_t k = 0; k < res.size(); ++k)
if (auto composite = dynamic_cast<CompositeType const*>(res[k]))
for (Type const* next: composite->decomposition())
if (seen.count(next->richIdentifier()) == 0)
{
seen.insert(next->richIdentifier());
res.push_back(next);
}
return res;
}
Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const
{
return TypeProvider::withLocation(this, _location, _isPointer);
@ -2155,7 +2116,7 @@ string ContractType::toString(bool) const
string ContractType::canonicalName() const
{
return m_contract.annotation().canonicalName;
return *m_contract.annotation().canonicalName;
}
MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const
@ -2406,7 +2367,7 @@ bool StructType::containsNestedMapping() const
string StructType::toString(bool _short) const
{
string ret = "struct " + m_struct.annotation().canonicalName;
string ret = "struct " + *m_struct.annotation().canonicalName;
if (!_short)
ret += " " + stringForReferencePart();
return ret;
@ -2585,7 +2546,7 @@ string StructType::signatureInExternalFunction(bool _structsByName) const
string StructType::canonicalName() const
{
return m_struct.annotation().canonicalName;
return *m_struct.annotation().canonicalName;
}
FunctionTypePointer StructType::constructorType() const
@ -2650,6 +2611,13 @@ vector<tuple<string, TypePointer>> StructType::makeStackItems() const
solAssert(false, "");
}
vector<Type const*> StructType::decomposition() const
{
vector<Type const*> res;
for (MemberList::Member const& member: members(nullptr))
res.push_back(member.type);
return res;
}
TypePointer EnumType::encodingType() const
{
@ -2685,12 +2653,12 @@ unsigned EnumType::storageBytes() const
string EnumType::toString(bool) const
{
return string("enum ") + m_enum.annotation().canonicalName;
return string("enum ") + *m_enum.annotation().canonicalName;
}
string EnumType::canonicalName() const
{
return m_enum.annotation().canonicalName;
return *m_enum.annotation().canonicalName;
}
size_t EnumType::numberOfMembers() const
@ -3163,7 +3131,7 @@ string FunctionType::toString(bool _short) const
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
if (auto const* contract = dynamic_cast<ContractDefinition const*>(functionDefinition->scope()))
name += contract->annotation().canonicalName + ".";
name += *contract->annotation().canonicalName + ".";
name += functionDefinition->name();
}
name += '(';
@ -3954,7 +3922,7 @@ bool ModuleType::operator==(Type const& _other) const
MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
{
MemberList::MemberMap symbols;
for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols)
for (auto const& symbolName: *m_sourceUnit.annotation().exportedSymbols)
for (Declaration const* symbol: symbolName.second)
symbols.emplace_back(symbolName.first, symbol->type(), symbol);
return symbols;
@ -3962,7 +3930,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
string ModuleType::toString(bool) const
{
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
return string("module \"") + *m_sourceUnit.annotation().path + string("\"");
}
string MagicType::richIdentifier() const

View File

@ -60,8 +60,6 @@ using BoolResult = util::Result<bool>;
namespace solidity::frontend
{
std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type);
inline rational makeRational(bigint const& _numerator, bigint const& _denominator)
{
solAssert(_denominator != 0, "division by zero");
@ -694,11 +692,37 @@ public:
TypeResult interfaceType(bool) const override { return this; }
};
/**
* Base class for types which can be thought of as several elements of other types put together.
* For example a struct is composed of its members, an array is composed of multiple copies of its
* base element and a mapping is composed of its value type elements (note that keys are not
* stored anywhere).
*/
class CompositeType: public Type
{
protected:
CompositeType() = default;
public:
/// @returns a list containing the type itself, elements of its decomposition,
/// elements of decomposition of these elements and so on, up to non-composite types.
/// Each type is included only once.
std::vector<Type const*> fullDecomposition() const;
protected:
/// @returns a list of types that together make up the data part of this type.
/// Contains all types that will have to be implicitly stored, whenever an object of this type is stored.
/// In particular, it returns the base type for arrays and array slices, the member types for structs,
/// the component types for tuples and the value type for mappings
/// (note that the key type of a mapping is *not* part of the list).
virtual std::vector<Type const*> decomposition() const = 0;
};
/**
* Base class used by types which are not value types and can be stored either in storage, memory
* or calldata. This is currently used by arrays and structs.
*/
class ReferenceType: public Type
class ReferenceType: public CompositeType
{
protected:
explicit ReferenceType(DataLocation _location): m_location(_location) {}
@ -829,6 +853,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override { return {m_baseType}; }
private:
/// String is interpreted as a subtype of Bytes.
enum class ArrayKind { Ordinary, Bytes, String };
@ -869,6 +895,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override { return {m_arrayType.baseType()}; }
private:
ArrayType const& m_arrayType;
};
@ -994,6 +1022,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override;
private:
StructDefinition const& m_struct;
// Caches for interfaceType(bool)
@ -1044,7 +1074,7 @@ private:
* Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders).
*/
class TupleType: public Type
class TupleType: public CompositeType
{
public:
explicit TupleType(std::vector<TypePointer> _types = {}): m_components(std::move(_types)) {}
@ -1067,6 +1097,16 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override
{
// Currently calling TupleType::decomposition() is not expected, because we cannot declare a variable of a tuple type.
// If that changes, before removing the solAssert, make sure the function does the right thing and is used properly.
// Note that different tuple members can have different data locations, so using decomposition() to check
// the tuple validity for a data location might require special care.
solUnimplemented("Tuple decomposition is not expected.");
return m_components;
}
private:
std::vector<TypePointer> const m_components;
};
@ -1349,7 +1389,7 @@ private:
* The type of a mapping, there is one distinct type per key/value type pair.
* Mappings always occupy their own storage slot, but do not actually use it.
*/
class MappingType: public Type
class MappingType: public CompositeType
{
public:
MappingType(Type const* _keyType, Type const* _valueType):
@ -1373,6 +1413,9 @@ public:
Type const* keyType() const { return m_keyType; }
Type const* valueType() const { return m_valueType; }
protected:
std::vector<Type const*> decomposition() const override { return {m_valueType}; }
private:
TypePointer m_keyType;
TypePointer m_valueType;

View File

@ -779,7 +779,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(function.parameterTypes().size() == 1, "");
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.front()->annotation().isPure)
if (!*arguments.front()->annotation().isPure)
{
arguments.front()->accept(*this);
utils().popStackElement(*arguments.front()->annotation().type);
@ -1078,7 +1078,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(function.kind() == FunctionType::Kind::Require, "");
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.at(1)->annotation().isPure)
if (!*arguments.at(1)->annotation().isPure)
{
arguments.at(1)->accept(*this);
utils().popStackElement(*arguments.at(1)->annotation().type);

View File

@ -142,6 +142,9 @@ public:
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
bool inlineAssemblySeen() const { return m_inlineAssemblySeen; }
void setInlineAssemblySeen() { m_inlineAssemblySeen = true; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
@ -159,6 +162,9 @@ private:
MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0;
/// Flag indicating whether any inline assembly block was seen.
bool m_inlineAssemblySeen = false;
/// Function definitions queued for code generation. They're the Solidity functions whose calls
/// were discovered by the IR generator during AST traversal.
/// Note that the queue gets filled in a lazy way - new definitions can be added while the

View File

@ -92,7 +92,7 @@ string IRGenerator::generate(
Whiskers t(R"(
object "<CreationObject>" {
code {
<memoryInit>
<memoryInitCreation>
<callValueCheck>
<?notLibrary>
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
@ -103,7 +103,7 @@ string IRGenerator::generate(
}
object "<RuntimeObject>" {
code {
<memoryInit>
<memoryInitRuntime>
<dispatch>
<runtimeFunctions>
}
@ -118,7 +118,6 @@ string IRGenerator::generate(
m_context.registerImmutableVariable(*var);
t("CreationObject", IRNames::creationObject(_contract));
t("memoryInit", memoryInit());
t("notLibrary", !_contract.isLibrary());
FunctionDefinition const* constructor = _contract.constructor();
@ -144,6 +143,10 @@ string IRGenerator::generate(
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
// This has to be called only after all other code generation for the creation object is complete.
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
resetContext(_contract);
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
@ -158,6 +161,10 @@ string IRGenerator::generate(
generateInternalDispatchFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
// This has to be called only after all other code generation for the runtime object is complete.
bool runtimeInvolvesAssembly = m_context.inlineAssemblySeen();
t("memoryInitRuntime", memoryInit(!runtimeInvolvesAssembly));
return t.render();
}
@ -651,16 +658,22 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
return t.render();
}
string IRGenerator::memoryInit()
string IRGenerator::memoryInit(bool _useMemoryGuard)
{
// This function should be called at the beginning of the EVM call frame
// and thus can assume all memory to be zero, including the contents of
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
return
Whiskers{"mstore(<memPtr>, <freeMemoryStart>)"}
Whiskers{
_useMemoryGuard ?
"mstore(<memPtr>, memoryguard(<freeMemoryStart>))" :
"mstore(<memPtr>, <freeMemoryStart>)"
}
("memPtr", to_string(CompilerUtils::freeMemoryPointer))
("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()))
.render();
(
"freeMemoryStart",
to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())
).render();
}
void IRGenerator::resetContext(ContractDefinition const& _contract)

View File

@ -100,7 +100,9 @@ private:
std::string dispatchRoutine(ContractDefinition const& _contract);
std::string memoryInit();
/// @a _useMemoryGuard If true, use a memory guard, allowing the optimiser
/// to perform memory optimizations.
std::string memoryInit(bool _useMemoryGuard);
void resetContext(ContractDefinition const& _contract);

View File

@ -1858,6 +1858,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
{
setLocation(_inlineAsm);
m_context.setInlineAssemblySeen();
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
yul::Statement modified = bodyCopier(_inlineAsm.operations());

View File

@ -792,12 +792,19 @@ void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
auto argument = _funCall.arguments().front();
unsigned argSize = argument->annotation().type->storageBytes();
unsigned castSize = _funCall.annotation().type->storageBytes();
if (argSize == castSize)
auto const& funCallCategory = _funCall.annotation().type->category();
// Allow casting number literals to address.
// TODO: remove the isNegative() check once the type checker disallows this
if (
auto const* numberType = dynamic_cast<RationalNumberType const*>(argument->annotation().type);
numberType && !numberType->isNegative() && (funCallCategory == Type::Category::Address)
)
defineExpr(_funCall, numberType->literalValue(nullptr));
else if (argSize == castSize)
defineExpr(_funCall, expr(*argument));
else
{
m_context.setUnknownValue(*m_context.expression(_funCall));
auto const& funCallCategory = _funCall.annotation().type->category();
// TODO: truncating and bytesX needs a different approach because of right padding.
if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address)
{

View File

@ -1097,7 +1097,7 @@ void CompilerStack::resolveImports()
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
string const& path = import->annotation().absolutePath;
string const& path = *import->annotation().absolutePath;
solAssert(m_sources.count(path), "");
import->annotation().sourceUnit = m_sources[path].ast.get();
toposort(&m_sources[path]);
@ -1295,9 +1295,9 @@ string CompilerStack::createMetadata(Contract const& _contract) const
/// All the source files (including self), which should be included in the metadata.
set<string> referencedSources;
referencedSources.insert(_contract.contract->sourceUnit().annotation().path);
referencedSources.insert(*_contract.contract->sourceUnit().annotation().path);
for (auto const sourceUnit: _contract.contract->sourceUnit().referencedSourceUnits(true))
referencedSources.insert(sourceUnit->annotation().path);
referencedSources.insert(*sourceUnit->annotation().path);
meta["sources"] = Json::objectValue;
for (auto const& s: m_sources)
@ -1363,7 +1363,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
meta["settings"]["evmVersion"] = m_evmVersion.name();
meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] =
_contract.contract->annotation().canonicalName;
*_contract.contract->annotation().canonicalName;
meta["settings"]["remappings"] = Json::arrayValue;
set<string> remappings;

View File

@ -79,6 +79,8 @@ public:
/// @throws BadSetOnceAccess when the stored value has not yet been set
T const* operator->() const { return std::addressof(**this); }
/// @return true if a value was assigned
bool set() const { return m_value.has_value(); }
private:
std::optional<T> m_value = std::nullopt;
};

View File

@ -105,6 +105,8 @@ add_library(yul
optimiser/ForLoopInitRewriter.h
optimiser/FullInliner.cpp
optimiser/FullInliner.h
optimiser/FunctionCallFinder.cpp
optimiser/FunctionCallFinder.h
optimiser/FunctionGrouper.cpp
optimiser/FunctionGrouper.h
optimiser/FunctionHoister.cpp
@ -150,6 +152,10 @@ add_library(yul
optimiser/SimplificationRules.h
optimiser/StackCompressor.cpp
optimiser/StackCompressor.h
optimiser/StackLimitEvader.cpp
optimiser/StackLimitEvader.h
optimiser/StackToMemoryMover.cpp
optimiser/StackToMemoryMover.h
optimiser/StructuralSimplifier.cpp
optimiser/StructuralSimplifier.h
optimiser/Substitution.cpp

View File

@ -33,7 +33,7 @@ using namespace solidity;
using namespace solidity::yul;
using namespace solidity::util;
map<YulString, int> CompilabilityChecker::run(
CompilabilityChecker::CompilabilityChecker(
Dialect const& _dialect,
Object const& _object,
bool _optimizeStackAllocation
@ -63,12 +63,11 @@ map<YulString, int> CompilabilityChecker::run(
);
transform(*_object.code);
std::map<YulString, int> functions;
for (StackTooDeepError const& error: transform.stackErrors())
functions[error.functionName] = max(error.depth, functions[error.functionName]);
return functions;
{
unreachableVariables[error.functionName].emplace(error.variable);
int& deficit = stackDeficit[error.functionName];
deficit = std::max(error.depth, deficit);
}
}
else
return {};
}

View File

@ -33,22 +33,20 @@ namespace solidity::yul
/**
* Component that checks whether all variables are reachable on the stack and
* returns a mapping from function name to the largest stack difference found
* in that function (no entry present if that function is compilable).
* provides a mapping from function name to the largest stack difference found
* in that function (no entry present if that function is compilable), as well
* as the set of unreachable variables for each function.
*
* This only works properly if the outermost block is compilable and
* functions are not nested. Otherwise, it might miss reporting some functions.
*
* Only checks the code of the object itself, does not descend into sub-objects.
*/
class CompilabilityChecker
struct CompilabilityChecker
{
public:
static std::map<YulString, int> run(
Dialect const& _dialect,
Object const& _object,
bool _optimizeStackAllocation
);
CompilabilityChecker(Dialect const& _dialect, Object const& _object, bool _optimizeStackAllocation);
std::map<YulString, std::set<YulString>> unreachableVariables;
std::map<YulString, int> stackDeficit;
};
}

View File

@ -72,6 +72,9 @@ struct Dialect: boost::noncopyable
virtual BuiltinFunction const* equalityFunction(YulString /* _type */) const { return nullptr; }
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
virtual BuiltinFunction const* memoryStoreFunction(YulString /* _type */) const { return nullptr; }
virtual BuiltinFunction const* memoryLoadFunction(YulString /* _type */) const { return nullptr; }
/// Check whether the given type is legal for the given literal value.
/// Should only be called if the type exists in the dialect at all.
virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const;

View File

@ -142,6 +142,23 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
Expression const& arg = _call.arguments.front();
_assembly.appendLinkerSymbol(std::get<Literal>(arg).value.str());
}));
builtins.emplace(createFunction(
"memoryguard",
1,
1,
SideEffects{},
{LiteralKind::Number},
[](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
function<void(Expression const&)> _visitExpression
) {
visitArguments(_assembly, _call, _visitExpression);
})
);
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {LiteralKind::String}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,

View File

@ -73,6 +73,8 @@ struct EVMDialect: public Dialect
BuiltinFunctionForEVM const* discardFunction(YulString /*_type*/) const override { return builtin("pop"_yulstring); }
BuiltinFunctionForEVM const* equalityFunction(YulString /*_type*/) const override { return builtin("eq"_yulstring); }
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); }
BuiltinFunctionForEVM const* memoryStoreFunction(YulString /*_type*/) const override { return builtin("mstore"_yulstring); }
BuiltinFunctionForEVM const* memoryLoadFunction(YulString /*_type*/) const override { return builtin("mload"_yulstring); }
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version);

View File

@ -1211,6 +1211,9 @@ function revert(x1, x2, x3, x4, y1, y2, y3, y4) {
function invalid() {
unreachable()
}
function memoryguard(x:i64) -> y1, y2, y3, y4 {
y4 := x
}
}
)"};

View File

@ -0,0 +1,39 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libyul/optimiser/FunctionCallFinder.h>
#include <libyul/AsmData.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
vector<FunctionCall*> FunctionCallFinder::run(Block& _block, YulString _functionName)
{
FunctionCallFinder functionCallFinder(_functionName);
functionCallFinder(_block);
return functionCallFinder.m_calls;
}
FunctionCallFinder::FunctionCallFinder(YulString _functionName): m_functionName(_functionName) {}
void FunctionCallFinder::operator()(FunctionCall& _functionCall)
{
ASTModifier::operator()(_functionCall);
if (_functionCall.functionName.name == m_functionName)
m_calls.emplace_back(&_functionCall);
}

View File

@ -0,0 +1,47 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* AST walker that finds all calls to a function of a given name.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <vector>
namespace solidity::yul
{
/**
* AST walker that finds all calls to a function of a given name.
*
* Prerequisite: Disambiguator
*/
class FunctionCallFinder: ASTModifier
{
public:
static std::vector<FunctionCall*> run(Block& _block, YulString _functionName);
private:
FunctionCallFinder(YulString _functionName);
using ASTModifier::operator();
void operator()(FunctionCall& _functionCall) override;
YulString m_functionName;
std::vector<FunctionCall*> m_calls;
};
}

View File

@ -168,7 +168,7 @@ bool StackCompressor::run(
bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code);
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
{
map<YulString, int> stackSurplus = CompilabilityChecker::run(_dialect, _object, _optimizeStackAllocation);
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
if (stackSurplus.empty())
return true;

View File

@ -0,0 +1,145 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libyul/optimiser/StackLimitEvader.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/FunctionCallFinder.h>
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/optimiser/StackToMemoryMover.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libyul/Exceptions.h>
#include <libyul/Object.h>
#include <libyul/Utilities.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/CommonData.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
namespace
{
/**
* Walks the call graph using a Depth-First-Search assigning memory slots to variables.
* - The leaves of the call graph will get the lowest slot, increasing towards the root.
* - ``slotsRequiredForFunction`` maps a function to the number of slots it requires (which is also the
* next available slot that can be used by another function that calls this function).
* - For each function starting from the root of the call graph:
* - Visit all children that are not already visited.
* - Determine the maximum value ``n`` of the values of ``slotsRequiredForFunction`` among the children.
* - If the function itself contains variables that need memory slots, but is contained in a cycle,
* abort the process as failure.
* - If not, assign each variable its slot starting from ``n`` (incrementing it).
* - Assign ``n`` to ``slotsRequiredForFunction`` of the function.
*/
struct MemoryOffsetAllocator
{
uint64_t run(YulString _function = YulString{})
{
if (slotsRequiredForFunction.count(_function))
return slotsRequiredForFunction[_function];
// Assign to zero early to guard against recursive calls.
slotsRequiredForFunction[_function] = 0;
uint64_t requiredSlots = 0;
if (callGraph.count(_function))
for (YulString child: callGraph.at(_function))
requiredSlots = std::max(run(child), requiredSlots);
if (unreachableVariables.count(_function))
{
yulAssert(!slotAllocations.count(_function), "");
auto& assignedSlots = slotAllocations[_function];
for (YulString variable: unreachableVariables.at(_function))
if (variable.empty())
{
// TODO: Too many function arguments or return parameters.
}
else
assignedSlots[variable] = requiredSlots++;
}
return slotsRequiredForFunction[_function] = requiredSlots;
}
map<YulString, set<YulString>> const& unreachableVariables;
map<YulString, set<YulString>> const& callGraph;
map<YulString, map<YulString, uint64_t>> slotAllocations{};
map<YulString, uint64_t> slotsRequiredForFunction{};
};
u256 literalArgumentValue(FunctionCall const& _call)
{
yulAssert(_call.arguments.size() == 1, "");
Literal const* literal = std::get_if<Literal>(&_call.arguments.front());
yulAssert(literal && literal->kind == LiteralKind::Number, "");
return valueOfLiteral(*literal);
}
}
void StackLimitEvader::run(
OptimiserStepContext& _context,
Object& _object,
map<YulString, set<YulString>> const& _unreachableVariables
)
{
yulAssert(_object.code, "");
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert(
evmDialect && evmDialect->providesObjectAccess(),
"StackLimitEvader can only be run on objects using the EVMDialect with object access."
);
vector<FunctionCall*> memoryGuardCalls = FunctionCallFinder::run(
*_object.code,
"memoryguard"_yulstring
);
// Do not optimise, if no ``memoryguard`` call is found.
if (memoryGuardCalls.empty())
return;
// Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort).
u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front());
for (FunctionCall const* memoryGuardCall: memoryGuardCalls)
if (reservedMemory != literalArgumentValue(*memoryGuardCall))
return;
CallGraph callGraph = CallGraphGenerator::callGraph(*_object.code);
// We cannot move variables in recursive functions to fixed memory offsets.
for (YulString function: callGraph.recursiveFunctions())
if (_unreachableVariables.count(function))
return;
MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls};
uint64_t requiredSlots = memoryOffsetAllocator.run();
StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code);
yulAssert(requiredSlots < std::numeric_limits<uint64_t>::max() / 32, "");
reservedMemory += 32 * requiredSlots;
for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring))
{
Literal* literal = std::get_if<Literal>(&memoryGuardCall->arguments.front());
yulAssert(literal && literal->kind == LiteralKind::Number, "");
literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)};
}
}

View File

@ -0,0 +1,66 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Optimisation stage that assigns memory offsets to variables that would become unreachable if
* assigned a stack slot as usual and replaces references and assignments to them by mload and mstore calls.
*/
#pragma once
#include <libyul/optimiser/OptimiserStep.h>
namespace solidity::yul
{
struct Object;
/**
* Optimisation stage that assigns memory offsets to variables that would become unreachable if
* assigned a stack slot as usual.
*
* Uses CompilabilityChecker to determine which variables in which functions are unreachable.
*
* Only variables outside of functions contained in cycles in the call graph are considered. Thereby it is possible
* to assign globally fixed memory offsets to the variable. If a variable in a function contained in a cycle in the
* call graph is reported as unreachable, the process is aborted.
*
* Offsets are assigned to the variables, s.t. on every path through the call graph each variable gets a unique offset
* in memory. However, distinct paths through the call graph can use the same memory offsets for their variables.
*
* The current arguments to the ``memoryguard`` calls are used as base memory offset and then replaced by the offset past
* the last memory offset used for a variable on any path through the call graph.
*
* Finally, the StackToMemoryMover is called to actually move the variables to their offsets in memory.
*
* Prerequisite: Disambiguator
*/
class StackLimitEvader
{
public:
/// @a _unreachableVariables can be determined by the CompilabilityChecker.
/// Can only be run on the EVM dialect with objects.
/// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls
/// with non-matching arguments are found, or if any of the @a _unreachableVariables
/// are contained in a recursive function.
static void run(
OptimiserStepContext& _context,
Object& _object,
std::map<YulString, std::set<YulString>> const& _unreachableVariables
);
};
}

View File

@ -0,0 +1,235 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libyul/optimiser/StackToMemoryMover.h>
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmData.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
namespace
{
vector<Statement> generateMemoryStore(
Dialect const& _dialect,
langutil::SourceLocation const& _loc,
YulString _mpos,
Expression _value
)
{
BuiltinFunction const* memoryStoreFunction = _dialect.memoryStoreFunction(_dialect.defaultType);
yulAssert(memoryStoreFunction, "");
vector<Statement> result;
result.emplace_back(ExpressionStatement{_loc, FunctionCall{
_loc,
Identifier{_loc, memoryStoreFunction->name},
{
Literal{_loc, LiteralKind::Number, _mpos, {}},
std::move(_value)
}
}});
return result;
}
}
void StackToMemoryMover::run(
OptimiserStepContext& _context,
u256 _reservedMemory,
map<YulString, map<YulString, uint64_t>> const& _memorySlots,
uint64_t _numRequiredSlots,
Block& _block
)
{
StackToMemoryMover stackToMemoryMover(_context, _reservedMemory, _memorySlots, _numRequiredSlots);
stackToMemoryMover(_block);
}
StackToMemoryMover::StackToMemoryMover(
OptimiserStepContext& _context,
u256 _reservedMemory,
map<YulString, map<YulString, uint64_t>> const& _memorySlots,
uint64_t _numRequiredSlots
):
m_context(_context),
m_reservedMemory(std::move(_reservedMemory)),
m_memorySlots(_memorySlots),
m_numRequiredSlots(_numRequiredSlots),
m_nameDispenser(_context.dispenser)
{
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert(
evmDialect && evmDialect->providesObjectAccess(),
"StackToMemoryMover can only be run on objects using the EVMDialect with object access."
);
if (m_memorySlots.count(YulString{}))
// If the global scope contains variables to be moved, start with those as if it were a function.
m_currentFunctionMemorySlots = &m_memorySlots.at(YulString{});
}
void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)
{
if (m_memorySlots.count(_functionDefinition.name))
{
map<YulString, uint64_t> const* saved = m_currentFunctionMemorySlots;
m_currentFunctionMemorySlots = &m_memorySlots.at(_functionDefinition.name);
for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables)
if (m_currentFunctionMemorySlots->count(param.name))
{
// TODO: we cannot handle function parameters yet.
m_currentFunctionMemorySlots = saved;
return;
}
ASTModifier::operator()(_functionDefinition);
m_currentFunctionMemorySlots = saved;
}
}
void StackToMemoryMover::operator()(Block& _block)
{
using OptionalStatements = std::optional<vector<Statement>>;
if (!m_currentFunctionMemorySlots)
{
ASTModifier::operator()(_block);
return;
}
auto containsVariableNeedingEscalation = [&](auto const& _variables) {
return util::contains_if(_variables, [&](auto const& var) {
return m_currentFunctionMemorySlots->count(var.name);
});
};
auto rewriteAssignmentOrVariableDeclaration = [&](
langutil::SourceLocation const& _loc,
auto const& _variables,
std::unique_ptr<Expression> _value
) -> std::vector<Statement> {
if (_variables.size() == 1)
return generateMemoryStore(
m_context.dialect,
_loc,
memoryOffset(_variables.front().name),
_value ? *std::move(_value) : Literal{_loc, LiteralKind::Number, "0"_yulstring, {}}
);
VariableDeclaration tempDecl{_loc, {}, std::move(_value)};
vector<Statement> memoryAssignments;
vector<Statement> variableAssignments;
for (auto& var: _variables)
{
YulString tempVarName = m_nameDispenser.newName(var.name);
tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}});
if (m_currentFunctionMemorySlots->count(var.name))
memoryAssignments += generateMemoryStore(
m_context.dialect,
_loc,
memoryOffset(var.name),
Identifier{_loc, tempVarName}
);
else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, Identifier>)
variableAssignments.emplace_back(Assignment{
_loc, { Identifier{var.location, var.name} },
make_unique<Expression>(Identifier{_loc, tempVarName})
});
else
variableAssignments.emplace_back(VariableDeclaration{
_loc, {std::move(var)},
make_unique<Expression>(Identifier{_loc, tempVarName})
});
}
std::vector<Statement> result;
result.emplace_back(std::move(tempDecl));
std::reverse(memoryAssignments.begin(), memoryAssignments.end());
result += std::move(memoryAssignments);
std::reverse(variableAssignments.begin(), variableAssignments.end());
result += std::move(variableAssignments);
return result;
};
util::iterateReplacing(
_block.statements,
[&](Statement& _statement)
{
auto defaultVisit = [&]() { ASTModifier::visit(_statement); return OptionalStatements{}; };
return std::visit(util::GenericVisitor{
[&](Assignment& _assignment) -> OptionalStatements
{
if (!containsVariableNeedingEscalation(_assignment.variableNames))
return defaultVisit();
visit(*_assignment.value);
return {rewriteAssignmentOrVariableDeclaration(
_assignment.location,
_assignment.variableNames,
std::move(_assignment.value)
)};
},
[&](VariableDeclaration& _varDecl) -> OptionalStatements
{
if (!containsVariableNeedingEscalation(_varDecl.variables))
return defaultVisit();
if (_varDecl.value)
visit(*_varDecl.value);
return {rewriteAssignmentOrVariableDeclaration(
_varDecl.location,
_varDecl.variables,
std::move(_varDecl.value)
)};
},
[&](auto&) { return defaultVisit(); }
}, _statement);
});
}
void StackToMemoryMover::visit(Expression& _expression)
{
if (
Identifier* identifier = std::get_if<Identifier>(&_expression);
identifier && m_currentFunctionMemorySlots && m_currentFunctionMemorySlots->count(identifier->name)
)
{
BuiltinFunction const* memoryLoadFunction = m_context.dialect.memoryLoadFunction(m_context.dialect.defaultType);
yulAssert(memoryLoadFunction, "");
langutil::SourceLocation loc = identifier->location;
_expression = FunctionCall{
loc,
Identifier{loc, memoryLoadFunction->name}, {
Literal{
loc,
LiteralKind::Number,
memoryOffset(identifier->name),
{}
}
}
};
}
else
ASTModifier::visit(_expression);
}
YulString StackToMemoryMover::memoryOffset(YulString _variable)
{
yulAssert(m_currentFunctionMemorySlots, "");
uint64_t slot = m_currentFunctionMemorySlots->at(_variable);
yulAssert(slot < m_numRequiredSlots, "");
return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))};
}

View File

@ -0,0 +1,121 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Optimisation stage that moves Yul variables from stack to memory.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libsolutil/Common.h>
namespace solidity::yul
{
/**
* Optimisation stage that moves Yul variables from stack to memory.
* It takes a map from functions names and variable names to memory offsets.
* It then transforms the AST as follows:
*
* Single variable declarations are replaced by mstore's as follows:
* If a is in the map, replace
* let a
* by
* mstore(<memory offset for a>, 0)
* respectively, replace
* let a := expr
* by
* mstore(<memory offset for a>, expr)
*
* In a multi-variable declaration, variables to be moved are replaced by fresh variables and then moved to memory:
* If b and d are in the map, replace
* let a, b, c, d := f()
* by
* let _1, _2, _3, _4 := f()
* mstore(<memory offset for d>, _4)
* mstore(<memory offset for b>, _2)
* let c := _3
* let a := _1
*
* Assignments to single variables are replaced by mstore's:
* If a is in the map, replace
* a := expr
* by
* mstore(<memory offset for a>, expr)
*
* Assignments to multiple variables are split up similarly to multi-variable declarations:
* If b and d are in the map, replace
* a, b, c, d := f()
* by
* let _1, _2, _3, _4 := f()
* mstore(<memory offset for d>, _4)
* mstore(<memory offset for b>, _2)
* c := _3
* a := _1
*
* Replace all references to a variable ``a`` in the map by ``mload(<memory offset for a>)``.
*
* If a visited function has arguments or return parameters that are contained in the map,
* the entire function is skipped (no local variables in the function will be moved at all).
*
* Prerequisite: Disambiguator, ForLoopInitRewriter, FunctionHoister.
*/
class StackToMemoryMover: ASTModifier
{
public:
/**
* Runs the stack to memory mover.
* @param _reservedMemory Is the amount of previously reserved memory,
* i.e. the lowest memory offset to which variables can be moved.
* @param _memorySlots A map from variables to a slot in memory. Based on the slot a unique offset in the memory range
* between _reservedMemory and _reservedMemory + 32 * _numRequiredSlots is calculated for each
* variable.
* @param _numRequiredSlots The number of slots required in total. The maximum value that may occur in @a _memorySlots.
*/
static void run(
OptimiserStepContext& _context,
u256 _reservedMemory,
std::map<YulString, std::map<YulString, uint64_t>> const& _memorySlots,
uint64_t _numRequiredSlots,
Block& _block
);
using ASTModifier::operator();
void operator()(FunctionDefinition& _functionDefinition) override;
void operator()(Block& _block) override;
void visit(Expression& _expression) override;
private:
StackToMemoryMover(
OptimiserStepContext& _context,
u256 _reservedMemory,
std::map<YulString, std::map<YulString, uint64_t>> const& _memorySlots,
uint64_t _numRequiredSlots
);
/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal.
YulString memoryOffset(YulString _variable);
OptimiserStepContext& m_context;
u256 m_reservedMemory;
std::map<YulString, std::map<YulString, uint64_t>> const& m_memorySlots;
uint64_t m_numRequiredSlots = 0;
NameDispenser& m_nameDispenser;
std::map<YulString, uint64_t> const* m_currentFunctionMemorySlots = nullptr;
};
}

View File

@ -51,6 +51,7 @@
#include <libyul/optimiser/SSAReverser.h>
#include <libyul/optimiser/SSATransform.h>
#include <libyul/optimiser/StackCompressor.h>
#include <libyul/optimiser/StackLimitEvader.h>
#include <libyul/optimiser/StructuralSimplifier.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/optimiser/RedundantAssignEliminator.h>
@ -73,6 +74,7 @@
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <libyul/CompilabilityChecker.h>
using namespace std;
using namespace solidity;
@ -124,6 +126,12 @@ void OptimiserSuite::run(
{
yulAssert(_meter, "");
ConstantOptimiser{*dialect, *_meter}(ast);
if (dialect->providesObjectAccess())
StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{
_dialect,
_object,
_optimizeStackAllocation
}.unreachableVariables);
}
else if (dynamic_cast<WasmDialect const*>(&_dialect))
{

View File

@ -1,3 +1,5 @@
$ErrorActionPreference = "Stop"
# Needed for Invoke-WebRequest to work via CI.
$progressPreference = "silentlyContinue"
@ -12,4 +14,5 @@ tar -xf boost.zip
cd boost_1_74_0
.\bootstrap.bat
.\b2 -j4 -d0 link=static runtime-link=static variant=release threading=multi address-model=64 --with-filesystem --with-system --with-program_options --with-test --prefix="$PSScriptRoot\..\deps\boost" install
if ( -not $? ) { throw "Error building boost." }
cd ..

View File

@ -0,0 +1,9 @@
$ErrorActionPreference = "Stop"
# Needed for Invoke-WebRequest to work via CI.
$progressPreference = "silentlyContinue"
Invoke-WebRequest -URI "https://github.com/ethereum/evmone/releases/download/v0.5.0/evmone-0.5.0-windows-amd64.zip" -OutFile "evmone.zip"
tar -xf evmone.zip "bin/evmone.dll"
mkdir deps
mv bin/evmone.dll deps

38
scripts/release.bat Normal file
View File

@ -0,0 +1,38 @@
@ECHO OFF
REM ---------------------------------------------------------------------------
REM Batch file for implementing release flow for solidity for Windows.
REM
REM The documentation for solidity is hosted at:
REM
REM https://solidity.readthedocs.org
REM
REM ---------------------------------------------------------------------------
REM This file is part of solidity.
REM
REM solidity is free software: you can redistribute it and/or modify
REM it under the terms of the GNU General Public License as published by
REM the Free Software Foundation, either version 3 of the License, or
REM (at your option) any later version.
REM
REM solidity is distributed in the hope that it will be useful,
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
REM GNU General Public License for more details.
REM
REM You should have received a copy of the GNU General Public License
REM along with solidity. If not, see <http://www.gnu.org/licenses/>
REM
REM Copyright (c) 2016 solidity contributors.
REM ---------------------------------------------------------------------------
set CONFIGURATION=%1
set VERSION=%2
set "DLLS=MSVC_DLLS_NOT_FOUND"
FOR /d %%d IN ("C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\*"
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\*") DO set "DLLS=%%d\x86\Microsoft.VC141.CRT\msvc*.dll"
7z a solidity-windows.zip ^
.\build\solc\%CONFIGURATION%\solc.exe .\build\test\%CONFIGURATION%\soltest.exe ^
"%DLLS%"

View File

@ -9,7 +9,7 @@ Optimized IR:
object "C_6" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("C_6_deployed")
codecopy(0, dataoffset("C_6_deployed"), _1)
@ -19,7 +19,7 @@ object "C_6" {
object "C_6_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
revert(0, 0)
}
}
@ -37,7 +37,7 @@ Optimized IR:
object "D_9" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("D_9_deployed")
codecopy(0, dataoffset("D_9_deployed"), _1)
@ -47,7 +47,7 @@ object "D_9" {
object "D_9_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
revert(0, 0)
}
}

View File

@ -9,7 +9,7 @@ Optimized IR:
object "C_2" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
@ -19,7 +19,7 @@ object "C_2" {
object "C_2_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
revert(0, 0)
}
}
@ -37,7 +37,7 @@ Optimized IR:
object "D_13" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("D_13_deployed")
codecopy(0, dataoffset("D_13_deployed"), _1)
@ -47,20 +47,21 @@ object "D_13" {
object "D_13_deployed" {
code {
{
mstore(64, 128)
let _1 := memoryguard(0x80)
mstore(64, _1)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
if eq(0x26121ff0, shr(224, calldataload(_1)))
let _2 := 0
if eq(0x26121ff0, shr(224, calldataload(_2)))
{
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
let _2 := datasize("C_2")
let _3 := add(128, _2)
if or(gt(_3, 0xffffffffffffffff), lt(_3, 128)) { revert(_1, _1) }
datacopy(128, dataoffset("C_2"), _2)
pop(create(_1, 128, _2))
return(allocateMemory(_1), _1)
if callvalue() { revert(_2, _2) }
if slt(add(calldatasize(), not(3)), _2) { revert(_2, _2) }
let _3 := datasize("C_2")
let _4 := add(_1, _3)
if or(gt(_4, 0xffffffffffffffff), lt(_4, _1)) { revert(_2, _2) }
datacopy(_1, dataoffset("C_2"), _3)
pop(create(_2, _1, sub(_4, _1)))
return(allocateMemory(_2), _2)
}
}
revert(0, 0)
@ -76,7 +77,7 @@ object "D_13" {
object "C_2" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
@ -86,7 +87,7 @@ object "D_13" {
object "C_2_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
revert(0, 0)
}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0.0;
contract D {
constructor() { assembly {}}
function f() public pure {}
}

View File

@ -0,0 +1,40 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_11" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("D_11_deployed")
codecopy(0, dataoffset("D_11_deployed"), _1)
return(0, _1)
}
}
object "D_11_deployed" {
code {
{
let _1 := memoryguard(0x80)
mstore(64, _1)
if iszero(lt(calldatasize(), 4))
{
let _2 := 0
if eq(0x26121ff0, shr(224, calldataload(_2)))
{
if callvalue() { revert(_2, _2) }
if slt(add(calldatasize(), not(3)), _2) { revert(_2, _2) }
if gt(_1, 0xffffffffffffffff) { revert(_2, _2) }
mstore(64, _1)
return(_1, _2)
}
}
revert(0, 0)
}
}
}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0.0;
contract D {
function f() public pure {
assembly {}
}
}

View File

@ -0,0 +1,38 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_7" {
code {
{
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("D_7_deployed")
codecopy(0, dataoffset("D_7_deployed"), _1)
return(0, _1)
}
}
object "D_7_deployed" {
code {
{
mstore(64, 128)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
if eq(0x26121ff0, shr(224, calldataload(_1)))
{
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
mstore(64, 128)
return(128, _1)
}
}
revert(0, 0)
}
}
}
}

View File

@ -9,7 +9,7 @@ Optimized IR:
object "C_56" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("C_56_deployed")
codecopy(0, dataoffset("C_56_deployed"), _1)
@ -19,7 +19,7 @@ object "C_56" {
object "C_56_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if iszero(lt(calldatasize(), 4))
{
let _1 := 0

View File

@ -9,7 +9,7 @@ Optimized IR:
object "Arraysum_33" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
let _1 := datasize("Arraysum_33_deployed")
codecopy(0, dataoffset("Arraysum_33_deployed"), _1)
@ -19,7 +19,7 @@ object "Arraysum_33" {
object "Arraysum_33_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if iszero(lt(calldatasize(), 4))
{
let _1 := 0

View File

@ -7,7 +7,7 @@
object \"C_6\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_6()
codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))
@ -17,7 +17,7 @@ object \"C_6\" {
}
object \"C_6_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{
let selector := shift_right_224_unsigned(calldataload(0))

View File

@ -8,7 +8,7 @@
object \"C_6\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_6()
@ -24,7 +24,7 @@ object \"C_6\" {
}
object \"C_6_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -9,7 +9,7 @@ Optimized IR:
object "C_6" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if callvalue() { revert(0, 0) }
codecopy(0, dataoffset("C_6_deployed"), datasize("C_6_deployed"))
return(0, datasize("C_6_deployed"))
@ -18,7 +18,7 @@ object "C_6" {
object "C_6_deployed" {
code {
{
mstore(64, 128)
mstore(64, memoryguard(0x80))
if iszero(lt(calldatasize(), 4))
{
let selector := shift_right_224_unsigned(calldataload(0))

View File

@ -8,7 +8,7 @@
object \"C_10\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_10()
@ -24,7 +24,7 @@ object \"C_10\" {
}
object \"C_10_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -8,7 +8,7 @@
object \"C_10\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_10()
@ -24,7 +24,7 @@ object \"C_10\" {
}
object \"C_10_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -8,7 +8,7 @@
object \"C_10\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_10()
@ -24,7 +24,7 @@ object \"C_10\" {
}
object \"C_10_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -8,7 +8,7 @@
object \"C_10\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_10()
@ -24,7 +24,7 @@ object \"C_10\" {
}
object \"C_10_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -8,7 +8,7 @@
object \"C_10\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if callvalue() { revert(0, 0) }
constructor_C_10()
@ -24,7 +24,7 @@ object \"C_10\" {
}
object \"C_10_deployed\" {
code {
mstore(64, 128)
mstore(64, memoryguard(128))
if iszero(lt(calldatasize(), 4))
{

View File

@ -16,7 +16,6 @@
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 8,
"linearizedBaseContracts":
[

View File

@ -25,7 +25,6 @@
null
],
"contractKind": "contract",
"fullyImplemented": true,
"linearizedBaseContracts":
[
8

View File

@ -16,7 +16,6 @@
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 6,
"linearizedBaseContracts":
[

View File

@ -25,7 +25,6 @@
null
],
"contractKind": "contract",
"fullyImplemented": true,
"linearizedBaseContracts":
[
6

View File

@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(function_no_implementation)
std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
ContractDefinition* contract = dynamic_cast<ContractDefinition*>(nodes[1].get());
BOOST_REQUIRE(contract);
BOOST_CHECK(!contract->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(!contract->annotation().unimplementedDeclarations->empty());
BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented());
}
@ -68,10 +68,10 @@ BOOST_AUTO_TEST_CASE(abstract_contract)
ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get());
ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get());
BOOST_REQUIRE(base);
BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(!base->annotation().unimplementedDeclarations->empty());
BOOST_CHECK(!base->definedFunctions()[0]->isImplemented());
BOOST_REQUIRE(derived);
BOOST_CHECK(derived->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(derived->annotation().unimplementedDeclarations->empty());
BOOST_CHECK(derived->definedFunctions()[0]->isImplemented());
}
@ -87,9 +87,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload)
ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get());
ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get());
BOOST_REQUIRE(base);
BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(!base->annotation().unimplementedDeclarations->empty());
BOOST_REQUIRE(derived);
BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(!derived->annotation().unimplementedDeclarations->empty());
}
BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor)
@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor)
BOOST_CHECK_EQUAL(nodes.size(), 3);
ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get());
BOOST_REQUIRE(derived);
BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty());
BOOST_CHECK(!derived->annotation().unimplementedDeclarations->empty());
}
BOOST_AUTO_TEST_CASE(function_canonical_signature)

View File

@ -0,0 +1,53 @@
contract C {
uint256[1024] s;
function f() public returns (uint256 x) {
x = 42;
uint256 x0 = s[0];
uint256 x1 = s[1];
uint256 x2 = s[2];
uint256 x3 = s[3];
uint256 x4 = s[4];
uint256 x5 = s[5];
uint256 x6 = s[6];
uint256 x7 = s[7];
uint256 x8 = s[8];
uint256 x9 = s[9];
uint256 x10 = s[10];
uint256 x11 = s[11];
uint256 x12 = s[12];
uint256 x13 = s[13];
uint256 x14 = s[14];
uint256 x15 = s[15];
uint256 x16 = s[16];
uint256 x17 = s[17];
uint256 x18 = s[18];
s[1000] = x0 + 2;
s[118] = x18;
s[117] = x17;
s[116] = x16;
s[115] = x15;
s[114] = x14;
s[113] = x13;
s[112] = x12;
s[111] = x11;
s[110] = x10;
s[109] = x9;
s[108] = x8;
s[107] = x7;
s[106] = x6;
s[105] = x5;
s[104] = x4;
s[103] = x3;
s[102] = x2;
s[101] = x1;
s[100] = x0;
}
function test() public view returns(uint256) {
return s[1000];
}
}
// ====
// compileViaYul: true
// ----
// f() -> 0x2a
// test() -> 2

View File

@ -0,0 +1,57 @@
contract C {
uint256[1024] s;
function g() public returns (uint256) {
// try to prevent inlining
return f() + f() + f() + f() + f();
}
function f() public returns (uint256 x) {
x = 42;
uint256 x0 = s[0];
uint256 x1 = s[1];
uint256 x2 = s[2];
uint256 x3 = s[3];
uint256 x4 = s[4];
uint256 x5 = s[5];
uint256 x6 = s[6];
uint256 x7 = s[7];
uint256 x8 = s[8];
uint256 x9 = s[9];
uint256 x10 = s[10];
uint256 x11 = s[11];
uint256 x12 = s[12];
uint256 x13 = s[13];
uint256 x14 = s[14];
uint256 x15 = s[15];
uint256 x16 = s[16];
uint256 x17 = s[17];
uint256 x18 = s[18];
s[1000] = x0 + 2;
s[118] = x18;
s[117] = x17;
s[116] = x16;
s[115] = x15;
s[114] = x14;
s[113] = x13;
s[112] = x12;
s[111] = x11;
s[110] = x10;
s[109] = x9;
s[108] = x8;
s[107] = x7;
s[106] = x6;
s[105] = x5;
s[104] = x4;
s[103] = x3;
s[102] = x2;
s[101] = x1;
s[100] = x0;
}
function test() public view returns(uint256) {
return s[1000];
}
}
// ====
// compileViaYul: true
// ----
// f() -> 0x2a
// test() -> 2

View File

@ -15,6 +15,3 @@ contract C
}
}
// ----
// Warning 5084: (208-218): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (208-218): Type conversion is not yet fully supported and might yield false positives.

View File

@ -20,8 +20,3 @@ contract C
}
}
// ----
// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (186-196): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives.

View File

@ -20,8 +20,3 @@ contract C
}
}
// ----
// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (189-199): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives.

View File

@ -25,7 +25,3 @@ contract C
}
// ----
// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (189-199): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives.

View File

@ -16,6 +16,3 @@ contract C
}
}
// ----
// Warning 5084: (219-229): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (134-144): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (219-229): Type conversion is not yet fully supported and might yield false positives.

View File

@ -19,6 +19,3 @@ contract C
}
}
// ----
// Warning 5084: (249-259): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (118-128): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (249-259): Type conversion is not yet fully supported and might yield false positives.

View File

@ -20,6 +20,3 @@ contract C
}
}
// ----
// Warning 5084: (247-257): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (162-172): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (247-257): Type conversion is not yet fully supported and might yield false positives.

View File

@ -35,4 +35,3 @@ contract C {
}
// ----
// Warning 6328: (528-565): Assertion violation happens here.
// Warning 5084: (544-554): Type conversion is not yet fully supported and might yield false positives.

View File

@ -44,4 +44,3 @@ contract C {
// ----
// Warning 6328: (452-466): Assertion violation happens here.
// Warning 6328: (470-496): Assertion violation happens here.
// Warning 5084: (92-102): Type conversion is not yet fully supported and might yield false positives.

View File

@ -36,4 +36,3 @@ contract C {
// ----
// Warning 6328: (381-395): Assertion violation happens here.
// Warning 6328: (399-425): Assertion violation happens here.
// Warning 5084: (116-126): Type conversion is not yet fully supported and might yield false positives.

View File

@ -40,4 +40,3 @@ contract C {
// ----
// Warning 6328: (435-461): Assertion violation happens here.
// Warning 6328: (594-631): Assertion violation happens here.
// Warning 5084: (610-620): Type conversion is not yet fully supported and might yield false positives.

View File

@ -13,4 +13,3 @@ contract C {
}
}
// ----
// Warning 5084: (198-208): Type conversion is not yet fully supported and might yield false positives.

View File

@ -13,4 +13,3 @@ contract C {
}
}
// ----
// Warning 5084: (107-117): Type conversion is not yet fully supported and might yield false positives.

View File

@ -17,5 +17,3 @@ contract C
}
}
// ----
// Warning 5084: (205-215): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (205-215): Type conversion is not yet fully supported and might yield false positives.

View File

@ -10,4 +10,3 @@ contract C
}
}
// ----
// Warning 5084: (98-108): Type conversion is not yet fully supported and might yield false positives.

View File

@ -0,0 +1,25 @@
pragma experimental SMTChecker;
contract C {
address x; // We know that this is "zero initialised".
function f() public view {
address a = address(0);
assert(x == address(0));
assert(x == a);
}
function g() public pure {
address a = address(0);
address b = address(1);
address c = address(0);
address d = a;
address e = address(0x12345678);
assert(c == d);
assert(a == c);
assert(e == address(305419896));
// This is untrue.
assert(a == b);
}
}
// ----
// Warning 6328: (487-501): Assertion violation happens here.

View File

@ -8,5 +8,3 @@ contract C
}
}
// ----
// Warning 5084: (98-108): Type conversion is not yet fully supported and might yield false positives.
// Warning 5084: (125-135): Type conversion is not yet fully supported and might yield false positives.

View File

@ -11,4 +11,3 @@ contract C {
}
}
// ----
// Warning 5084: (142-152): Type conversion is not yet fully supported and might yield false positives.

View File

@ -0,0 +1,13 @@
contract test {
function f(uint a, bool b, bytes memory c, uint d, bool e) public returns (uint r) {
if (b && !e)
r = a + d;
else
r = c.length;
}
function g() public returns (uint r) {
r = f({c: "abc", x: 1, e: 2, a: 11, b: 12});
}
}
// ----
// TypeError 4974: (249-288): Named argument "x" does not match function declaration.

View File

@ -13,6 +13,8 @@ contract b {
}
// ----
// Warning 7325: (66-67): Type struct 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.
// Warning 7325: (66-67): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] 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 7325: (111-112): Type struct 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.
// Warning 7325: (111-112): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] 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 7325: (152-169): Type function ()[984770902183611232881] 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 2072: (152-180): Unused local variable.

View File

@ -56,13 +56,19 @@ contract C {
}
// ----
// Warning 7325: (106-108): Type struct C.S0 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 7325: (106-108): Type struct C.P[101] 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 7325: (171-173): Type struct C.S1 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 7325: (171-173): Type struct C.P[102] 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 7325: (341-343): Type struct C.P[103] 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 7325: (341-343): Type struct C.P[104] 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 7325: (505-507): Type uint256[100000000000000000002] 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 7325: (505-507): Type uint256[100000000000000000004] 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 7325: (505-507): Type struct C.Q0 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 7325: (505-507): Type uint256[1][][100000000000000000001] 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 7325: (505-507): Type uint256[][100000000000000000003] 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 7325: (505-507): Type uint256[100000000000000000004] 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 7325: (505-507): Type uint256[100000000000000000002] 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 7325: (576-578): Type struct C.Q1 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 7325: (576-578): Type uint256[1][][100000000000000000005] 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 7325: (647-649): Type uint256[100000000000000000006] 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 7325: (715-717): Type struct C.Q3 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 7325: (715-717): Type uint256[][100000000000000000007] 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 7325: (783-785): Type uint256[100000000000000000008] 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

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

View File

@ -5,4 +5,4 @@ contract C {
uint[2**255] b;
}
// ----
// TypeError 7676: (60-114): Contract too large for storage.
// TypeError 7676: (60-114): Contract requires too much storage.

View File

@ -7,4 +7,4 @@ contract D is C {
uint[2**255] b;
}
// ----
// TypeError 7676: (95-134): Contract too large for storage.
// TypeError 7676: (95-134): Contract requires too much storage.

View File

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

View File

@ -4,3 +4,4 @@ contract C {
}
// ----
// Warning 7325: (64-65): Type struct C.S 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 7325: (64-65): Type uint256[57896044618658097711785492504343953926634992332820282019728792003956564819968] 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

@ -1,11 +0,0 @@
contract test {
function a(uint a, uint b) public returns (uint r) {
r = a + b;
}
function b() public returns (uint r) {
r = a({a: 1, c: 2});
}
}
// ----
// Warning 2519: (31-37): This declaration shadows an existing declaration.
// TypeError 4974: (153-168): Named argument "c" does not match function declaration.

View File

@ -0,0 +1,7 @@
contract C {
function f() public pure {
abi.encodePacked([new uint[](5), new uint[](7)]);
}
}
// ----
// TypeError 9578: (69-99): Type not supported in packed mode.

Some files were not shown because too many files have changed in this diff Show More