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
- run:
name: Check for error codes
command: ./scripts/fix_error_ids.py --check-only
command: ./scripts/error_codes.py --check
chk_pylint:
docker:

View File

@ -27,6 +27,8 @@ Bugfixes:
Language Features:
* 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:
@ -34,6 +36,9 @@ Compiler Features:
* NatSpec: Inherit tags from unique base if derived function does not provide any.
* Commandline Interface: Prevent some incompatible commandline options from being used together.
* 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:
* 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
like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should
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
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

View File

@ -38,6 +38,7 @@ class Error;
using ErrorList = std::vector<std::shared_ptr<Error const>>;
struct CompilerError: virtual util::Exception {};
struct StackTooDeepError: virtual CompilerError {};
struct InternalCompilerError: virtual util::Exception {};
struct FatalError: 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.
* Suffix _error helps to find them in the sources.
* 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.
*/
struct ErrorId

View File

@ -48,9 +48,9 @@ public:
// Z3 "basic resources" limit.
// 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.
static int const resourceLimit = 40000000;
static int const resourceLimit = 20000000;
private:
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())
{
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(
3408_error,
_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 "
"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;
}
@ -339,30 +348,43 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
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())
{
case Type::Category::Array:
{
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:
{
auto const& t = dynamic_cast<StructType const&>(_type);
bigint size = 1;
if (!_structsSeen.count(&t.structDefinition()))
{
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
size += structureSizeEstimate(*m.type, _structsSeen);
}
if (_structsSeen.count(&t.structDefinition()))
return size;
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
size += structureSizeEstimate(*m.type, _structsSeen, _oversizedSubTypes);
_structsSeen.erase(&t.structDefinition());
return size;
}
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:
break;

View File

@ -73,8 +73,22 @@ private:
bool visit(BinaryOperation const& _operation) 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.
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;

View File

@ -226,8 +226,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
else
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>...
solAssert(
assertThrow(
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
StackTooDeepError,
"Stack too deep, try removing local variables."
);
// fetch target storage reference

View File

@ -404,7 +404,7 @@ void CompilerContext::appendInlineAssembly(
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_identifier.location) <<
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
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
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
{
@ -507,8 +511,9 @@ void CompilerUtils::encodeToMemory(
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{
// copy tail pointer (=mem_end - mem_start) to memory
solAssert(
assertThrow(
(2 + dynPointers) <= 16,
StackTooDeepError,
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
);
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
if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_variable.location()) <<
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)
{
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)
m_context << dupInstruction(_stackDepth);
}
@ -1321,14 +1330,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
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)
m_context << swapInstruction(_items - i);
}
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)
m_context << swapInstruction(i);
}

View File

@ -634,7 +634,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_function.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
@ -798,7 +798,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
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;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
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), "");
if (retSizeOnStack > 15)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_varDecl.location()) <<
errinfo_comment("Stack too deep.")
);
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
{
if (itemSize + lvalueSize > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_assignment.location()) <<
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);
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
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;
if (stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
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, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
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
<#member>
mstore(offset, <zeroValue>())
@ -1661,10 +1678,9 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
templ("allocStruct", allocateMemoryStructFunction(_type));
TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)

View File

