Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2020-07-06 15:25:25 +02:00
commit ab68406006
118 changed files with 1713 additions and 380 deletions

View File

@ -323,7 +323,7 @@ jobs:
- checkout - checkout
- run: - run:
name: Check for error codes name: Check for error codes
command: ./scripts/fix_error_ids.py --check-only command: ./scripts/error_codes.py --check
chk_pylint: chk_pylint:
docker: docker:

View File

@ -27,6 +27,8 @@ Bugfixes:
Language Features: Language Features:
* General: Add unit denomination ``gwei`` * General: Add unit denomination ``gwei``
* Yul: Support ``linkersymbol`` builtin in standalone assembly mode.
* Yul: Support using string literals exceeding 32 bytes as literal arguments for builtins.
Compiler Features: Compiler Features:
@ -34,6 +36,9 @@ Compiler Features:
* NatSpec: Inherit tags from unique base if derived function does not provide any. * NatSpec: Inherit tags from unique base if derived function does not provide any.
* Commandline Interface: Prevent some incompatible commandline options from being used together. * Commandline Interface: Prevent some incompatible commandline options from being used together.
* NatSpec: Support NatSpec comments on events. * NatSpec: Support NatSpec comments on events.
* Yul Optimizer: Store knowledge about storage / memory after ``a := sload(x)`` / ``a := mload(x)``.
* SMTChecker: Support external calls to unknown code.
Bugfixes: Bugfixes:
* NatSpec: Do not consider ``////`` and ``/***`` as NatSpec comments. * NatSpec: Do not consider ``////`` and ``/***`` as NatSpec comments.

View File

@ -30,7 +30,7 @@ The design of Yul tries to achieve several goals:
In order to achieve the first and second goal, Yul provides high-level constructs In order to achieve the first and second goal, Yul provides high-level constructs
like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should
be sufficient for adequately representing the control flow for assembly programs. be sufficient for adequately representing the control flow for assembly programs.
Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI`` Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMPDEST``, ``JUMP`` and ``JUMPI``
are provided, because the first two obfuscate the data flow are provided, because the first two obfuscate the data flow
and the last two obfuscate control flow. Furthermore, functional statements of and the last two obfuscate control flow. Furthermore, functional statements of
the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like

View File

@ -38,6 +38,7 @@ class Error;
using ErrorList = std::vector<std::shared_ptr<Error const>>; using ErrorList = std::vector<std::shared_ptr<Error const>>;
struct CompilerError: virtual util::Exception {}; struct CompilerError: virtual util::Exception {};
struct StackTooDeepError: virtual CompilerError {};
struct InternalCompilerError: virtual util::Exception {}; struct InternalCompilerError: virtual util::Exception {};
struct FatalError: virtual util::Exception {}; struct FatalError: virtual util::Exception {};
struct UnimplementedFeatureError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {};
@ -61,7 +62,7 @@ struct InvalidAstError: virtual util::Exception {};
* They are passed as the first parameter of error reporting functions. * They are passed as the first parameter of error reporting functions.
* Suffix _error helps to find them in the sources. * Suffix _error helps to find them in the sources.
* The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error). * The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error).
* To create a new ID, one can add 0000_error and then run "python ./scripts/fix_error_ids.py" * To create a new ID, one can add 0000_error and then run "python ./scripts/error_codes.py --fix"
* from the root of the repo. * from the root of the repo.
*/ */
struct ErrorId struct ErrorId

View File

@ -48,9 +48,9 @@ public:
// Z3 "basic resources" limit. // Z3 "basic resources" limit.
// This is used to make the runs more deterministic and platform/machine independent. // This is used to make the runs more deterministic and platform/machine independent.
// The tests start failing for Z3 with less than 20000000, // The tests start failing for Z3 with less than 10000000,
// so using double that. // so using double that.
static int const resourceLimit = 40000000; static int const resourceLimit = 20000000;
private: private:
void declareFunction(std::string const& _name, Sort const& _sort); void declareFunction(std::string const& _name, Sort const& _sort);

View File

@ -158,7 +158,8 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
else if (_variable.isStateVariable()) else if (_variable.isStateVariable())
{ {
set<StructDefinition const*> structsSeen; set<StructDefinition const*> structsSeen;
if (structureSizeEstimate(*_variable.type(), structsSeen) >= bigint(1) << 64) TypeSet oversizedSubTypes;
if (structureSizeEstimate(*_variable.type(), structsSeen, oversizedSubTypes) >= bigint(1) << 64)
m_errorReporter.warning( m_errorReporter.warning(
3408_error, 3408_error,
_variable.location(), _variable.location(),
@ -166,6 +167,14 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
"Either use mappings or dynamic arrays and allow their size to be increased only " "Either use mappings or dynamic arrays and allow their size to be increased only "
"in small quantities per transaction." "in small quantities per transaction."
); );
for (Type const* type: oversizedSubTypes)
m_errorReporter.warning(
7325_error,
_variable.location(),
"Type " + type->canonicalName() + " has large size and thus makes collisions likely. "
"Either use mappings or dynamic arrays and allow their size to be increased only "
"in small quantities per transaction."
);
} }
return true; return true;
} }
@ -339,30 +348,43 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
return true; return true;
} }
bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen) bigint StaticAnalyzer::structureSizeEstimate(
Type const& _type,
set<StructDefinition const*>& _structsSeen,
TypeSet& _oversizedSubTypes
)
{ {
switch (_type.category()) switch (_type.category())
{ {
case Type::Category::Array: case Type::Category::Array:
{ {
auto const& t = dynamic_cast<ArrayType const&>(_type); auto const& t = dynamic_cast<ArrayType const&>(_type);
return structureSizeEstimate(*t.baseType(), _structsSeen) * (t.isDynamicallySized() ? 1 : t.length()); bigint baseTypeSize = structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes);
if (baseTypeSize >= bigint(1) << 64)
_oversizedSubTypes.insert(t.baseType());
if (!t.isDynamicallySized())
return structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes) * t.length();
break;
} }
case Type::Category::Struct: case Type::Category::Struct:
{ {
auto const& t = dynamic_cast<StructType const&>(_type); auto const& t = dynamic_cast<StructType const&>(_type);
bigint size = 1; bigint size = 1;
if (!_structsSeen.count(&t.structDefinition())) if (_structsSeen.count(&t.structDefinition()))
{ return size;
_structsSeen.insert(&t.structDefinition()); _structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr)) for (auto const& m: t.members(nullptr))
size += structureSizeEstimate(*m.type, _structsSeen); size += structureSizeEstimate(*m.type, _structsSeen, _oversizedSubTypes);
} _structsSeen.erase(&t.structDefinition());
return size; return size;
} }
case Type::Category::Mapping: case Type::Category::Mapping:
{ {
return structureSizeEstimate(*dynamic_cast<MappingType const&>(_type).valueType(), _structsSeen); auto const* valueType = dynamic_cast<MappingType const&>(_type).valueType();
bigint valueTypeSize = structureSizeEstimate(*valueType, _structsSeen, _oversizedSubTypes);
if (valueTypeSize >= bigint(1) << 64)
_oversizedSubTypes.insert(valueType);
break;
} }
default: default:
break; break;

View File

@ -73,8 +73,22 @@ private:
bool visit(BinaryOperation const& _operation) override; bool visit(BinaryOperation const& _operation) override;
bool visit(FunctionCall const& _functionCall) override; bool visit(FunctionCall const& _functionCall) override;
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>;
/// @returns the size of this type in storage, including all sub-types. /// @returns the size of this type in storage, including all sub-types.
static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen); static bigint structureSizeEstimate(
Type const& _type,
std::set<StructDefinition const*>& _structsSeen,
TypeSet& _oversizedSubTypes
);
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;

View File

@ -226,8 +226,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
else else
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
solAssert( assertThrow(
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables." "Stack too deep, try removing local variables."
); );
// fetch target storage reference // fetch target storage reference

View File

@ -404,7 +404,7 @@ void CompilerContext::appendInlineAssembly(
stackDiff -= 1; stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16) if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_identifier.location) << errinfo_sourceLocation(_identifier.location) <<
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
); );

View File

@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory(
// leave end_of_mem as dyn head pointer // leave end_of_mem as dyn head pointer
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++; dynPointers++;
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables."); assertThrow(
(argSize + dynPointers) < 16,
StackTooDeepError,
"Stack too deep, try using fewer variables."
);
} }
else else
{ {
@ -507,8 +511,9 @@ void CompilerUtils::encodeToMemory(
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{ {
// copy tail pointer (=mem_end - mem_start) to memory // copy tail pointer (=mem_end - mem_start) to memory
solAssert( assertThrow(
(2 + dynPointers) <= 16, (2 + dynPointers) <= 16,
StackTooDeepError,
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables." "Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
); );
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
@ -1289,7 +1294,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
// move variable starting from its top end in the stack // move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16) if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_variable.location()) << errinfo_sourceLocation(_variable.location()) <<
util::errinfo_comment("Stack too deep, try removing local variables.") util::errinfo_comment("Stack too deep, try removing local variables.")
); );
@ -1299,7 +1304,11 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{ {
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); assertThrow(
_stackDepth <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 0; i < _itemSize; ++i) for (unsigned i = 0; i < _itemSize; ++i)
m_context << dupInstruction(_stackDepth); m_context << dupInstruction(_stackDepth);
} }
@ -1321,14 +1330,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
void CompilerUtils::rotateStackUp(unsigned _items) void CompilerUtils::rotateStackUp(unsigned _items)
{ {
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); assertThrow(
_items - 1 <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 1; i < _items; ++i) for (unsigned i = 1; i < _items; ++i)
m_context << swapInstruction(_items - i); m_context << swapInstruction(_items - i);
} }
void CompilerUtils::rotateStackDown(unsigned _items) void CompilerUtils::rotateStackDown(unsigned _items)
{ {
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); assertThrow(
_items - 1 <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
for (unsigned i = 1; i < _items; ++i) for (unsigned i = 1; i < _items; ++i)
m_context << swapInstruction(i); m_context << swapInstruction(i);
} }

View File

@ -634,7 +634,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (stackLayout.size() > 17) if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_function.location()) << errinfo_sourceLocation(_function.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
@ -798,7 +798,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(variable->type()->sizeOnStack() == 1, ""); solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16) if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
@ -831,7 +831,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1; unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1) if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
); );

View File

