mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
commit
ab68406006
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.")
|
||||
);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.")
|
||||
);
|
||||
|
@ -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.")
|
||||
);
|
||||
|
@ -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.")
|
||||
);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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, "");
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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 ||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() : ".")
|
||||
);
|
||||
|
@ -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());
|
||||
}
|
||||
));
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
261
scripts/error_codes.py
Executable 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:])
|
@ -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
39
scripts/get_nightly_version.sh
Executable 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}"
|
@ -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:
|
||||
#
|
||||
|
@ -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:
|
||||
#
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -6,5 +6,7 @@ contract C {
|
||||
return (s.a, s.b);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f((uint256,uint256)): 42, 23 -> 42, 23
|
||||
|
@ -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
|
@ -28,5 +28,7 @@ contract Test {
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 1, 2, 3
|
||||
|
@ -38,5 +38,7 @@ contract Test {
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 1, 2, 3, 4
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -27,6 +27,8 @@ contract test {
|
||||
data.recursive[4].z = 9;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// check() -> false
|
||||
// set() ->
|
||||
|
20
test/libsolidity/smtCheckerTests/external_calls/external.sol
Normal file
20
test/libsolidity/smtCheckerTests/external_calls/external.sol
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
28
test/libsolidity/smtCheckerTests/external_calls/mutex.sol
Normal file
28
test/libsolidity/smtCheckerTests/external_calls/mutex.sol
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
@ -17,4 +17,3 @@ contract C
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 4661: (257-271): Assertion violation happens here
|
||||
|
@ -18,4 +18,3 @@ contract C
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 4661: (355-379): Assertion violation happens here
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
// ----
|
@ -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)
|
@ -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
|
||||
// ----
|
@ -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)
|
@ -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)
|
@ -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.
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
assembly {
|
||||
function linkersymbol(a) {}
|
||||
|
||||
linkersymbol("contract/library.sol:L")
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
@ -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.
|
@ -0,0 +1,4 @@
|
||||
contract C {
|
||||
uint[200][200][2**30][][2**30] x;
|
||||
}
|
||||
// ----
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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
|
||||
|
@ -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")
|
||||
|
13
test/libyul/yulOptimizerTests/loadResolver/double_mload.yul
Normal file
13
test/libyul/yulOptimizerTests/loadResolver/double_mload.yul
Normal 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)
|
||||
// }
|
@ -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))
|
||||
// }
|
@ -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))
|
||||
// }
|
@ -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)
|
||||
// }
|
@ -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)
|
||||
// }
|
@ -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))
|
||||
// }
|
15
test/libyul/yulOptimizerTests/loadResolver/mload_self.yul
Normal file
15
test/libyul/yulOptimizerTests/loadResolver/mload_self.yul
Normal 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))
|
||||
// }
|
@ -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)
|
||||
// }
|
@ -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
|
||||
// }
|
||||
// { }
|
||||
// }
|
||||
// }
|
@ -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
|
||||
|
@ -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)
|
||||
// }
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
6
test/libyul/yulSyntaxTests/are_we_perl_yet.yul
Normal file
6
test/libyul/yulSyntaxTests/are_we_perl_yet.yul
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
function _...($..) {}
|
||||
let a...
|
||||
_...(a...)
|
||||
}
|
||||
// ----
|
4
test/libyul/yulSyntaxTests/dot_consecutive_function.yul
Normal file
4
test/libyul/yulSyntaxTests/dot_consecutive_function.yul
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
function x..y() {}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
function x(a..b) {}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
function x() -> a..b {}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
let a..b := 1
|
||||
}
|
||||
// ----
|
4
test/libyul/yulSyntaxTests/dot_ellipse_function.yul
Normal file
4
test/libyul/yulSyntaxTests/dot_ellipse_function.yul
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
function x...y() {}
|
||||
}
|
||||
// ----
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user