@ -286,8 +286,13 @@ public:
/// signature: (length) -> memPtr
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.
/// signature: (members) -> memPtr
/// signature: () -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @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)
{
solUnimplementedAssert(
_functionCall.annotation().kind == FunctionCallKind::FunctionCall ||
_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
_functionCall.annotation().kind != FunctionCallKind::Unset,
"This type of function call is not yet implemented"
);
Type const& funcType = type(_functionCall.expression());
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");
define(_functionCall, *_functionCall.arguments().front());
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();
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))]);
}
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());
if (memberAccess)
{

View File

@ -197,8 +197,11 @@ void CHC::endVisit(ContractDefinition const& _contract)
bool CHC::visit(FunctionDefinition const& _function)
{
if (!shouldVisit(_function))
if (!_function.isImplemented())
{
connectBlocks(genesis(), summary(_function));
return false;
}
// This is the case for base constructor inlining.
if (m_currentFunction)
@ -243,7 +246,7 @@ bool CHC::visit(FunctionDefinition const& _function)
void CHC::endVisit(FunctionDefinition const& _function)
{
if (!shouldVisit(_function))
if (!_function.isImplemented())
return;
// This is the case for base constructor inlining.
@ -474,11 +477,14 @@ void CHC::endVisit(FunctionCall const& _funCall)
internalFunctionCall(_funCall);
break;
case FunctionType::Kind::External:
case FunctionType::Kind::BareStaticCall:
externalFunctionCall(_funCall);
SMTEncoder::endVisit(_funCall);
break;
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Creation:
case FunctionType::Kind::KECCAK256:
case FunctionType::Kind::ECRecover:
@ -574,6 +580,35 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
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&)
{
/// 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(
smt::SymbolicFunctionVariable const& _block,
vector<smtutil::Expression> const* _arguments
@ -710,10 +740,14 @@ smtutil::SortPointer CHC::constructorSort()
smtutil::SortPointer CHC::interfaceSort()
{
return make_shared<smtutil::FunctionSort>(
m_stateSorts,
smtutil::SortProvider::boolSort
);
solAssert(m_currentContract, "");
return interfaceSort(*m_currentContract);
}
smtutil::SortPointer CHC::nondetInterfaceSort()
{
solAssert(m_currentContract, "");
return nondetInterfaceSort(*m_currentContract);
}
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()
{
return make_shared<smtutil::FunctionSort>(
@ -778,7 +821,12 @@ smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, Contr
auto inputSorts = applyMap(_function.parameters(), smtSort);
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
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
);
}
@ -802,11 +850,48 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
{
string suffix = base->name() + "_" + to_string(base->id());
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
m_nondetInterfaces[base] = createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix);
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
if (!m_context.knownVariable(*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 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));
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()};
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 += 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); });
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)
@ -893,13 +985,18 @@ vector<smtutil::Expression> CHC::initialStateVariables()
return stateVariablesAtIndex(0);
}
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index)
vector<smtutil::Expression> CHC::initialStateVariables(ContractDefinition const& _contract)
{
solAssert(m_currentContract, "");
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
return stateVariablesAtIndex(0, _contract);
}
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(
stateVariablesIncludingInheritedAndPrivate(_contract),
@ -910,7 +1007,12 @@ vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, Contract
vector<smtutil::Expression> CHC::currentStateVariables()
{
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()
@ -978,22 +1080,28 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
m_error.increaseIndex();
vector<smtutil::Expression> args{m_error.currentValue()};
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);
for (auto const& var: m_stateVariables)
m_context.variable(*var)->increaseIndex();
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
if (!otherContract)
for (auto const& var: m_stateVariables)
m_context.variable(*var)->increaseIndex();
args += otherContract ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
auto const& returnParams = function->returnParameters();
for (auto param: returnParams)
if (m_context.knownVariable(*param))
m_context.variable(*param)->increaseIndex();
for (auto var: function->parameters() + function->returnParameters())
{
if (m_context.knownVariable(*var))
m_context.variable(*var)->increaseIndex();
else
createVariable(*param);
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
createVariable(*var);
args.push_back(currentValue(*var));
}
if (contract->isLibrary())
if (otherContract)
return (*m_summaries.at(contract).at(function))(args);
solAssert(m_currentContract, "");

View File

@ -77,6 +77,7 @@ private:
void visitAssert(FunctionCall const& _funCall);
void internalFunctionCall(FunctionCall const& _funCall);
void externalFunctionCall(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall);
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
//@}
@ -95,7 +96,6 @@ private:
void resetContractAnalysis();
void eraseKnowledge();
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);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
@ -106,7 +106,9 @@ private:
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
smtutil::SortPointer constructorSort();
smtutil::SortPointer interfaceSort();
smtutil::SortPointer nondetInterfaceSort();
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
smtutil::SortPointer arity0FunctionSort();
smtutil::SortPointer sort(FunctionDefinition const& _function);
smtutil::SortPointer sort(ASTNode const* _block);
@ -149,10 +151,12 @@ private:
/// @returns the symbolic values of the state variables at the beginning
/// of the current transaction.
std::vector<smtutil::Expression> initialStateVariables();
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index);
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract);
std::vector<smtutil::Expression> initialStateVariables(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.
std::vector<smtutil::Expression> currentStateVariables();
std::vector<smtutil::Expression> currentStateVariables(ContractDefinition const& _contract);
/// @returns the current symbolic values of the current function's
/// input and output parameters.
@ -173,6 +177,7 @@ private:
smtutil::Expression summary(ContractDefinition const& _contract);
/// @returns a predicate that defines a function summary.
smtutil::Expression summary(FunctionDefinition const& _function);
smtutil::Expression summary(FunctionDefinition const& _function, ContractDefinition const& _contract);
//@}
/// Solver related.
@ -212,6 +217,12 @@ private:
/// Single entry block for all functions.
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.
/// Single error block for all assertions.
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
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end)
{
m_errorReporter->docstringParsingError(
9222_error,
"End of tag " + string(tagPos, tagNameEndPos) + " not found"
);
break;
}
currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos));
auto tagName = string(tagPos + 1, tagNameEndPos);
auto tagDataPos = (tagNameEndPos != end) ? tagNameEndPos + 1 : tagNameEndPos;
currPos = parseDocTag(tagDataPos, end, tagName);
}
else if (!!m_lastTag) // continuation of the previous tag
currPos = appendDocTag(currPos, end);
currPos = parseDocTagLine(currPos, end, true);
else if (currPos != end)
{
// 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, "");
auto nlPos = find(_pos, _end, '\n');
if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
if (_appending && _pos != _end && *_pos != ' ' && *_pos != '\t')
m_lastTag->content += " ";
else if (!_appending)
_pos = skipWhitespace(_pos, _end);
@ -179,13 +172,7 @@ DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string
}
}
else
return appendDocTag(_pos, _end);
}
DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end)
{
solAssert(!!m_lastTag, "");
return parseDocTagLine(_pos, _end, true);
return parseDocTagLine(_pos, _end, true);
}
void DocStringParser::newTag(string const& _tagName)