@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), ""); solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
if (retSizeOnStack > 15) if (retSizeOnStack > 15)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_varDecl.location()) << errinfo_sourceLocation(_varDecl.location()) <<
errinfo_comment("Stack too deep.") errinfo_comment("Stack too deep.")
); );
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
{ {
if (itemSize + lvalueSize > 16) if (itemSize + lvalueSize > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_assignment.location()) << errinfo_sourceLocation(_assignment.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );

View File

@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
@ -61,7 +61,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
if (stackDiff > 16) if (stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << StackTooDeepError() <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );

View File

@ -1646,13 +1646,30 @@ string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType cons
}); });
} }
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type) string YulUtilFunctions::allocateMemoryStructFunction(StructType const& _type)
{ {
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier(); string functionName = "allocate_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>() -> memPtr { function <functionName>() -> memPtr {
memPtr := <alloc>(<allocSize>) memPtr := <alloc>(<allocSize>)
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
templ("allocSize", _type.memoryDataSize().str());
return templ.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_zero_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
memPtr := <allocStruct>()
let offset := memPtr let offset := memPtr
<#member> <#member>
mstore(offset, <zeroValue>()) mstore(offset, <zeroValue>())
@ -1661,10 +1678,9 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
templ("alloc", allocationFunction()); templ("allocStruct", allocateMemoryStructFunction(_type));
TypePointers const& members = _type.memoryMemberTypes(); TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size()); vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i) for (size_t i = 0; i < members.size(); ++i)

View File

@ -286,8 +286,13 @@ public:
/// signature: (length) -> memPtr /// signature: (length) -> memPtr
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type); std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates a memory struct (no
/// initialization takes place).
/// signature: () -> memPtr
std::string allocateMemoryStructFunction(StructType const& _type);
/// @returns the name of a function that allocates and zeroes a memory struct. /// @returns the name of a function that allocates and zeroes a memory struct.
/// signature: (members) -> memPtr /// signature: () -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type); std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @returns the name of the function that converts a value of type @a _from /// @returns the name of the function that converts a value of type @a _from

View File

@ -600,22 +600,30 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{ {
solUnimplementedAssert( solUnimplementedAssert(
_functionCall.annotation().kind == FunctionCallKind::FunctionCall || _functionCall.annotation().kind != FunctionCallKind::Unset,
_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
"This type of function call is not yet implemented" "This type of function call is not yet implemented"
); );
Type const& funcType = type(_functionCall.expression());
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
{ {
solAssert(funcType.category() == Type::Category::TypeType, "Expected category to be TypeType"); solAssert(
_functionCall.expression().annotation().type->category() == Type::Category::TypeType,
"Expected category to be TypeType"
);
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion"); solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
define(_functionCall, *_functionCall.arguments().front()); define(_functionCall, *_functionCall.arguments().front());
return; return;
} }
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(&funcType); FunctionTypePointer functionType = nullptr;
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
functionType = structType.constructorType();
}
else
functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
TypePointers parameterTypes = functionType->parameterTypes(); TypePointers parameterTypes = functionType->parameterTypes();
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments(); vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
@ -639,6 +647,34 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
arguments.push_back(callArguments[static_cast<size_t>(std::distance(callArgumentNames.begin(), it))]); arguments.push_back(callArguments[static_cast<size_t>(std::distance(callArgumentNames.begin(), it))]);
} }
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
define(_functionCall) << m_utils.allocateMemoryStructFunction(structType) << "()\n";
MemberList::MemberMap members = structType.nativeMembers(nullptr);
solAssert(members.size() == arguments.size(), "Struct parameter mismatch.");
for (size_t i = 0; i < arguments.size(); i++)
{
IRVariable converted = convert(*arguments[i], *parameterTypes[i]);
m_code <<
m_utils.writeToMemoryFunction(*functionType->parameterTypes()[i]) <<
"(add(" <<
IRVariable(_functionCall).part("mpos").name() <<
", " <<
structType.memoryOffsetOfMember(members[i].name) <<
"), " <<
converted.commaSeparatedList() <<
")\n";
}
return;
}
auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()); auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression());
if (memberAccess) if (memberAccess)
{ {

View File

@ -197,8 +197,11 @@ void CHC::endVisit(ContractDefinition const& _contract)
bool CHC::visit(FunctionDefinition const& _function) bool CHC::visit(FunctionDefinition const& _function)
{ {
if (!shouldVisit(_function)) if (!_function.isImplemented())
{
connectBlocks(genesis(), summary(_function));
return false; return false;
}
// This is the case for base constructor inlining. // This is the case for base constructor inlining.
if (m_currentFunction) if (m_currentFunction)
@ -243,7 +246,7 @@ bool CHC::visit(FunctionDefinition const& _function)
void CHC::endVisit(FunctionDefinition const& _function) void CHC::endVisit(FunctionDefinition const& _function)
{ {
if (!shouldVisit(_function)) if (!_function.isImplemented())
return; return;
// This is the case for base constructor inlining. // This is the case for base constructor inlining.
@ -474,11 +477,14 @@ void CHC::endVisit(FunctionCall const& _funCall)
internalFunctionCall(_funCall); internalFunctionCall(_funCall);
break; break;
case FunctionType::Kind::External: case FunctionType::Kind::External:
case FunctionType::Kind::BareStaticCall:
externalFunctionCall(_funCall);
SMTEncoder::endVisit(_funCall);
break;
case FunctionType::Kind::DelegateCall: case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Creation: case FunctionType::Kind::Creation:
case FunctionType::Kind::KECCAK256: case FunctionType::Kind::KECCAK256:
case FunctionType::Kind::ECRecover: case FunctionType::Kind::ECRecover:
@ -574,6 +580,35 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
m_context.addAssertion(m_error.currentValue() == previousError); m_context.addAssertion(m_error.currentValue() == previousError);
} }
void CHC::externalFunctionCall(FunctionCall const& _funCall)
{
solAssert(m_currentContract, "");
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
auto const* function = functionCallToDefinition(_funCall);
if (!function)
return;
for (auto var: function->returnParameters())
m_context.variable(*var)->increaseIndex();
auto preCallState = currentStateVariables();
bool usesStaticCall = kind == FunctionType::Kind::BareStaticCall ||
function->stateMutability() == StateMutability::Pure ||
function->stateMutability() == StateMutability::View;
if (!usesStaticCall)
for (auto const* var: m_stateVariables)
m_context.variable(*var)->increaseIndex();
auto nondet = (*m_nondetInterfaces.at(m_currentContract))(preCallState + currentStateVariables());
m_context.addAssertion(nondet);
m_context.addAssertion(m_error.currentValue() == 0);
}
void CHC::unknownFunctionCall(FunctionCall const&) void CHC::unknownFunctionCall(FunctionCall const&)
{ {
/// Function calls are not handled at the moment, /// Function calls are not handled at the moment,
@ -651,11 +686,6 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
} }
} }
bool CHC::shouldVisit(FunctionDefinition const& _function) const
{
return _function.isImplemented();
}
void CHC::setCurrentBlock( void CHC::setCurrentBlock(
smt::SymbolicFunctionVariable const& _block, smt::SymbolicFunctionVariable const& _block,
vector<smtutil::Expression> const* _arguments vector<smtutil::Expression> const* _arguments
@ -710,10 +740,14 @@ smtutil::SortPointer CHC::constructorSort()
smtutil::SortPointer CHC::interfaceSort() smtutil::SortPointer CHC::interfaceSort()
{ {
return make_shared<smtutil::FunctionSort>( solAssert(m_currentContract, "");
m_stateSorts, return interfaceSort(*m_currentContract);
smtutil::SortProvider::boolSort }
);
smtutil::SortPointer CHC::nondetInterfaceSort()
{
solAssert(m_currentContract, "");
return nondetInterfaceSort(*m_currentContract);
} }
smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract) smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
@ -724,6 +758,15 @@ smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
); );
} }
smtutil::SortPointer CHC::nondetInterfaceSort(ContractDefinition const& _contract)
{
auto sorts = stateSorts(_contract);
return make_shared<smtutil::FunctionSort>(
sorts + sorts,
smtutil::SortProvider::boolSort
);
}
smtutil::SortPointer CHC::arity0FunctionSort() smtutil::SortPointer CHC::arity0FunctionSort()
{ {
return make_shared<smtutil::FunctionSort>( return make_shared<smtutil::FunctionSort>(
@ -778,7 +821,12 @@ smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, Contr
auto inputSorts = applyMap(_function.parameters(), smtSort); auto inputSorts = applyMap(_function.parameters(), smtSort);
auto outputSorts = applyMap(_function.returnParameters(), smtSort); auto outputSorts = applyMap(_function.returnParameters(), smtSort);
return make_shared<smtutil::FunctionSort>( return make_shared<smtutil::FunctionSort>(
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + sorts + inputSorts + sorts + outputSorts, vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} +
sorts +
inputSorts +
sorts +
inputSorts +
outputSorts,
smtutil::SortProvider::boolSort smtutil::SortProvider::boolSort
); );
} }
@ -802,11 +850,48 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
{ {
string suffix = base->name() + "_" + to_string(base->id()); string suffix = base->name() + "_" + to_string(base->id());
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix); m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
m_nondetInterfaces[base] = createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix);
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base)) for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
if (!m_context.knownVariable(*var)) if (!m_context.knownVariable(*var))
createVariable(*var); createVariable(*var);
/// Base nondeterministic interface that allows
/// 0 steps to be taken, used as base for the inductive
/// rule for each function.
auto const& iface = *m_nondetInterfaces.at(base);
auto state0 = stateVariablesAtIndex(0, *base);
addRule(iface(state0 + state0), "base_nondet");
for (auto const* function: base->definedFunctions()) for (auto const* function: base->definedFunctions())
{
for (auto var: function->parameters())
createVariable(*var);
for (auto var: function->returnParameters())
createVariable(*var);
for (auto const* var: function->localVariables())
createVariable(*var);
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract)); m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
if (!base->isLibrary() && !base->isInterface() && !function->isConstructor())
{
auto state1 = stateVariablesAtIndex(1, *base);
auto state2 = stateVariablesAtIndex(2, *base);
auto nondetPre = iface(state0 + state1);
auto nondetPost = iface(state0 + state2);
vector<smtutil::Expression> args{m_error.currentValue()};
args += state1 +
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); }) +
state2 +
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) +
applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); });
connectBlocks(nondetPre, nondetPost, (*m_summaries.at(base).at(function))(args));
}
}
} }
} }
@ -842,15 +927,22 @@ smtutil::Expression CHC::summary(ContractDefinition const&)
); );
} }
smtutil::Expression CHC::summary(FunctionDefinition const& _function) smtutil::Expression CHC::summary(FunctionDefinition const& _function, ContractDefinition const& _contract)
{ {
vector<smtutil::Expression> args{m_error.currentValue()}; vector<smtutil::Expression> args{m_error.currentValue()};
auto contract = _function.annotation().contract; auto contract = _function.annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables(); args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables(_contract);
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); }); args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables(); args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables(_contract);
args += applyMap(_function.parameters(), [this](auto _var) { return currentValue(*_var); });
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); }); args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
return (*m_summaries.at(m_currentContract).at(&_function))(args); return (*m_summaries.at(&_contract).at(&_function))(args);
}
smtutil::Expression CHC::summary(FunctionDefinition const& _function)
{
solAssert(m_currentContract, "");
return summary(_function, *m_currentContract);
} }
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix) unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
@ -893,13 +985,18 @@ vector<smtutil::Expression> CHC::initialStateVariables()
return stateVariablesAtIndex(0); return stateVariablesAtIndex(0);
} }
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index) vector<smtutil::Expression> CHC::initialStateVariables(ContractDefinition const& _contract)
{ {
solAssert(m_currentContract, ""); return stateVariablesAtIndex(0, _contract);
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
} }
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract) vector<smtutil::Expression> CHC::stateVariablesAtIndex(int _index)
{
solAssert(m_currentContract, "");
return stateVariablesAtIndex(_index, *m_currentContract);
}
vector<smtutil::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
{ {
return applyMap( return applyMap(
stateVariablesIncludingInheritedAndPrivate(_contract), stateVariablesIncludingInheritedAndPrivate(_contract),
@ -910,7 +1007,12 @@ vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, Contract
vector<smtutil::Expression> CHC::currentStateVariables() vector<smtutil::Expression> CHC::currentStateVariables()
{ {
solAssert(m_currentContract, ""); solAssert(m_currentContract, "");
return applyMap(m_stateVariables, [this](auto _var) { return currentValue(*_var); }); return currentStateVariables(*m_currentContract);
}
vector<smtutil::Expression> CHC::currentStateVariables(ContractDefinition const& _contract)
{
return applyMap(stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); });
} }
vector<smtutil::Expression> CHC::currentFunctionVariables() vector<smtutil::Expression> CHC::currentFunctionVariables()
@ -978,22 +1080,28 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
m_error.increaseIndex(); m_error.increaseIndex();
vector<smtutil::Expression> args{m_error.currentValue()}; vector<smtutil::Expression> args{m_error.currentValue()};
auto const* contract = function->annotation().contract; auto const* contract = function->annotation().contract;
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
bool otherContract = contract->isLibrary() ||
funType.kind() == FunctionType::Kind::External ||
funType.kind() == FunctionType::Kind::BareStaticCall;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : currentStateVariables(); args += otherContract ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
args += symbolicArguments(_funCall); args += symbolicArguments(_funCall);
for (auto const& var: m_stateVariables) if (!otherContract)
m_context.variable(*var)->increaseIndex(); for (auto const& var: m_stateVariables)
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables(); m_context.variable(*var)->increaseIndex();
args += otherContract ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
auto const& returnParams = function->returnParameters(); for (auto var: function->parameters() + function->returnParameters())
for (auto param: returnParams) {
if (m_context.knownVariable(*param)) if (m_context.knownVariable(*var))
m_context.variable(*param)->increaseIndex(); m_context.variable(*var)->increaseIndex();
else else
createVariable(*param); createVariable(*var);
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); }); args.push_back(currentValue(*var));
}
if (contract->isLibrary()) if (otherContract)
return (*m_summaries.at(contract).at(function))(args); return (*m_summaries.at(contract).at(function))(args);
solAssert(m_currentContract, ""); solAssert(m_currentContract, "");

