mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9862 from ethereum/develop
Merge develop into breaking
This commit is contained in:
commit
0c6dc1dce4
@ -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." }
|
||||
|
@ -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
12
.circleci/soltest.ps1
Executable 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." }
|
@ -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``.
|
||||
|
@ -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``.
|
||||
|
||||
|
@ -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::
|
||||
|
20
docs/yul.rst
20
docs/yul.rst
@ -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:
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -158,17 +158,16 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
|
||||
}
|
||||
|
||||
if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage)
|
||||
if (auto varType = dynamic_cast<CompositeType const*>(_variable.annotation().type))
|
||||
for (Type const* type: varType->fullDecomposition())
|
||||
if (type->storageSizeUpperBound() >= (bigint(1) << 64))
|
||||
{
|
||||
TypePointer varType = _variable.annotation().type;
|
||||
for (Type const* subtype: frontend::oversizedSubtypes(*varType))
|
||||
{
|
||||
string message = "Type " + subtype->toString(true) +
|
||||
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])
|
||||
))
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
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())
|
||||
{
|
||||
Json::Value exportedSymbols = Json::objectValue;
|
||||
for (auto const& sym: _node.annotation().exportedSymbols)
|
||||
for (auto const& sym: *_node.annotation().exportedSymbols)
|
||||
{
|
||||
exportedSymbols[sym.first] = Json::arrayValue;
|
||||
for (Declaration const* overload: sym.second)
|
||||
exportedSymbols[sym.first].append(nodeId(*overload));
|
||||
}
|
||||
setJsonNode(
|
||||
_node,
|
||||
"SourceUnit",
|
||||
{
|
||||
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()))
|
||||
}
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
)"};
|
||||
|
||||
|
39
libyul/optimiser/FunctionCallFinder.cpp
Normal file
39
libyul/optimiser/FunctionCallFinder.cpp
Normal 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);
|
||||
}
|
47
libyul/optimiser/FunctionCallFinder.h
Normal file
47
libyul/optimiser/FunctionCallFinder.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
145
libyul/optimiser/StackLimitEvader.cpp
Normal file
145
libyul/optimiser/StackLimitEvader.cpp
Normal 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)};
|
||||
}
|
||||
}
|
66
libyul/optimiser/StackLimitEvader.h
Normal file
66
libyul/optimiser/StackLimitEvader.h
Normal 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
|
||||
);
|
||||
};
|
||||
|
||||
}
|
235
libyul/optimiser/StackToMemoryMover.cpp
Normal file
235
libyul/optimiser/StackToMemoryMover.cpp
Normal 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))};
|
||||
}
|
||||
|
121
libyul/optimiser/StackToMemoryMover.h
Normal file
121
libyul/optimiser/StackToMemoryMover.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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))
|
||||
{
|
||||
|
@ -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 ..
|
||||
|
9
scripts/install_evmone.ps1
Normal file
9
scripts/install_evmone.ps1
Normal 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
38
scripts/release.bat
Normal 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%"
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
--ir-optimized --optimize
|
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.0.0;
|
||||
|
||||
contract D {
|
||||
constructor() { assembly {}}
|
||||
function f() public pure {}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
--ir-optimized --optimize
|
@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.0.0;
|
||||
|
||||
contract D {
|
||||
function f() public pure {
|
||||
assembly {}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -16,7 +16,6 @@
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"id": 8,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
|
@ -25,7 +25,6 @@
|
||||
null
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
8
|
||||
|
@ -16,7 +16,6 @@
|
||||
"baseContracts": [],
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"id": 6,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
|
@ -25,7 +25,6 @@
|
||||
null
|
||||
],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
6
|
||||
|
@ -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)
|
||||
|
@ -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
|
@ -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
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -13,4 +13,3 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 5084: (198-208): Type conversion is not yet fully supported and might yield false positives.
|
||||
|
@ -13,4 +13,3 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 5084: (107-117): Type conversion is not yet fully supported and might yield false positives.
|
||||
|
@ -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.
|
||||
|
@ -10,4 +10,3 @@ contract C
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 5084: (98-108): Type conversion is not yet fully supported and might yield false positives.
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -11,4 +11,3 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 5084: (142-152): Type conversion is not yet fully supported and might yield false positives.
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
@ -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
Loading…
Reference in New Issue
Block a user