View File

@ -50,7 +50,6 @@ private:
iter parseDocTagParam(iter _pos, iter _end);
iter appendDocTagParam(iter _pos, iter _end);
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
/// after the 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)
needsLiteralArguments = &f->literalArguments.value();
warnOnInstructions(_funCall);
validateInstructions(_funCall);
}
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
[&](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.");
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));
if (builtin && builtin->instruction.has_value())
return warnOnInstructions(builtin->instruction.value(), _location);
return validateInstructions(builtin->instruction.value(), _location);
else
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
// or all not available.
@ -558,9 +558,9 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
// Similarly we assume bitwise shifting and create2 go together.
yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
auto errorForVM = [&](string const& vmKindMessage) {
auto errorForVM = [&](ErrorId _errorId, string const& vmKindMessage) {
m_errorReporter.typeError(
7079_error,
_errorId,
_location,
"The \"" +
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::RETURNDATASIZE
) && !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())
errorForVM("only available for Byzantium-compatible");
errorForVM(1503_error, "only available for Byzantium-compatible");
else if ((
_instr == evmasm::Instruction::SHL ||
_instr == evmasm::Instruction::SHR ||
_instr == evmasm::Instruction::SAR
) && !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())
errorForVM("only available for Constantinople-compatible");
errorForVM(6166_error, "only available for Constantinople-compatible");
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())
errorForVM("only available for Istanbul-compatible");
errorForVM(1561_error, "only available for Istanbul-compatible");
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)
{
m_errorReporter.error(
2450_error,
Error::Type::SyntaxError,
@ -603,7 +602,8 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
"PC instruction is a low-level EVM feature. "
"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 (
_instr == evmasm::Instruction::JUMP ||
_instr == evmasm::Instruction::JUMPI ||

View File

@ -110,12 +110,12 @@ private:
Scope& scope(Block const* _block);
void expectValidType(YulString _type, 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;

View File

@ -21,7 +21,7 @@
#include <libyul/AsmJsonConverter.h>
#include <libyul/AsmData.h>
#include <liblangutil/Exceptions.h>
#include <libyul/Exceptions.h>
#include <libsolutil/CommonData.h>
using namespace std;
@ -38,7 +38,7 @@ Json::Value AsmJsonConverter::operator()(Block 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");
ret["name"] = _node.name.str();
ret["type"] = _node.type.str();
@ -51,7 +51,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
switch (_node.kind)
{
case LiteralKind::Number:
solAssert(
yulAssert(
util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()),
"Invalid number literal"
);
@ -71,7 +71,7 @@ Json::Value AsmJsonConverter::operator()(Literal 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");
ret["name"] = _node.name.str();
return ret;
@ -79,7 +79,7 @@ Json::Value AsmJsonConverter::operator()(Identifier 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");
for (auto const& var: _node.variableNames)
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
{
solAssert(!_node.name.empty(), "Invalid function name.");
yulAssert(!_node.name.empty(), "Invalid function name.");
Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition");
ret["name"] = _node.name.str();
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
{
solAssert(_node.condition, "Invalid if condition.");
yulAssert(_node.condition, "Invalid if condition.");
Json::Value ret = createAstNode(_node.location, "YulIf");
ret["condition"] = std::visit(*this, *_node.condition);
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
{
solAssert(_node.expression, "Invalid expression pointer.");
yulAssert(_node.expression, "Invalid expression pointer.");
Json::Value ret = createAstNode(_node.location, "YulSwitch");
ret["expression"] = std::visit(*this, *_node.expression);
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
{
solAssert(_node.condition, "Invalid for loop condition.");
yulAssert(_node.condition, "Invalid for loop condition.");
Json::Value ret = createAstNode(_node.location, "YulForLoop");
ret["pre"] = (*this)(_node.pre);
ret["condition"] = std::visit(*this, *_node.condition);
ret["post"] = (*this)(_node.post);
ret["body"] = (*this)(_node.body);
return ret;
}
}
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};
for (auto const& var: _vec)
ret.append(std::visit(*this, var));
return ret;
}

View File

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

View File

@ -218,8 +218,9 @@ void CodeGenerator::assemble(
}
catch (StackTooDeepError const& _e)
{
yulAssert(
assertThrow(
false,
langutil::StackTooDeepError,
"Stack too deep when compiling inline assembly" +
(_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
)
{
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
yulAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
YulString name{std::move(_name)};
BuiltinFunctionForEVM f;
@ -124,19 +124,18 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
)
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)
{
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}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
@ -207,7 +206,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
BuiltinContext&,
std::function<void(Expression const&)> _visitExpression
) {
solAssert(_call.arguments.size() == 2, "");
yulAssert(_call.arguments.size() == 2, "");
_visitExpression(_call.arguments[1]);
_assembly.setSourceLocation(_call.location);
@ -227,7 +226,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
BuiltinContext&,
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());
}
));

View File

@ -253,6 +253,21 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
// assignment to slot contents denoted by "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)
@ -401,3 +416,25 @@ std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore(
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.
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(
evmasm::Instruction _store,
ExpressionStatement const& _statement
) 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;
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
/// 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)
{
(*this)(_for.pre);
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
Assignments assignments;
assignments(_for.body);
@ -357,11 +357,7 @@ void PropagateValues::operator()(Assignment& _assignment)
void PropagateValues::operator()(ForLoop& _for)
{
// This will clear the current value in case of a reassignment inside the
// 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);
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
Assignments assignments;
assignments(_for.body);

View File

@ -85,7 +85,7 @@ class NameDispenser;
*
* TODO Which transforms are required to keep this idempotent?
*
* Prerequisite: Disambiguator.
* Prerequisite: Disambiguator, ForLoopInitRewriter.
*/
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
#------------------------------------------------------------------------------
# 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:
#

View File

@ -1,7 +1,7 @@
#!/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:
#

View File

@ -1,7 +1,7 @@
#!/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:
# ./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_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]["type"] == "YulException");
BOOST_CHECK(result["errors"][0]["type"] == "CompilerError");
}
BOOST_AUTO_TEST_CASE(standard_output_selection_wildcard)

View File

@ -6,5 +6,7 @@ contract C {
return (s.a, s.b);
}
}
// ====
// compileViaYul: also
// ----
// 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

View File

@ -38,5 +38,7 @@ contract Test {
}
}
// ====
// compileViaYul: also
// ----
// 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;
}
}
// ====
// compileViaYul: also
// ----
// check() -> false
// 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 4661: (296-309): Assertion violation happens here

View File

@ -20,6 +20,3 @@ contract C
// ----
// Warning 2072: (224-240): Unused local variable.
// 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
// ----
// 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)
// 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)

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 {}
}
// ----
// 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")
{
disambiguate();
ForLoopInitRewriter::run(*m_context, *m_ast);
SSATransform::run(*m_context, *m_ast);
}
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 := a_1
// for { mstore(0, a_1) }
// mstore(0, a_1)
// for { }
// a
// {
// let a_4 := a

View File

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

View File

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

View File

@ -32,10 +32,9 @@
// a := a_4
// }
// let a_10 := a
// for {
// let a_5 := add(a_10, 3)
// a := a_5
// }
// let a_5 := add(a_10, 3)
// a := a_5
// for { }
// 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