View File

@ -77,6 +77,7 @@ private:
void visitAssert(FunctionCall const& _funCall); void visitAssert(FunctionCall const& _funCall);
void internalFunctionCall(FunctionCall const& _funCall); void internalFunctionCall(FunctionCall const& _funCall);
void externalFunctionCall(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall); void unknownFunctionCall(FunctionCall const& _funCall);
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override; void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
//@} //@}
@ -95,7 +96,6 @@ private:
void resetContractAnalysis(); void resetContractAnalysis();
void eraseKnowledge(); void eraseKnowledge();
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override; void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
bool shouldVisit(FunctionDefinition const& _function) const;
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr); void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot); std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract); static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
@ -106,7 +106,9 @@ private:
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract); static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
smtutil::SortPointer constructorSort(); smtutil::SortPointer constructorSort();
smtutil::SortPointer interfaceSort(); smtutil::SortPointer interfaceSort();
smtutil::SortPointer nondetInterfaceSort();
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const); static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
smtutil::SortPointer arity0FunctionSort(); smtutil::SortPointer arity0FunctionSort();
smtutil::SortPointer sort(FunctionDefinition const& _function); smtutil::SortPointer sort(FunctionDefinition const& _function);
smtutil::SortPointer sort(ASTNode const* _block); smtutil::SortPointer sort(ASTNode const* _block);
@ -149,10 +151,12 @@ private:
/// @returns the symbolic values of the state variables at the beginning /// @returns the symbolic values of the state variables at the beginning
/// of the current transaction. /// of the current transaction.
std::vector<smtutil::Expression> initialStateVariables(); std::vector<smtutil::Expression> initialStateVariables();
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index); std::vector<smtutil::Expression> initialStateVariables(ContractDefinition const& _contract);
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract); std::vector<smtutil::Expression> stateVariablesAtIndex(int _index);
std::vector<smtutil::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
/// @returns the current symbolic values of the current state variables. /// @returns the current symbolic values of the current state variables.
std::vector<smtutil::Expression> currentStateVariables(); std::vector<smtutil::Expression> currentStateVariables();
std::vector<smtutil::Expression> currentStateVariables(ContractDefinition const& _contract);
/// @returns the current symbolic values of the current function's /// @returns the current symbolic values of the current function's
/// input and output parameters. /// input and output parameters.
@ -173,6 +177,7 @@ private:
smtutil::Expression summary(ContractDefinition const& _contract); smtutil::Expression summary(ContractDefinition const& _contract);
/// @returns a predicate that defines a function summary. /// @returns a predicate that defines a function summary.
smtutil::Expression summary(FunctionDefinition const& _function); smtutil::Expression summary(FunctionDefinition const& _function);
smtutil::Expression summary(FunctionDefinition const& _function, ContractDefinition const& _contract);
//@} //@}
/// Solver related. /// Solver related.
@ -212,6 +217,12 @@ private:
/// Single entry block for all functions. /// Single entry block for all functions.
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces; std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
/// Nondeterministic interfaces.
/// These are used when the analyzed contract makes external calls to unknown code,
/// which means that the analyzed contract can potentially be called
/// nondeterministically.
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_nondetInterfaces;
/// Artificial Error predicate. /// Artificial Error predicate.
/// Single error block for all assertions. /// Single error block for all assertions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate; std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;

View File

@ -94,19 +94,12 @@ void DocStringParser::parse(string const& _docString, ErrorReporter& _errorRepor
{ {
// we found a tag // we found a tag
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end); auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end) auto tagName = string(tagPos + 1, tagNameEndPos);
{ auto tagDataPos = (tagNameEndPos != end) ? tagNameEndPos + 1 : tagNameEndPos;
m_errorReporter->docstringParsingError( currPos = parseDocTag(tagDataPos, end, tagName);
9222_error,
"End of tag " + string(tagPos, tagNameEndPos) + " not found"
);
break;
}
currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos));
} }
else if (!!m_lastTag) // continuation of the previous tag else if (!!m_lastTag) // continuation of the previous tag
currPos = appendDocTag(currPos, end); currPos = parseDocTagLine(currPos, end, true);
else if (currPos != end) else if (currPos != end)
{ {
// if it begins without a tag then consider it as @notice // if it begins without a tag then consider it as @notice
@ -127,7 +120,7 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo
{ {
solAssert(!!m_lastTag, ""); solAssert(!!m_lastTag, "");
auto nlPos = find(_pos, _end, '\n'); auto nlPos = find(_pos, _end, '\n');
if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t') if (_appending && _pos != _end && *_pos != ' ' && *_pos != '\t')
m_lastTag->content += " "; m_lastTag->content += " ";
else if (!_appending) else if (!_appending)
_pos = skipWhitespace(_pos, _end); _pos = skipWhitespace(_pos, _end);
@ -179,13 +172,7 @@ DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string
} }
} }
else else
return appendDocTag(_pos, _end); return parseDocTagLine(_pos, _end, true);
}
DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end)
{
solAssert(!!m_lastTag, "");
return parseDocTagLine(_pos, _end, true);
} }
void DocStringParser::newTag(string const& _tagName) void DocStringParser::newTag(string const& _tagName)

View File

@ -50,7 +50,6 @@ private:
iter parseDocTagParam(iter _pos, iter _end); iter parseDocTagParam(iter _pos, iter _end);
iter appendDocTagParam(iter _pos, iter _end); iter appendDocTagParam(iter _pos, iter _end);
void parseDocString(std::string const& _string); void parseDocString(std::string const& _string);
iter appendDocTag(iter _pos, iter _end);
/// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position /// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position
/// after the tag. /// after the tag.
iter parseDocTag(iter _pos, iter _end, std::string const& _tag); iter parseDocTag(iter _pos, iter _end, std::string const& _tag);

View File

@ -264,7 +264,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
if (f->literalArguments) if (f->literalArguments)
needsLiteralArguments = &f->literalArguments.value(); needsLiteralArguments = &f->literalArguments.value();
warnOnInstructions(_funCall); validateInstructions(_funCall);
} }
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
[&](Scope::Variable const&) [&](Scope::Variable const&)
@ -282,7 +282,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
} }
})) }))
{ {
if (!warnOnInstructions(_funCall)) if (!validateInstructions(_funCall))
m_errorReporter.declarationError(4619_error, _funCall.functionName.location, "Function not found."); m_errorReporter.declarationError(4619_error, _funCall.functionName.location, "Function not found.");
yulAssert(!watcher.ok(), "Expected a reported error."); yulAssert(!watcher.ok(), "Expected a reported error.");
} }
@ -541,16 +541,16 @@ void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, Sour
); );
} }
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location) bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
{ {
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier)); auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
if (builtin && builtin->instruction.has_value()) if (builtin && builtin->instruction.has_value())
return warnOnInstructions(builtin->instruction.value(), _location); return validateInstructions(builtin->instruction.value(), _location);
else else
return false; return false;
} }
bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation const& _location) bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocation const& _location)
{ {
// We assume that returndatacopy, returndatasize and staticcall are either all available // We assume that returndatacopy, returndatasize and staticcall are either all available
// or all not available. // or all not available.
@ -558,9 +558,9 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
// Similarly we assume bitwise shifting and create2 go together. // Similarly we assume bitwise shifting and create2 go together.
yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
auto errorForVM = [&](string const& vmKindMessage) { auto errorForVM = [&](ErrorId _errorId, string const& vmKindMessage) {
m_errorReporter.typeError( m_errorReporter.typeError(
7079_error, _errorId,
_location, _location,
"The \"" + "The \"" +
boost::to_lower_copy(instructionInfo(_instr).name) boost::to_lower_copy(instructionInfo(_instr).name)
@ -577,25 +577,24 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
_instr == evmasm::Instruction::RETURNDATACOPY || _instr == evmasm::Instruction::RETURNDATACOPY ||
_instr == evmasm::Instruction::RETURNDATASIZE _instr == evmasm::Instruction::RETURNDATASIZE
) && !m_evmVersion.supportsReturndata()) ) && !m_evmVersion.supportsReturndata())
errorForVM("only available for Byzantium-compatible"); errorForVM(7756_error, "only available for Byzantium-compatible");
else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall())
errorForVM("only available for Byzantium-compatible"); errorForVM(1503_error, "only available for Byzantium-compatible");
else if (( else if ((
_instr == evmasm::Instruction::SHL || _instr == evmasm::Instruction::SHL ||
_instr == evmasm::Instruction::SHR || _instr == evmasm::Instruction::SHR ||
_instr == evmasm::Instruction::SAR _instr == evmasm::Instruction::SAR
) && !m_evmVersion.hasBitwiseShifting()) ) && !m_evmVersion.hasBitwiseShifting())
errorForVM("only available for Constantinople-compatible"); errorForVM(6612_error, "only available for Constantinople-compatible");
else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2())
errorForVM("only available for Constantinople-compatible"); errorForVM(6166_error, "only available for Constantinople-compatible");
else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
errorForVM("only available for Constantinople-compatible"); errorForVM(7110_error, "only available for Constantinople-compatible");
else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID()) else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID())
errorForVM("only available for Istanbul-compatible"); errorForVM(1561_error, "only available for Istanbul-compatible");
else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance()) else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
errorForVM("only available for Istanbul-compatible"); errorForVM(7721_error, "only available for Istanbul-compatible");
else if (_instr == evmasm::Instruction::PC) else if (_instr == evmasm::Instruction::PC)
{
m_errorReporter.error( m_errorReporter.error(
2450_error, 2450_error,
Error::Type::SyntaxError, Error::Type::SyntaxError,
@ -603,7 +602,8 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
"PC instruction is a low-level EVM feature. " "PC instruction is a low-level EVM feature. "
"Because of that PC is disallowed in strict assembly." "Because of that PC is disallowed in strict assembly."
); );
} else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
errorForVM(3672_error, "only available for Istanbul-compatible");
else if ( else if (
_instr == evmasm::Instruction::JUMP || _instr == evmasm::Instruction::JUMP ||
_instr == evmasm::Instruction::JUMPI || _instr == evmasm::Instruction::JUMPI ||

View File

@ -110,12 +110,12 @@ private:
Scope& scope(Block const* _block); Scope& scope(Block const* _block);
void expectValidType(YulString _type, langutil::SourceLocation const& _location); void expectValidType(YulString _type, langutil::SourceLocation const& _location);
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location); void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
bool warnOnInstructions(FunctionCall const& _functionCall) bool validateInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
bool validateInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
bool validateInstructions(FunctionCall const& _functionCall)
{ {
return warnOnInstructions(_functionCall.functionName.name.str(), _functionCall.functionName.location); return validateInstructions(_functionCall.functionName.name.str(), _functionCall.functionName.location);
} }
yul::ExternalIdentifierAccess::Resolver m_resolver; yul::ExternalIdentifierAccess::Resolver m_resolver;

View File

@ -21,7 +21,7 @@
#include <libyul/AsmJsonConverter.h> #include <libyul/AsmJsonConverter.h>
#include <libyul/AsmData.h> #include <libyul/AsmData.h>
#include <liblangutil/Exceptions.h> #include <libyul/Exceptions.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
using namespace std; using namespace std;
@ -38,7 +38,7 @@ Json::Value AsmJsonConverter::operator()(Block const& _node) const
Json::Value AsmJsonConverter::operator()(TypedName const& _node) const Json::Value AsmJsonConverter::operator()(TypedName const& _node) const
{ {
solAssert(!_node.name.empty(), "Invalid variable name."); yulAssert(!_node.name.empty(), "Invalid variable name.");
Json::Value ret = createAstNode(_node.location, "YulTypedName"); Json::Value ret = createAstNode(_node.location, "YulTypedName");
ret["name"] = _node.name.str(); ret["name"] = _node.name.str();
ret["type"] = _node.type.str(); ret["type"] = _node.type.str();
@ -51,7 +51,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
switch (_node.kind) switch (_node.kind)
{ {
case LiteralKind::Number: case LiteralKind::Number:
solAssert( yulAssert(
util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()), util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()),
"Invalid number literal" "Invalid number literal"
); );
@ -71,7 +71,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
Json::Value AsmJsonConverter::operator()(Identifier const& _node) const Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
{ {
solAssert(!_node.name.empty(), "Invalid identifier"); yulAssert(!_node.name.empty(), "Invalid identifier");
Json::Value ret = createAstNode(_node.location, "YulIdentifier"); Json::Value ret = createAstNode(_node.location, "YulIdentifier");
ret["name"] = _node.name.str(); ret["name"] = _node.name.str();
return ret; return ret;
@ -79,7 +79,7 @@ Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
Json::Value AsmJsonConverter::operator()(Assignment const& _node) const Json::Value AsmJsonConverter::operator()(Assignment const& _node) const
{ {
solAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax"); yulAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax");
Json::Value ret = createAstNode(_node.location, "YulAssignment"); Json::Value ret = createAstNode(_node.location, "YulAssignment");
for (auto const& var: _node.variableNames) for (auto const& var: _node.variableNames)
ret["variableNames"].append((*this)(var)); ret["variableNames"].append((*this)(var));
@ -115,7 +115,7 @@ Json::Value AsmJsonConverter::operator()(VariableDeclaration const& _node) const
Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
{ {
solAssert(!_node.name.empty(), "Invalid function name."); yulAssert(!_node.name.empty(), "Invalid function name.");
Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition"); Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition");
ret["name"] = _node.name.str(); ret["name"] = _node.name.str();
for (auto const& var: _node.parameters) for (auto const& var: _node.parameters)
@ -128,7 +128,7 @@ Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
Json::Value AsmJsonConverter::operator()(If const& _node) const Json::Value AsmJsonConverter::operator()(If const& _node) const
{ {
solAssert(_node.condition, "Invalid if condition."); yulAssert(_node.condition, "Invalid if condition.");
Json::Value ret = createAstNode(_node.location, "YulIf"); Json::Value ret = createAstNode(_node.location, "YulIf");
ret["condition"] = std::visit(*this, *_node.condition); ret["condition"] = std::visit(*this, *_node.condition);
ret["body"] = (*this)(_node.body); ret["body"] = (*this)(_node.body);
@ -137,7 +137,7 @@ Json::Value AsmJsonConverter::operator()(If const& _node) const
Json::Value AsmJsonConverter::operator()(Switch const& _node) const Json::Value AsmJsonConverter::operator()(Switch const& _node) const
{ {
solAssert(_node.expression, "Invalid expression pointer."); yulAssert(_node.expression, "Invalid expression pointer.");
Json::Value ret = createAstNode(_node.location, "YulSwitch"); Json::Value ret = createAstNode(_node.location, "YulSwitch");
ret["expression"] = std::visit(*this, *_node.expression); ret["expression"] = std::visit(*this, *_node.expression);
for (auto const& var: _node.cases) for (auto const& var: _node.cases)
@ -155,14 +155,14 @@ Json::Value AsmJsonConverter::operator()(Case const& _node) const
Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const
{ {
solAssert(_node.condition, "Invalid for loop condition."); yulAssert(_node.condition, "Invalid for loop condition.");
Json::Value ret = createAstNode(_node.location, "YulForLoop"); Json::Value ret = createAstNode(_node.location, "YulForLoop");
ret["pre"] = (*this)(_node.pre); ret["pre"] = (*this)(_node.pre);
ret["condition"] = std::visit(*this, *_node.condition); ret["condition"] = std::visit(*this, *_node.condition);
ret["post"] = (*this)(_node.post); ret["post"] = (*this)(_node.post);
ret["body"] = (*this)(_node.body); ret["body"] = (*this)(_node.body);
return ret; return ret;
} }
Json::Value AsmJsonConverter::operator()(Break const& _node) const Json::Value AsmJsonConverter::operator()(Break const& _node) const
{ {
@ -196,7 +196,6 @@ Json::Value AsmJsonConverter::vectorOfVariantsToJson(vector<T> const& _vec) cons
Json::Value ret{Json::arrayValue}; Json::Value ret{Json::arrayValue};
for (auto const& var: _vec) for (auto const& var: _vec)
ret.append(std::visit(*this, var)); ret.append(std::visit(*this, var));
return ret; return ret;
} }

View File

@ -108,7 +108,7 @@ void AssemblyStack::translate(AssemblyStack::Language _targetLanguage)
if (m_language == _targetLanguage) if (m_language == _targetLanguage)
return; return;
solAssert( yulAssert(
m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm, m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm,
"Invalid language combination" "Invalid language combination"
); );
@ -160,7 +160,7 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o
dialect = &EVMDialectTyped::instance(m_evmVersion); dialect = &EVMDialectTyped::instance(m_evmVersion);
break; break;
default: default:
solAssert(false, "Invalid language."); yulAssert(false, "Invalid language.");
break; break;
} }

View File

@ -218,8 +218,9 @@ void CodeGenerator::assemble(
} }
catch (StackTooDeepError const& _e) catch (StackTooDeepError const& _e)
{ {
yulAssert( assertThrow(
false, false,
langutil::StackTooDeepError,
"Stack too deep when compiling inline assembly" + "Stack too deep when compiling inline assembly" +
(_e.comment() ? ": " + *_e.comment() : ".") (_e.comment() ? ": " + *_e.comment() : ".")
); );

View File

@ -93,7 +93,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> _generateCode std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> _generateCode
) )
{ {
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), ""); yulAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
YulString name{std::move(_name)}; YulString name{std::move(_name)};
BuiltinFunctionForEVM f; BuiltinFunctionForEVM f;
@ -124,19 +124,18 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
) )
builtins.emplace(createEVMFunction(instr.first, instr.second)); builtins.emplace(createEVMFunction(instr.first, instr.second));
builtins.emplace(createFunction("linkersymbol", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
function<void(Expression const&)>
) {
yulAssert(_call.arguments.size() == 1, "");
Expression const& arg = _call.arguments.front();
_assembly.appendLinkerSymbol(std::get<Literal>(arg).value.str());
}));
if (_objectAccess) if (_objectAccess)
{ {
builtins.emplace(createFunction("linkersymbol", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
function<void(Expression const&)>
) {
yulAssert(_call.arguments.size() == 1, "");
Expression const& arg = _call.arguments.front();
_assembly.appendLinkerSymbol(std::get<Literal>(arg).value.str());
}));
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, []( builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
@ -207,7 +206,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
BuiltinContext&, BuiltinContext&,
std::function<void(Expression const&)> _visitExpression std::function<void(Expression const&)> _visitExpression
) { ) {
solAssert(_call.arguments.size() == 2, ""); yulAssert(_call.arguments.size() == 2, "");
_visitExpression(_call.arguments[1]); _visitExpression(_call.arguments[1]);
_assembly.setSourceLocation(_call.location); _assembly.setSourceLocation(_call.location);
@ -227,7 +226,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
BuiltinContext&, BuiltinContext&,
std::function<void(Expression const&)> std::function<void(Expression const&)>
) { ) {
solAssert(_call.arguments.size() == 1, ""); yulAssert(_call.arguments.size() == 1, "");
_assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str()); _assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str());
} }
)); ));

View File

@ -253,6 +253,21 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
// assignment to slot contents denoted by "name" // assignment to slot contents denoted by "name"
m_memory.eraseValue(name); m_memory.eraseValue(name);
} }
if (_value && _variables.size() == 1)
{
YulString variable = *_variables.begin();
if (!movableChecker.referencedVariables().count(variable))
{
// This might erase additional knowledge about the slot.
// On the other hand, if we knew the value in the slot
// already, then the sload() / mload() would have been replaced by a variable anyway.
if (auto key = isSimpleLoad(evmasm::Instruction::MLOAD, *_value))
m_memory.set(*key, variable);
else if (auto key = isSimpleLoad(evmasm::Instruction::SLOAD, *_value))
m_storage.set(*key, variable);
}
}
} }
void DataFlowAnalyzer::pushScope(bool _functionScope) void DataFlowAnalyzer::pushScope(bool _functionScope)
@ -401,3 +416,25 @@ std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore(
return {}; return {};
} }
std::optional<YulString> DataFlowAnalyzer::isSimpleLoad(
evmasm::Instruction _load,
Expression const& _expression
) const
{
yulAssert(
_load == evmasm::Instruction::MLOAD ||
_load == evmasm::Instruction::SLOAD,
""
);
if (holds_alternative<FunctionCall>(_expression))
{
FunctionCall const& funCall = std::get<FunctionCall>(_expression);
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
if (auto const* builtin = dialect->builtin(funCall.functionName.name))
if (builtin->instruction == _load)
if (holds_alternative<Identifier>(funCall.arguments.at(0)))
return std::get<Identifier>(funCall.arguments.at(0)).name;
}
return {};
}

View File

@ -142,11 +142,20 @@ protected:
/// Returns true iff the variable is in scope. /// Returns true iff the variable is in scope.
bool inScope(YulString _variableName) const; bool inScope(YulString _variableName) const;
/// Checks if the statement is sstore(a, b) / mstore(a, b)
/// where a and b are variables and returns these variables in that case.
std::optional<std::pair<YulString, YulString>> isSimpleStore( std::optional<std::pair<YulString, YulString>> isSimpleStore(
evmasm::Instruction _store, evmasm::Instruction _store,
ExpressionStatement const& _statement ExpressionStatement const& _statement
) const; ) const;
/// Checks if the expression is sload(a) / mload(a)
/// where a is a variable and returns the variable in that case.
std::optional<YulString> isSimpleLoad(
evmasm::Instruction _load,
Expression const& _expression
) const;
Dialect const& m_dialect; Dialect const& m_dialect;
/// Side-effects of user-defined functions. Worst-case side-effects are assumed /// Side-effects of user-defined functions. Worst-case side-effects are assumed
/// if this is not provided or the function is not found. /// if this is not provided or the function is not found.

View File

@ -194,7 +194,7 @@ void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
void IntroduceControlFlowSSA::operator()(ForLoop& _for) void IntroduceControlFlowSSA::operator()(ForLoop& _for)
{ {
(*this)(_for.pre); yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
Assignments assignments; Assignments assignments;
assignments(_for.body); assignments(_for.body);
@ -357,11 +357,7 @@ void PropagateValues::operator()(Assignment& _assignment)
void PropagateValues::operator()(ForLoop& _for) void PropagateValues::operator()(ForLoop& _for)
{ {
// This will clear the current value in case of a reassignment inside the yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
// init part, although the new variable would still be in scope inside the whole loop.
// This small inefficiency is fine if we move the pre part of all for loops out
// of the for loop.
(*this)(_for.pre);
Assignments assignments; Assignments assignments;
assignments(_for.body); assignments(_for.body);

View File

@ -85,7 +85,7 @@ class NameDispenser;
* *
* TODO Which transforms are required to keep this idempotent? * TODO Which transforms are required to keep this idempotent?
* *
* Prerequisite: Disambiguator. * Prerequisite: Disambiguator, ForLoopInitRewriter.
*/ */
class SSATransform: public ASTModifier class SSATransform: public ASTModifier
{ {

261
scripts/error_codes.py Executable file
View File

@ -0,0 +1,261 @@
#! /usr/bin/env python3
import random
import re
import os
import getopt
import sys
from os import path
ENCODING = "utf-8"
SOURCE_FILE_PATTERN = r"\b\d+_error\b"
def read_file(file_name):
content = None
try:
with open(file_name, "r", encoding=ENCODING) as f:
content = f.read()
finally:
if content == None:
print(f"Error reading: {file_name}")
return content
def write_file(file_name, content):
with open(file_name, "w", encoding=ENCODING) as f:
f.write(content)
def in_comment(source, pos):
slash_slash_pos = source.rfind("//", 0, pos)
lf_pos = source.rfind("\n", 0, pos)
if slash_slash_pos > lf_pos:
return True
slash_star_pos = source.rfind("/*", 0, pos)
star_slash_pos = source.rfind("*/", 0, pos)
return slash_star_pos > star_slash_pos
def find_ids_in_source_file(file_name, ids):
source = read_file(file_name)
for m in re.finditer(SOURCE_FILE_PATTERN, source):
if in_comment(source, m.start()):
continue
underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos]
if id in ids:
ids[id] += 1
else:
ids[id] = 1
def get_used_ids(file_names):
used_ids = {}
for file_name in file_names:
find_ids_in_source_file(file_name, used_ids)
return used_ids
def get_next_id(available_ids):
assert len(available_ids) > 0, "Out of IDs"
next_id = random.choice(list(available_ids))
available_ids.remove(next_id)
return next_id
def fix_ids_in_file(file_name, available_ids, used_ids):
source = read_file(file_name)
k = 0
destination = []
for m in re.finditer(SOURCE_FILE_PATTERN, source):
destination.extend(source[k:m.start()])
underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos]
# incorrect id or id has a duplicate somewhere
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
assert id in used_ids
new_id = get_next_id(available_ids)
assert new_id not in used_ids
used_ids[id] -= 1
else:
new_id = id
destination.extend(new_id + "_error")
k = m.end()
destination.extend(source[k:])
destination = ''.join(destination)
if source != destination:
write_file(file_name, destination)
print(f"Fixed file: {file_name}")
def fix_ids(used_ids, file_names):
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
for file_name in file_names:
fix_ids_in_file(file_name, available_ids, used_ids)
def find_files(top_dir, sub_dirs, extensions):
"""Builds a list of files with given extensions in specified subdirectories"""
source_file_names = []
for dir in sub_dirs:
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
for file_name in file_names:
_, ext = path.splitext(file_name)
if ext in extensions:
source_file_names.append(path.join(root, file_name))
return source_file_names
def find_ids_in_test_file(file_name):
source = read_file(file_name)
pattern = r"^// (.*Error|Warning) \d\d\d\d:"
return {m.group(0)[-5:-1] for m in re.finditer(pattern, source, flags=re.MULTILINE)}
def find_ids_in_test_files(file_names):
used_ids = set()
for file_name in file_names:
used_ids |= find_ids_in_test_file(file_name)
return used_ids
def print_ids(ids):
for k, id in enumerate(sorted(ids)):
if k % 10 > 0:
print(" ", end="")
elif k > 0:
print()
print(id, end="")
def examine_id_coverage(top_dir, used_ids):
test_sub_dirs = [
path.join("test", "libsolidity", "errorRecoveryTests"),
path.join("test", "libsolidity", "smtCheckerTests"),
path.join("test", "libsolidity", "syntaxTests")
]
test_file_names = find_files(
top_dir,
test_sub_dirs,
[".sol"]
)
covered_ids = find_ids_in_test_files(test_file_names)
print(f"IDs in source files: {len(used_ids)}")
print(f"IDs in test files : {len(covered_ids)} ({len(covered_ids) - len(used_ids)})")
print()
unused_covered_ids = covered_ids - used_ids
if len(unused_covered_ids) != 0:
print("Error. The following error codes found in tests, but not in sources:")
print_ids(unused_covered_ids)
return 1
used_uncovered_ids = used_ids - covered_ids
if len(used_uncovered_ids) != 0:
print("The following error codes found in sources, but not in tests:")
print_ids(used_uncovered_ids)
print("\n\nPlease make sure to add appropriate tests.")
return 1
return 0
def main(argv):
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
check = False
fix = False
no_confirm = False
examine_coverage = False
next = False
opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
for opt, arg in opts:
if opt == '--check':
check = True
elif opt == "--fix":
fix = True
elif opt == '--no-confirm':
no_confirm = True
elif opt == '--examine-coverage':
examine_coverage = True
elif opt == '--next':
next = True
if [check, fix, examine_coverage, next].count(True) != 1:
print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next")
exit(1)
cwd = os.getcwd()
source_file_names = find_files(
cwd,
["libevmasm", "liblangutil", "libsolc", "libsolidity", "libsolutil", "libyul", "solc"],
[".h", ".cpp"]
)
used_ids = get_used_ids(source_file_names)
ok = True
for id in sorted(used_ids):
if len(id) != 4:
print(f"ID {id} length != 4")
ok = False
if id[0] == "0":
print(f"ID {id} starts with zero")
ok = False
if used_ids[id] > 1:
print(f"ID {id} appears {used_ids[id]} times")
ok = False
if examine_coverage:
if not ok:
print("Incorrect IDs have to be fixed before applying --examine-coverage")
res = examine_id_coverage(cwd, used_ids.keys())
exit(res)
random.seed()
if next:
if not ok:
print("Incorrect IDs have to be fixed before applying --next")
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
next_id = get_next_id(available_ids)
print(f"Next ID: {next_id}")
exit(0)
if ok:
print("No incorrect IDs found")
exit(0)
if check:
exit(1)
assert fix, "Unexpected state, should not come here without --fix"
if not no_confirm:
answer = input(
"\nDo you want to fix incorrect IDs?\n"
"Please commit current changes first, and review the results when the script finishes.\n"
"[Y/N]? "
)
while len(answer) == 0 or answer not in "YNyn":
answer = input("[Y/N]? ")
if answer not in "yY":
exit(1)
fix_ids(used_ids, source_file_names)
print("Fixing completed")
exit(2)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -1,175 +0,0 @@
#! /usr/bin/env python3
import random
import re
import os
import getopt
import sys
from os import path
ENCODING = "utf-8"
PATTERN = r"\b\d+_error\b"
def read_file(file_name):
content = None
try:
with open(file_name, "r", encoding=ENCODING) as f:
content = f.read()
finally:
if content == None:
print(f"Error reading: {file_name}")
return content
def write_file(file_name, content):
with open(file_name, "w", encoding=ENCODING) as f:
f.write(content)
def in_comment(source, pos):
slash_slash_pos = source.rfind("//", 0, pos)
lf_pos = source.rfind("\n", 0, pos)
if slash_slash_pos > lf_pos:
return True
slash_star_pos = source.rfind("/*", 0, pos)
star_slash_pos = source.rfind("*/", 0, pos)
return slash_star_pos > star_slash_pos
def find_ids_in_file(file_name, ids):
source = read_file(file_name)
for m in re.finditer(PATTERN, source):
if in_comment(source, m.start()):
continue
underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos]
if id in ids:
ids[id] += 1
else:
ids[id] = 1
def get_used_ids(file_names):
used_ids = {}
for file_name in file_names:
find_ids_in_file(file_name, used_ids)
return used_ids
def get_id(available_ids, used_ids):
while len(available_ids) > 0:
k = random.randrange(len(available_ids))
id = list(available_ids.keys())[k]
del available_ids[id]
if id not in used_ids:
return id
assert False, "Out of IDs"
def fix_ids_in_file(file_name, available_ids, used_ids):
source = read_file(file_name)
k = 0
destination = []
for m in re.finditer(PATTERN, source):
destination.extend(source[k:m.start()])
underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos]
# incorrect id or id has a duplicate somewhere
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
assert id in used_ids
new_id = get_id(available_ids, used_ids)
used_ids[id] -= 1
else:
new_id = id
destination.extend(new_id + "_error")
k = m.end()
destination.extend(source[k:])
destination = ''.join(destination)
if source != destination:
write_file(file_name, destination)
print(f"Fixed file: {file_name}")
def fix_ids(used_ids, file_names):
available_ids = {str(id): None for id in range(1000, 10000)}
for file_name in file_names:
fix_ids_in_file(file_name, available_ids, used_ids)
def find_source_files(top_dir):
"""Builds the list of .h and .cpp files in top_dir directory"""
source_file_names = []
dirs = ['libevmasm', 'liblangutil', 'libsolc', 'libsolidity', 'libsolutil', 'libyul', 'solc']
for dir in dirs:
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
for file_name in file_names:
_, ext = path.splitext(file_name)
if ext in [".h", ".cpp"]:
source_file_names.append(path.join(root, file_name))
return source_file_names
def main(argv):
check_only = False
noconfirm = False
opts, args = getopt.getopt(argv, "", ["check-only", "noconfirm"])
for opt, arg in opts:
if opt == '--check-only':
check_only = True
elif opt == '--noconfirm':
noconfirm = True
random.seed()
cwd = os.getcwd()
source_file_names = find_source_files(cwd)
used_ids = get_used_ids(source_file_names)
ok = True
for id in sorted(used_ids):
if len(id) != 4:
print(f"ID {id} length != 4")
ok = False
if id[0] == "0":
print(f"ID {id} starts with zero")
ok = False
if used_ids[id] > 1:
print(f"ID {id} appears {used_ids[id]} times")
ok = False
if ok:
print("No incorrect IDs found")
exit(0)
if check_only:
exit(1)
if not noconfirm:
answer = input(
"\nDo you want to fix incorrect IDs?\n"
"Please commit current changes first, and review the results when the script finishes.\n"
"[Y/N]? "
)
while len(answer) == 0 or answer not in "YNyn":
answer = input("[Y/N]? ")
if answer not in "yY":
exit(1)
fix_ids(used_ids, source_file_names)
print("Fixing completed")
exit(2)
if __name__ == "__main__":
main(sys.argv[1:])

39
scripts/get_nightly_version.sh Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
#------------------------------------------------------------------------------
# Prints the exact version string that would be used to describe a nightly
# build of the compiler.
#
# The documentation for solidity is hosted at:
#
# https://solidity.readthedocs.org
#
# ------------------------------------------------------------------------------
# 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/>
#
# (c) 2017 solidity contributors.
#------------------------------------------------------------------------------
set -e
script_dir="$(dirname "$0")"
solidity_version=$("${script_dir}/get_version.sh")
last_commit_timestamp=$(git log -1 --date=iso --format=%ad HEAD)
last_commit_date=$(date --date="$last_commit_timestamp" --utc +%Y.%-m.%-d)
last_commit_hash=$(git rev-parse --short=8 HEAD)
echo "v${solidity_version}-nightly.${last_commit_date}+commit.${last_commit_hash}"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Bash script to execute the Solidity tests. # Prints version of the Solidity compiler that the source code corresponds to.
# #
# The documentation for solidity is hosted at: # The documentation for solidity is hosted at:
# #

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Bash script to execute the Solidity tests. # Bash script to execute the Solidity tests using the emscripten binary.
# #
# The documentation for solidity is hosted at: # The documentation for solidity is hosted at:
# #

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Bash script to determine the percantage of tests that are compilable via Yul. # Bash script to determine the percentage of tests that are compilable via Yul.
# #
# Usage: # Usage:
# ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors] # ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors]

View File

@ -1242,7 +1242,7 @@ BOOST_AUTO_TEST_CASE(use_stack_optimization)
BOOST_CHECK(result["errors"][0]["severity"] == "error"); BOOST_CHECK(result["errors"][0]["severity"] == "error");
BOOST_REQUIRE(result["errors"][0]["message"].isString()); BOOST_REQUIRE(result["errors"][0]["message"].isString());
BOOST_CHECK(result["errors"][0]["message"].asString().find("Stack too deep when compiling inline assembly") != std::string::npos); BOOST_CHECK(result["errors"][0]["message"].asString().find("Stack too deep when compiling inline assembly") != std::string::npos);
BOOST_CHECK(result["errors"][0]["type"] == "YulException"); BOOST_CHECK(result["errors"][0]["type"] == "CompilerError");
} }
BOOST_AUTO_TEST_CASE(standard_output_selection_wildcard) BOOST_AUTO_TEST_CASE(standard_output_selection_wildcard)

View File

@ -6,5 +6,7 @@ contract C {
return (s.a, s.b); return (s.a, s.b);
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f((uint256,uint256)): 42, 23 -> 42, 23 // f((uint256,uint256)): 42, 23 -> 42, 23

View File

@ -0,0 +1,18 @@
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint256 a;
bool x;
}
function s() public returns(S memory)
{
return S({x: true, a: 8});
}
}
// ====
// compileViaYul: also
// ----
// s() -> 8, true

View File

@ -28,5 +28,7 @@ contract Test {
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// test() -> 1, 2, 3 // test() -> 1, 2, 3

View File

@ -38,5 +38,7 @@ contract Test {
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// test() -> 1, 2, 3, 4 // test() -> 1, 2, 3, 4

View File

@ -0,0 +1,24 @@
contract C {
struct I {
uint b;
uint c;
function(uint) external returns (uint) x;
}
struct S {
I a;
}
function o(uint a) external returns(uint) { return a+1; }
function f() external returns (uint) {
S memory s = S(I(1,2, this.o));
return s.a.x(1);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 2

View File

@ -0,0 +1,18 @@
contract C {
struct I {
uint b;
uint c;
}
struct S {
I a;
}
function f() external returns (uint) {
S memory s = S(I(1,2));
return s.a.b;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1

View File

@ -0,0 +1,14 @@
contract C {
struct S {
uint a;
}
function f() external returns (uint) {
S memory s = S(1);
return s.a;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1

View File

@ -27,6 +27,8 @@ contract test {
data.recursive[4].z = 9; data.recursive[4].z = 9;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// check() -> false // check() -> false
// set() -> // set() ->

View File

@ -0,0 +1,20 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
D d;
function f() public {
if (x < 10)
++x;
}
function g() public {
d.d();
assert(x < 10);
}
}
// ----
// Warning 4661: (200-214): Assertion violation happens here

View File

@ -0,0 +1,29 @@
pragma experimental SMTChecker;
abstract contract Crypto {
function hash(bytes32) external pure virtual returns (bytes32);
}
contract C {
address owner;
bytes32 sig_1;
bytes32 sig_2;
Crypto d;
constructor() public {
owner = msg.sender;
}
function f1(bytes32 _msg) public {
address prevOwner = owner;
sig_1 = d.hash(_msg);
sig_2 = d.hash(_msg);
assert(prevOwner == owner);
}
function inv() public view {
assert(sig_1 == sig_2);
}
}
// ----
// Warning 4661: (430-452): Assertion violation happens here

View File

@ -0,0 +1,31 @@
pragma experimental SMTChecker;
contract Crypto {
function hash(bytes32) external pure returns (bytes32) {
return bytes32(0);
}
}
contract C {
address owner;
bytes32 sig_1;
bytes32 sig_2;
Crypto d;
constructor() public {
owner = msg.sender;
}
function f1(bytes32 _msg) public {
address prevOwner = owner;
sig_1 = d.hash(_msg);
sig_2 = d.hash(_msg);
assert(prevOwner == owner);
}
function inv() public view {
assert(sig_1 == sig_2);
}
}
// ----
// Warning 4661: (438-460): Assertion violation happens here

View File

@ -0,0 +1,38 @@
pragma experimental SMTChecker;
contract State {
uint x;
function f() public returns (uint) {
if (x == 0) x = 1;
else if (x == 1) x = 2;
else if (x == 2) x = 0;
return x;
}
}
contract C {
address owner;
uint y;
uint z;
State s;
constructor() public {
owner = msg.sender;
}
function f() public {
address prevOwner = owner;
y = s.f();
z = s.f();
assert(prevOwner == owner);
}
function inv() public view {
// This is safe but external calls do not yet support the state
// of the called contract.
assert(owner == address(0) || y != z);
}
}
// ----
// Warning 5084: (551-561): Type conversion is not yet fully supported and might yield false positives.
// Warning 4661: (535-572): Assertion violation happens here

View File

@ -0,0 +1,32 @@
pragma experimental SMTChecker;
contract State {
uint x;
C c;
function f() public view returns (uint) {
return c.g();
}
}
contract C {
address owner;
uint y;
State s;
constructor() public {
owner = msg.sender;
}
function f() public view {
address prevOwner = owner;
uint z = s.f();
assert(z == y);
assert(prevOwner == owner);
}
function g() public view returns (uint) {
return y;
}
}
// ----
// Warning 4661: (306-320): Assertion violation happens here

View File

@ -0,0 +1,47 @@
pragma experimental SMTChecker;
contract Other {
C c;
function h() public {
c.setOwner(address(0));
}
}
contract State {
uint x;
Other o;
C c;
function f() public returns (uint) {
o.h();
return c.g();
}
}
contract C {
address owner;
uint y;
State s;
constructor() public {
owner = msg.sender;
}
function setOwner(address _owner) public {
owner = _owner;
}
function f() public {
address prevOwner = owner;
uint z = s.f();
assert(z == y);
assert(prevOwner == owner);
}
function g() public view returns (uint) {
return y;
}
}
// ----
// Warning 5084: (92-102): Type conversion is not yet fully supported and might yield false positives.
// Warning 4661: (459-473): Assertion violation happens here
// Warning 4661: (477-503): Assertion violation happens here

View File

@ -0,0 +1,39 @@
pragma experimental SMTChecker;
contract State {
uint x;
C c;
function f() public returns (uint) {
c.setOwner(address(0));
return c.g();
}
}
contract C {
address owner;
uint y;
State s;
constructor() public {
owner = msg.sender;
}
function setOwner(address _owner) public {
owner = _owner;
}
function f() public {
address prevOwner = owner;
uint z = s.f();
assert(z == y);
assert(prevOwner == owner);
}
function g() public view returns (uint) {
return y;
}
}
// ----
// Warning 5084: (116-126): Type conversion is not yet fully supported and might yield false positives.
// Warning 4661: (388-402): Assertion violation happens here
// Warning 4661: (406-432): Assertion violation happens here

View File

@ -0,0 +1,43 @@
pragma experimental SMTChecker;
contract State {
uint x;
function f() public returns (uint) {
if (x == 0) x = 1;
else if (x == 1) x = 2;
else if (x == 2) x = 0;
return x;
}
}
contract C {
address owner;
uint y;
uint z;
State s;
constructor() public {
owner = msg.sender;
}
function setOwner(address _owner) public {
owner = _owner;
}
function f() public {
address prevOwner = owner;
y = s.f();
z = s.f();
assert(prevOwner == owner);
}
function inv() public view {
// This is safe but external calls do not yet support the state
// of the called contract.
assert(owner == address(0) || y != z);
}
}
// ----
// Warning 4661: (442-468): Assertion violation happens here
// Warning 5084: (617-627): Type conversion is not yet fully supported and might yield false positives.
// Warning 4661: (601-638): Assertion violation happens here

View File

@ -0,0 +1,22 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
D d;
function inc() public {
++x;
}
function f() public {
d.d();
assert(x < 10);
}
}
// ----
// Warning 2661: (146-149): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning 4661: (189-203): Assertion violation happens here

View File

@ -0,0 +1,28 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
uint y;
D d;
function inc2() public {
if (y == 1)
x = 1;
}
function inc1() public {
if (x == 0)
y = 1;
}
function f() public {
uint oldX = x;
d.d();
assert(oldX == x);
}
}
// ----
// Warning 4661: (286-303): Assertion violation happens here

View File

@ -0,0 +1,18 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
D d;
function f() public {
if (x < 10)
++x;
}
function g() public {
d.d();
assert(x < 11);
}
}

View File

@ -0,0 +1,26 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
uint y;
D d;
function inc() public {
if (y == 1)
x = 1;
if (x == 0)
y = 1;
}
function f() public {
uint oldX = x;
d.d();
assert(oldX == x);
}
}
// ----
// Warning 4661: (256-273): Assertion violation happens here

View File

@ -0,0 +1,28 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
D d;
bool lock;
modifier mutex {
require(!lock);
lock = true;
_;
lock = false;
}
function set(uint _x) mutex public {
x = _x;
}
function f() mutex public {
uint y = x;
d.d();
assert(y == x);
}
}

View File

@ -0,0 +1,30 @@
pragma experimental SMTChecker;
abstract contract D {
function d() external virtual;
}
contract C {
uint x;
D d;
bool lock;
modifier mutex {
require(!lock);
lock = true;
_;
lock = false;
}
function set(uint _x) mutex public {
x = _x;
}
function f() public {
uint y = x;
d.d();
assert(y == x);
}
}
// ----
// Warning 4661: (307-321): Assertion violation happens here

View File

@ -17,4 +17,3 @@ contract C
} }
} }
// ---- // ----
// Warning 4661: (257-271): Assertion violation happens here

View File

@ -18,4 +18,3 @@ contract C
} }
} }
// ---- // ----
// Warning 4661: (355-379): Assertion violation happens here

View File

@ -14,4 +14,6 @@ contract C
} }
} }
// ---- // ----
// Warning 1218: (296-309): Error trying to invoke SMT solver.
// Warning 2661: (176-181): Overflow (resulting value larger than 2**256 - 1) happens here // Warning 2661: (176-181): Overflow (resulting value larger than 2**256 - 1) happens here
// Warning 4661: (296-309): Assertion violation happens here

View File

@ -20,6 +20,3 @@ contract C
// ---- // ----
// Warning 2072: (224-240): Unused local variable. // Warning 2072: (224-240): Unused local variable.
// Warning 4661: (266-281): Assertion violation happens here // Warning 4661: (266-281): Assertion violation happens here
// Warning 4661: (285-299): Assertion violation happens here
// Warning 4661: (303-322): Assertion violation happens here
// Warning 4661: (326-350): Assertion violation happens here

View File

@ -0,0 +1,16 @@
contract C {
function f() pure external {
assembly {
let s := returndatasize()
returndatacopy(0, 0, s)
}
}
function g() view external returns (uint ret) {
assembly {
ret := staticcall(0, gas(), 0, 0, 0, 0)
}
}
}
// ====
// EVMVersion: >=byzantium
// ----

View File

@ -0,0 +1,21 @@
contract C {
function f() pure external {
assembly {
let s := returndatasize()
returndatacopy(0, 0, s)
}
}
function g() view external returns (uint ret) {
assembly {
ret := staticcall(0, gas(), 0, 0, 0, 0)
}
}
}
// ====
// EVMVersion: =homestead
// ----
// TypeError 7756: (86-100): The "returndatasize" instruction is only available for Byzantium-compatible VMs (you are currently compiling for "homestead").
// DeclarationError 3812: (77-102): Variable count mismatch: 1 variables and 0 values.
// TypeError 7756: (115-129): The "returndatacopy" instruction is only available for Byzantium-compatible VMs (you are currently compiling for "homestead").
// TypeError 1503: (245-255): The "staticcall" instruction is only available for Byzantium-compatible VMs (you are currently compiling for "homestead").
// DeclarationError 8678: (238-277): Variable count does not match number of values (1 vs. 0)

View File

@ -0,0 +1,17 @@
contract C {
function f() view external returns (uint ret) {
assembly {
ret := shl(gas(), 5)
ret := shr(ret, 2)
ret := sar(ret, 2)
}
}
function g() external returns (address ret) {
assembly {
ret := create2(0, 0, 0, 0)
}
}
}
// ====
// EVMVersion: >=constantinople
// ----

View File

@ -0,0 +1,25 @@
contract C {
function f() view external returns (uint ret) {
assembly {
ret := shl(gas(), 5)
ret := shr(ret, 2)
ret := sar(ret, 2)
}
}
function g() external returns (address ret) {
assembly {
ret := create2(0, 0, 0, 0)
}
}
}
// ====
// EVMVersion: =byzantium
// ----
// TypeError 6612: (103-106): The "shl" instruction is only available for Constantinople-compatible VMs (you are currently compiling for "byzantium").
// DeclarationError 8678: (96-116): Variable count does not match number of values (1 vs. 0)
// TypeError 6612: (136-139): The "shr" instruction is only available for Constantinople-compatible VMs (you are currently compiling for "byzantium").
// DeclarationError 8678: (129-147): Variable count does not match number of values (1 vs. 0)
// TypeError 6612: (167-170): The "sar" instruction is only available for Constantinople-compatible VMs (you are currently compiling for "byzantium").
// DeclarationError 8678: (160-178): Variable count does not match number of values (1 vs. 0)
// TypeError 6166: (283-290): The "create2" instruction is only available for Constantinople-compatible VMs (you are currently compiling for "byzantium").
// DeclarationError 8678: (276-302): Variable count does not match number of values (1 vs. 0)

View File

@ -13,7 +13,7 @@ contract C {
// ==== // ====
// EVMVersion: =petersburg // EVMVersion: =petersburg
// ---- // ----
// TypeError 7079: (101-108): The "chainid" instruction is only available for Istanbul-compatible VMs (you are currently compiling for "petersburg"). // TypeError 1561: (101-108): The "chainid" instruction is only available for Istanbul-compatible VMs (you are currently compiling for "petersburg").
// DeclarationError 8678: (95-110): Variable count does not match number of values (1 vs. 0) // DeclarationError 8678: (95-110): Variable count does not match number of values (1 vs. 0)
// TypeError 7079: (215-226): The "selfbalance" instruction is only available for Istanbul-compatible VMs (you are currently compiling for "petersburg"). // TypeError 3672: (215-226): The "selfbalance" instruction is only available for Istanbul-compatible VMs (you are currently compiling for "petersburg").
// DeclarationError 8678: (209-228): Variable count does not match number of values (1 vs. 0) // DeclarationError 8678: (209-228): Variable count does not match number of values (1 vs. 0)

View File

@ -0,0 +1,10 @@
contract C {
function f() public pure {
assembly {
pop(linkersymbol("contract/library.sol:L"))
}
}
}
// ----
// DeclarationError 4619: (67-79): Function not found.
// TypeError 3950: (67-105): Expected expression to evaluate to one value, but got 0 values instead.

View File

@ -0,0 +1,10 @@
contract C {
function f() public pure {
assembly {
function linkersymbol(a) {}
linkersymbol("contract/library.sol:L")
}
}
}
// ----

View File

@ -0,0 +1,5 @@
contract C {
mapping(uint => uint[2**100]) x;
}
// ----
// Warning 7325: (17-48): Type uint256[1267650600228229401496703205376] has large size and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.

View File

@ -0,0 +1,4 @@
contract C {
uint[200][200][2**30][][2**30] x;
}
// ----

View File

@ -0,0 +1,65 @@
contract C {
struct P { uint256[2**63] x; }
struct S0 {
P[2**62] x;
P y;
}
S0 s0;
struct S1 {
P x;
P[2**62] y;
}
S1 s1;
struct S2 {
mapping(uint => P[2**62]) x;
mapping(uint => P[2**62]) y;
mapping(uint => S2) z;
}
S2 s2;
struct Q0
{
uint[1][][2**65] x;
uint[2**65][][1] y;
uint[][2**65] z;
uint[2**65][] t;
}
Q0 q0;
struct Q1
{
uint[1][][2**65] x;
}
Q1 q1;
struct Q2
{
uint[2**65][][1] y;
}
Q2 q2;
struct Q3
{
uint[][2**65] z;
}
Q3 q3;
struct Q4
{
uint[2**65][] t;
}
Q4 q4;
}
// ----
// Warning 3408: (108-113): Variable covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (175-180): Variable 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: (314-319): Type C.P[4611686018427387904] has large size and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (458-463): Variable 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: (458-463): Type uint256[36893488147419103232] has large size and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (524-529): Variable 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: (590-595): Type uint256[36893488147419103232] has large size and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.
// Warning 3408: (653-658): Variable 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: (716-721): Type uint256[36893488147419103232] has large size and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.

View File

@ -1,5 +0,0 @@
contract C {
uint[200][200][2**30][][2**30] x;
}
// ----
// Warning 3408: (17-49): Variable covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.

View File

@ -1,5 +0,0 @@
contract C {
mapping(uint => uint[2**100]) x;
}
// ----
// Warning 3408: (17-48): Variable covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction.

View File

@ -3,4 +3,4 @@ abstract contract C {
function vote(uint id) public {} function vote(uint id) public {}
} }
// ---- // ----
// DocstringParsingError 9222: End of tag @param not found // DocstringParsingError 3335: No param name given

View File

@ -255,6 +255,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
else if (m_optimizerStep == "ssaTransform") else if (m_optimizerStep == "ssaTransform")
{ {
disambiguate(); disambiguate();
ForLoopInitRewriter::run(*m_context, *m_ast);
SSATransform::run(*m_context, *m_ast); SSATransform::run(*m_context, *m_ast);
} }
else if (m_optimizerStep == "redundantAssignEliminator") else if (m_optimizerStep == "redundantAssignEliminator")

View File

@ -0,0 +1,13 @@
{
let x := calldataload(0)
let a := mload(x)
let b := mload(x)
sstore(a, b)
}
// ----
// step: loadResolver
//
// {
// let a := mload(calldataload(0))
// sstore(a, a)
// }

View File

@ -0,0 +1,16 @@
{
let x := calldataload(0)
let a := mload(x)
x := 7
let b := mload(x)
sstore(a, b)
}
// ----
// step: loadResolver
//
// {
// let x := calldataload(0)
// let a := mload(x)
// x := 7
// sstore(a, mload(x))
// }

View File

@ -0,0 +1,16 @@
{
let x := calldataload(0)
let a := mload(x)
a := 7
let b := mload(x)
sstore(a, b)
}
// ----
// step: loadResolver
//
// {
// let x := calldataload(0)
// let a := mload(x)
// a := 7
// sstore(a, mload(x))
// }

View File

@ -0,0 +1,19 @@
{
let x := mload(calldataload(0))
if calldataload(1) {
mstore(add(calldataload(0), 0x20), 1)
}
let t := mload(add(calldataload(0), 0x20))
let q := mload(calldataload(0))
sstore(t, q)
}
// ----
// step: loadResolver
//
// {
// let _2 := calldataload(0)
// let x := mload(_2)
// let _3 := 1
// if calldataload(_3) { mstore(add(_2, 0x20), _3) }
// sstore(mload(add(_2, 0x20)), x)
// }

View File

@ -0,0 +1,22 @@
{
let b := mload(2)
if calldataload(1) {
mstore(2, 7)
// Re-writing the old value, should allow to eliminate the load below.
mstore(2, b)
}
sstore(0, mload(2))
}
// ----
// step: loadResolver
//
// {
// let _1 := 2
// let b := mload(_1)
// if calldataload(1)
// {
// mstore(_1, 7)
// mstore(_1, b)
// }
// sstore(0, b)
// }

View File

@ -0,0 +1,19 @@
{
let b := mload(2)
sstore(0, b)
if calldataload(1) {
mstore(2, 7)
}
sstore(0, mload(2))
}
// ----
// step: loadResolver
//
// {
// let _1 := 2
// let b := mload(_1)
// let _2 := 0
// sstore(_2, b)
// if calldataload(1) { mstore(_1, 7) }
// sstore(_2, mload(_1))
// }

View File

@ -0,0 +1,15 @@
{
let x := calldataload(0)
x := mload(x)
let y := mload(x)
sstore(0, y)
}
// ----
// step: loadResolver
//
// {
// let _1 := 0
// let x := calldataload(_1)
// x := mload(x)
// sstore(_1, mload(x))
// }

View File

@ -0,0 +1,30 @@
{
let x := calldataload(0)
let len := sload(x)
let sum
for { let i := 0} lt(i, sload(x)) { i := add(i, 1) } {
let p := add(x, add(i, 1))
if gt(p, sload(x)) { revert(0, 0) }
sum := add(sum, sload(p))
}
mstore(0, sum)
return(0, 0x20)
}
// ----
// step: loadResolver
//
// {
// let _1 := 0
// let x := calldataload(_1)
// let len := sload(x)
// let sum
// let i := _1
// for { } lt(i, len) { i := add(i, 1) }
// {
// let p := add(add(x, i), 1)
// if gt(p, len) { revert(_1, _1) }
// sum := add(sum, sload(p))
// }
// mstore(_1, sum)
// return(_1, 0x20)
// }

View File

@ -0,0 +1,33 @@
{
for { let x := 0 } 1 { x := 2 } {
for { let y := 0 } 1 { y := 6 } {
}
}
}
// ----
// step: ssaTransform
//
// {
// let x_1 := 0
// let x := x_1
// for { }
// 1
// {
// let x_7 := x
// let x_2 := 2
// x := x_2
// }
// {
// let x_5 := x
// let y_3 := 0
// let y := y_3
// for { }
// 1
// {
// let y_6 := y
// let y_4 := 6
// y := y_4
// }
// { }
// }
// }

View File

@ -12,7 +12,8 @@
// { // {
// let a_1 := mload(0) // let a_1 := mload(0)
// let a := a_1 // let a := a_1
// for { mstore(0, a_1) } // mstore(0, a_1)
// for { }
// a // a
// { // {
// let a_4 := a // let a_4 := a

View File

@ -12,19 +12,9 @@
// { // {
// let a_1 := mload(0) // let a_1 := mload(0)
// let a := a_1 // let a := a_1
// for { // let a_2 := add(a_1, 3)
// let a_2 := add(a_1, 3) // a := a_2
// a := a_2 // for { } a_2 { mstore(0, a_2) }
// } // { mstore(0, a_2) }
// a // mstore(0, a_2)
// {
// let a_4 := a
// mstore(0, a_4)
// }
// {
// let a_3 := a
// mstore(0, a_3)
// }
// let a_5 := a
// mstore(0, a_5)
// } // }

View File

@ -12,7 +12,8 @@
// { // {
// let a_1 := mload(0) // let a_1 := mload(0)
// let a := a_1 // let a := a_1
// for { mstore(0, a_1) } // mstore(0, a_1)
// for { }
// a // a
// { // {
// let a_4 := a // let a_4 := a

View File

@ -32,10 +32,9 @@
// a := a_4 // a := a_4
// } // }
// let a_10 := a // let a_10 := a
// for { // let a_5 := add(a_10, 3)
// let a_5 := add(a_10, 3) // a := a_5
// a := a_5 // for { }
// }
// a // a
// { // {
// let a_12 := a // let a_12 := a

View File

@ -0,0 +1,6 @@
{
function _...($..) {}
let a...
_...(a...)
}
// ----

View File

@ -0,0 +1,4 @@
{
function x..y() {}
}
// ----

View File

@ -0,0 +1,4 @@
{
function x(a..b) {}
}
// ----

View File

@ -0,0 +1,4 @@
{
function x() -> a..b {}
}
// ----

View File

@ -0,0 +1,4 @@
{
let a..b := 1
}
// ----

View File

@ -0,0 +1,4 @@
{
function x...y() {}
}
// ----

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