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

This commit is contained in:
chriseth 2020-07-22 15:26:44 +02:00
commit 6bb6783d39
53 changed files with 675 additions and 168 deletions

View File

@ -34,29 +34,33 @@ Bugfixes:
* State Mutability: Constant public state variables are considered ``pure`` functions.
### 0.6.12 (unreleased)
### 0.6.12 (2020-07-22)
Language Features:
* NatSpec: Implement tag ``@inheritdoc`` to copy documentation from a specific base contract.
* Wasm backend: Add ``i32.ctz``, ``i64.ctz``, ``i32.popcnt``, and ``i64.popcnt``.
Compiler Features:
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
* Peephole Optimizer: Remove unnecessary masking of tags.
* Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only.
* NatSpec: Implement tag ``@inheritdoc`` to copy documentation from a specific contract.
Bugfixes:
* SMTChecker: Fix internal error when using bitwise operators on fixed bytes type.
* SMTChecker: Fix internal error when using compound bitwise operator assignments on array indices inside branches.
* SMTChecker: Fix error in events with indices of type static array.
* Type Checker: Fix overload resolution in combination with ``{value: ...}``.
* Type Checker: Fix internal compiler error related to oversized types.
Compiler Features:
* Code Generator: Avoid double cleanup when copying to memory.
Compiler Features:
* Build System: Update internal dependency of jsoncpp to 1.9.3.
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
* Optimizer: Add rule to remove shifts inside the byte opcode.
* Peephole Optimizer: Add rule to remove swap after dup.
* Peephole Optimizer: Remove unnecessary masking of tags.
* Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only.
Bugfixes:
* SMTChecker: Fix error in events with indices of type static array.
* SMTChecker: Fix internal error in sequential storage array pushes (``push().push()``).
* SMTChecker: Fix internal error when using bitwise operators on fixed bytes type.
* SMTChecker: Fix internal error when using compound bitwise operator assignments on array indices inside branches.
* Type Checker: Fix internal compiler error related to oversized types.
* Type Checker: Fix overload resolution in combination with ``{value: ...}``.
Build System:
* Update internal dependency of jsoncpp to 1.9.3.
### 0.6.11 (2020-07-07)

View File

@ -1113,6 +1113,10 @@
"bugs": [],
"released": "2020-07-07"
},
"0.6.12": {
"bugs": [],
"released": "2020-07-22"
},
"0.6.2": {
"bugs": [
"MissingEscapingInFormatting",

View File

@ -77,7 +77,7 @@ version stands as a reference.
* `Japanese <https://solidity-jp.readthedocs.io>`_
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
* `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress)
* `Simplified Chinese <https://learnblockchain.cn/docs/solidity/>`_ (in progress)
* `Spanish <https://solidity-es.readthedocs.io>`_
* `Turkish <https://github.com/denizozzgur/Solidity_TR/blob/master/README.md>`_ (partial)

View File

@ -237,6 +237,23 @@ memory arrays, i.e. the following is not possible:
It is planned to remove this restriction in the future, but it creates some
complications because of how arrays are passed in the ABI.
If you want to initialize dynamically-sized arrays, you have to assign the
individual elements:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
contract C {
function f() public pure {
uint[] memory x = new uint[](3);
x[0] = 1;
x[1] = 3;
x[2] = 4;
}
}
.. index:: ! array;length, length, push, pop, !array;push, !array;pop
.. _array-members:

View File

@ -92,8 +92,8 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const&
class Functionalizer
{
public:
Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes):
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes)
Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes, Assembly const& _assembly):
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly)
{}
void feed(AssemblyItem const& _item)
@ -104,6 +104,9 @@ public:
m_location = _item.location();
printLocation();
}
string expression = _item.toAssemblyText(m_assembly);
if (!(
_item.canBeFunctional() &&
_item.returnValues() <= 1 &&
@ -111,10 +114,9 @@ public:
))
{
flush();
m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl;
m_out << m_prefix << (_item.type() == Tag ? "" : " ") << expression << endl;
return;
}
string expression = _item.toAssemblyText();
if (_item.arguments() > 0)
{
expression += "(";
@ -160,13 +162,14 @@ private:
ostream& m_out;
string const& m_prefix;
StringMap const& m_sourceCodes;
Assembly const& m_assembly;
};
}
void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
{
Functionalizer f(_out, _prefix, _sourceCodes);
Functionalizer f(_out, _prefix, _sourceCodes, *this);
for (auto const& i: m_items)
f.feed(i);
@ -639,7 +642,7 @@ LinkerObject const& Assembly::assemble() const
case PushSubSize:
{
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
auto s = m_subs.at(static_cast<size_t>(i.data()))->assemble().bytecode.size();
auto s = subAssemblyById(static_cast<size_t>(i.data()))->assemble().bytecode.size();
i.setPushedValue(u256(s));
uint8_t b = max<unsigned>(1, util::bytesRequired(s));
ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b);
@ -707,25 +710,20 @@ LinkerObject const& Assembly::assemble() const
// Append an INVALID here to help tests find miscompilation.
ret.bytecode.push_back(uint8_t(Instruction::INVALID));
for (size_t i = 0; i < m_subs.size(); ++i)
for (auto const& [subIdPath, bytecodeOffset]: subRef)
{
auto references = subRef.equal_range(i);
if (references.first == references.second)
continue;
for (auto ref = references.first; ref != references.second; ++ref)
{
bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef);
toBigEndian(ret.bytecode.size(), r);
}
ret.append(m_subs[i]->assemble());
bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef);
toBigEndian(ret.bytecode.size(), r);
ret.append(subAssemblyById(subIdPath)->assemble());
}
for (auto const& i: tagRef)
{
size_t subId;
size_t tagId;
tie(subId, tagId) = i.second;
assertThrow(subId == numeric_limits<size_t>::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id");
std::vector<size_t> const& tagPositions =
vector<size_t> const& tagPositions =
subId == numeric_limits<size_t>::max() ?
m_tagPositionsInBytecode :
m_subs[subId]->m_tagPositionsInBytecode;
@ -758,3 +756,51 @@ LinkerObject const& Assembly::assemble() const
}
return ret;
}
vector<size_t> Assembly::decodeSubPath(size_t _subObjectId) const
{
if (_subObjectId < m_subs.size())
return {_subObjectId};
auto subIdPathIt = find_if(
m_subPaths.begin(),
m_subPaths.end(),
[_subObjectId](auto const& subId) { return subId.second == _subObjectId; }
);
assertThrow(subIdPathIt != m_subPaths.end(), AssemblyException, "");
return subIdPathIt->first;
}
size_t Assembly::encodeSubPath(vector<size_t> const& _subPath)
{
assertThrow(!_subPath.empty(), AssemblyException, "");
if (_subPath.size() == 1)
{
assertThrow(_subPath[0] < m_subs.size(), AssemblyException, "");
return _subPath[0];
}
if (m_subPaths.find(_subPath) == m_subPaths.end())
{
size_t objectId = numeric_limits<size_t>::max() - m_subPaths.size();
assertThrow(objectId >= m_subs.size(), AssemblyException, "");
m_subPaths[_subPath] = objectId;
}
return m_subPaths[_subPath];
}
Assembly const* Assembly::subAssemblyById(size_t _subId) const
{
vector<size_t> subIds = decodeSubPath(_subId);
Assembly const* currentAssembly = this;
for (size_t currentSubId: subIds)
{
currentAssembly = currentAssembly->m_subs.at(currentSubId).get();
assertThrow(currentAssembly, AssemblyException, "");
}
assertThrow(currentAssembly != this, AssemblyException, "");
return currentAssembly;
}

View File

@ -146,6 +146,9 @@ public:
/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
void markAsInvalid() { m_invalid = true; }
std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
size_t encodeSubPath(std::vector<size_t> const& _subPath);
protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
@ -166,6 +169,9 @@ private:
static std::string toStringInHex(u256 _value);
bool m_invalid = false;
Assembly const* subAssemblyById(size_t _subId) const;
protected:
/// 0 is reserved for exception
unsigned m_usedTags = 1;
@ -179,6 +185,10 @@ protected:
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
/// Map from a vector representing a path to a particular sub assembly to sub assembly id.
/// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value).
std::map<std::vector<size_t>, size_t> m_subPaths;
mutable LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -18,7 +18,10 @@
#include <libevmasm/AssemblyItem.h>
#include <libevmasm/Assembly.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/FixedHash.h>
#include <liblangutil/SourceLocation.h>
@ -170,7 +173,7 @@ string AssemblyItem::getJumpTypeAsString() const
}
}
string AssemblyItem::toAssemblyText() const
string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
{
string text;
switch (type())
@ -208,11 +211,18 @@ string AssemblyItem::toAssemblyText() const
text = string("data_") + util::toHex(data());
break;
case PushSub:
text = string("dataOffset(sub_") + to_string(static_cast<size_t>(data())) + ")";
break;
case PushSubSize:
text = string("dataSize(sub_") + to_string(static_cast<size_t>(data())) + ")";
{
vector<string> subPathComponents;
for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast<size_t>(data())))
subPathComponents.emplace_back("sub_" + to_string(subPathComponentId));
text =
(type() == PushSub ? "dataOffset"s : "dataSize"s) +
"(" +
solidity::util::joinHumanReadable(subPathComponents, ".") +
")";
break;
}
case PushProgramSize:
text = string("bytecodeSize");
break;

View File

@ -152,7 +152,7 @@ public:
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
u256 const* pushedValue() const { return m_pushedValue.get(); }
std::string toAssemblyText() const;
std::string toAssemblyText(Assembly const& _assembly) const;
size_t m_modifierDepth = 0;

View File

@ -1491,7 +1491,6 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(false, "Blockhash has been removed.");
else if (member == "creationCode" || member == "runtimeCode")
{
solUnimplementedAssert(member != "runtimeCode", "");
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
m_context.subObjectsCreated().insert(&contract);
@ -1503,7 +1502,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
)")
("allocationFunction", m_utils.allocationFunction())
("size", m_context.newYulVariable())
("objectName", IRNames::creationObject(contract))
("objectName", IRNames::creationObject(contract) + (member == "runtimeCode" ? "." + IRNames::runtimeObject(contract) : ""))
("result", IRVariable(_memberAccess).commaSeparatedList()).render();
}
else if (member == "name")

View File

@ -1077,7 +1077,7 @@ void SMTEncoder::arrayPush(FunctionCall const& _funCall)
m_context.addAssertion(symbArray->length() == oldLength + 1);
if (arguments.empty())
defineExpr(_funCall, element);
defineExpr(_funCall, smtutil::Expression::select(symbArray->elements(), oldLength));
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
@ -1119,6 +1119,28 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
}
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(&_expr))
arrayIndexAssignment(*indexAccess, _array);
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(&_expr))
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
if (funType.kind() == FunctionType::Kind::ArrayPush)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression());
solAssert(memberAccess, "");
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
solAssert(symbArray, "");
auto oldLength = symbArray->length();
auto store = smtutil::Expression::store(
symbArray->elements(),
symbArray->length() - 1,
_array
);
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == store);
m_context.addAssertion(symbArray->length() == oldLength);
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
}
else if (dynamic_cast<MemberAccess const*>(&_expr))
m_errorReporter.warning(
9599_error,

View File

@ -72,7 +72,7 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
errors,
_dialect,
{},
_object.dataNames()
_object.qualifiedDataNames()
).analyze(*_object.code);
yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code.");
return analysisInfo;

View File

@ -102,7 +102,6 @@ private:
/// Vists the expression and expects it to return a single boolean value.
/// Reports an error otherwise.
void expectBoolExpression(Expression const& _expr);
bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists, can be assigned to
/// and has the same type as the value.

View File

@ -138,7 +138,7 @@ bool AssemblyStack::analyzeParsed(Object& _object)
m_errorReporter,
languageToDialect(m_language, m_evmVersion),
{},
_object.dataNames()
_object.qualifiedDataNames()
);
bool success = analyzer.analyze(*_object.code);
for (auto& subNode: _object.subObjects)

View File

@ -39,7 +39,7 @@ map<YulString, int> CompilabilityChecker::run(
bool _optimizeStackAllocation
)
{
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
if (auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
{
NoOutputEVMDialect noOutputDialect(*evmDialect);
@ -48,8 +48,10 @@ map<YulString, int> CompilabilityChecker::run(
BuiltinContext builtinContext;
builtinContext.currentObject = &_object;
for (auto name: _object.dataNames())
builtinContext.subIDs[name] = 1;
if (!_object.name.empty())
builtinContext.subIDs[_object.name] = 1;
for (auto const& subNode: _object.subObjects)
builtinContext.subIDs[subNode->name] = 1;
NoOutputAssembly assembly;
CodeTransform transform(
assembly,

View File

@ -24,9 +24,10 @@
#include <libyul/AsmPrinter.h>
#include <libyul/Exceptions.h>
#include <libsolutil/Visitor.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/replace.hpp>
using namespace std;
@ -62,13 +63,54 @@ string Object::toString(Dialect const* _dialect) const
return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
}
set<YulString> Object::dataNames() const
set<YulString> Object::qualifiedDataNames() const
{
set<YulString> names;
names.insert(name);
for (auto const& subObject: subIndexByName)
names.insert(subObject.first);
// The empty name is not valid
names.erase(YulString{});
return names;
set<YulString> qualifiedNames = name.empty() ? set<YulString>{} : set<YulString>{name};
for (shared_ptr<ObjectNode> const& subObjectNode: subObjects)
{
yulAssert(qualifiedNames.count(subObjectNode->name) == 0, "");
qualifiedNames.insert(subObjectNode->name);
if (auto const* subObject = dynamic_cast<Object const*>(subObjectNode.get()))
for (YulString const& subSubObj: subObject->qualifiedDataNames())
if (subObject->name != subSubObj)
{
yulAssert(qualifiedNames.count(YulString{subObject->name.str() + "." + subSubObj.str()}) == 0, "");
qualifiedNames.insert(YulString{subObject->name.str() + "." + subSubObj.str()});
}
}
yulAssert(qualifiedNames.count(YulString{}) == 0, "");
qualifiedNames.erase(YulString{});
return qualifiedNames;
}
vector<size_t> Object::pathToSubObject(YulString _qualifiedName) const
{
yulAssert(_qualifiedName != name, "");
yulAssert(subIndexByName.count(name) == 0, "");
if (boost::algorithm::starts_with(_qualifiedName.str(), name.str() + "."))
_qualifiedName = YulString{_qualifiedName.str().substr(name.str().length() + 1)};
yulAssert(!_qualifiedName.empty(), "");
vector<string> subObjectPathComponents;
boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of("."));
vector<size_t> path;
Object const* object = this;
for (string const& currentSubObjectName: subObjectPathComponents)
{
yulAssert(!currentSubObjectName.empty(), "");
auto subIndexIt = object->subIndexByName.find(YulString{currentSubObjectName});
yulAssert(
subIndexIt != object->subIndexByName.end(),
"Assembly object <" + _qualifiedName.str() + "> not found or does not contain code."
);
object = dynamic_cast<Object const*>(object->subObjects[subIndexIt->second].get());
yulAssert(object, "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code.");
yulAssert(object->subId != numeric_limits<size_t>::max(), "");
path.push_back({object->subId});
}
return path;
}

View File

@ -44,6 +44,8 @@ struct ObjectNode
virtual std::string toString(Dialect const* _dialect) const = 0;
std::string toString() { return toString(nullptr); }
/// Name of the object.
/// Can be empty since .yul files can also just contain code, without explicitly placing it in an object.
YulString name;
};
@ -68,8 +70,23 @@ public:
std::string toString(Dialect const* _dialect) const override;
/// @returns the set of names of data objects accessible from within the code of
/// this object.
std::set<YulString> dataNames() const;
/// this object, including the name of object itself
std::set<YulString> qualifiedDataNames() const;
/// @returns vector of subIDs if possible to reach subobject with @a _qualifiedName, throws otherwise
/// For "B.C" should return vector of two values if success (subId of B and subId of C in B).
/// In object "A" if called for "A.B" will return only one value (subId for B)
/// will return empty vector for @a _qualifiedName that equals to object name.
/// Example:
/// A1{ B2{ C3, D3 }, E2{ F3{ G4, K4, H4{ I5 } } } }
/// pathToSubObject("A1.E2.F3.H4") == {1, 0, 2}
/// pathToSubObject("E2.F3.H4") == {1, 0, 2}
/// pathToSubObject("A1.E2") == {1}
/// The path must not lead to a @a Data object (will throw in that case).
std::vector<size_t> pathToSubObject(YulString _qualifiedName) const;
/// sub id for object if it is subobject of another object, max value if it is not subobject
size_t subId = std::numeric_limits<size_t>::max();
std::shared_ptr<Block> code;
std::vector<std::shared_ptr<ObjectNode>> subObjects;

View File

@ -102,9 +102,9 @@ public:
/// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset.
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() = 0;
/// Appends the offset of the given sub-assembly or data.
virtual void appendDataOffset(SubID _sub) = 0;
virtual void appendDataOffset(std::vector<SubID> const& _subPath) = 0;
/// Appends the size of the given sub-assembly or data.
virtual void appendDataSize(SubID _sub) = 0;
virtual void appendDataSize(std::vector<SubID> const& _subPath) = 0;
/// Appends the given data to the assembly and returns its ID.
virtual SubID appendData(bytes const& _data) = 0;

View File

@ -147,22 +147,28 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::
return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())};
}
void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub)
void EthAssemblyAdapter::appendDataOffset(vector<AbstractAssembly::SubID> const& _subPath)
{
auto it = m_dataHashBySubId.find(_sub);
if (it == m_dataHashBySubId.end())
m_assembly.pushSubroutineOffset(_sub);
else
if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
{
yulAssert(_subPath.size() == 1, "");
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second);
return;
}
m_assembly.pushSubroutineOffset(m_assembly.encodeSubPath(_subPath));
}
void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub)
void EthAssemblyAdapter::appendDataSize(vector<AbstractAssembly::SubID> const& _subPath)
{
auto it = m_dataHashBySubId.find(_sub);
if (it == m_dataHashBySubId.end())
m_assembly.pushSubroutineSize(static_cast<size_t>(_sub));
else
if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
{
yulAssert(_subPath.size() == 1, "");
m_assembly << u256(m_assembly.data(h256(it->second)).size());
return;
}
m_assembly.pushSubroutineSize(m_assembly.encodeSubPath(_subPath));
}
AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data)

View File

@ -58,8 +58,8 @@ public:
void appendReturnsub(int, int) override;
void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override;
void appendDataSize(SubID _sub) override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;

View File

@ -202,12 +202,12 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EVMAssembly::createS
return {};
}
void EVMAssembly::appendDataOffset(AbstractAssembly::SubID)
void EVMAssembly::appendDataOffset(vector<AbstractAssembly::SubID> const&)
{
yulAssert(false, "Data not implemented.");
}
void EVMAssembly::appendDataSize(AbstractAssembly::SubID)
void EVMAssembly::appendDataSize(vector<AbstractAssembly::SubID> const&)
{
yulAssert(false, "Data not implemented.");
}

View File

@ -80,8 +80,8 @@ public:
/// Append the assembled size as a constant.
void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override;
void appendDataSize(SubID _sub) override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;

View File

@ -144,7 +144,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context,
std::function<void(Expression const&)>
std::function<void(Expression const&)> const&
) {
yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, "");
@ -154,18 +154,19 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendAssemblySize();
else
{
yulAssert(
_context.subIDs.count(dataName) != 0,
"Could not find assembly object <" + dataName.str() + ">."
);
_assembly.appendDataSize(_context.subIDs.at(dataName));
vector<size_t> subIdPath =
_context.subIDs.count(dataName) == 0 ?
_context.currentObject->pathToSubObject(dataName) :
vector<size_t>{_context.subIDs.at(dataName)};
yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">.");
_assembly.appendDataSize(subIdPath);
}
}));
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context,
std::function<void(Expression const&)>
std::function<void(Expression const&)> const&
) {
yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, "");
@ -175,11 +176,12 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendConstant(0);
else
{
yulAssert(
_context.subIDs.count(dataName) != 0,
"Could not find assembly object <" + dataName.str() + ">."
);
_assembly.appendDataOffset(_context.subIDs.at(dataName));
vector<size_t> subIdPath =
_context.subIDs.count(dataName) == 0 ?
_context.currentObject->pathToSubObject(dataName) :
vector<size_t>{_context.subIDs.at(dataName)};
yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">.");
_assembly.appendDataOffset(subIdPath);
}
}));
builtins.emplace(createFunction(

View File

@ -26,6 +26,7 @@
#include <libyul/Object.h>
#include <libyul/Exceptions.h>
#include <libevmasm/Assembly.h>
using namespace solidity::yul;
using namespace std;
@ -41,11 +42,12 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
BuiltinContext context;
context.currentObject = &_object;
for (auto& subNode: _object.subObjects)
if (Object* subObject = dynamic_cast<Object*>(subNode.get()))
for (auto const& subNode: _object.subObjects)
if (auto* subObject = dynamic_cast<Object*>(subNode.get()))
{
auto subAssemblyAndID = m_assembly.createSubAssembly();
context.subIDs[subObject->name] = subAssemblyAndID.second;
subObject->subId = subAssemblyAndID.second;
compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize);
}
else

View File

@ -132,12 +132,12 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::cr
return {};
}
void NoOutputAssembly::appendDataOffset(AbstractAssembly::SubID)
void NoOutputAssembly::appendDataOffset(std::vector<AbstractAssembly::SubID> const&)
{
appendInstruction(evmasm::Instruction::PUSH1);
}
void NoOutputAssembly::appendDataSize(AbstractAssembly::SubID)
void NoOutputAssembly::appendDataSize(std::vector<AbstractAssembly::SubID> const&)
{
appendInstruction(evmasm::Instruction::PUSH1);
}

View File

@ -68,8 +68,8 @@ public:
void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override;
void appendDataOffset(SubID _sub) override;
void appendDataSize(SubID _sub) override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;

View File

@ -1244,7 +1244,7 @@ Object EVMToEwasmTranslator::run(Object const& _object)
ErrorList errors;
ErrorReporter errorReporter(errors);
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames());
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.qualifiedDataNames());
if (!analyzer.analyze(*ret.code))
{
string message = "Invalid code generated after EVM to wasm translation.\n";

View File

@ -36,24 +36,26 @@ def in_comment(source, pos):
return slash_star_pos > star_slash_pos
def find_ids_in_source_file(file_name, ids):
def find_ids_in_source_file(file_name, id_to_file_names):
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
if id in id_to_file_names:
id_to_file_names[id].append(file_name)
else:
ids[id] = 1
id_to_file_names[id] = [file_name]
def get_used_ids(file_names):
used_ids = {}
def find_ids_in_source_files(file_names):
"""Returns a dictionary with list of source files for every appearance of every id"""
id_to_file_names = {}
for file_name in file_names:
find_ids_in_source_file(file_name, used_ids)
return used_ids
find_ids_in_source_file(file_name, id_to_file_names)
return id_to_file_names
def get_next_id(available_ids):
@ -63,7 +65,7 @@ def get_next_id(available_ids):
return next_id
def fix_ids_in_file(file_name, available_ids, used_ids):
def fix_ids_in_source_file(file_name, id_to_count, available_ids):
source = read_file(file_name)
k = 0
@ -75,11 +77,11 @@ def fix_ids_in_file(file_name, available_ids, used_ids):
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
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or id_to_count[id] > 1):
assert id in id_to_count
new_id = get_next_id(available_ids)
assert new_id not in used_ids
used_ids[id] -= 1
assert new_id not in id_to_count
id_to_count[id] -= 1
else:
new_id = id
@ -94,10 +96,15 @@ def fix_ids_in_file(file_name, available_ids, used_ids):
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()
def fix_ids_in_source_files(file_names, id_to_count):
"""
Fixes ids in given source files;
id_to_count contains number of appearances of every id in sources
"""
available_ids = {str(id) for id in range(1000, 10000)} - id_to_count.keys()
for file_name in file_names:
fix_ids_in_file(file_name, available_ids, used_ids)
fix_ids_in_source_file(file_name, id_to_count, available_ids)
def find_files(top_dir, sub_dirs, extensions):
@ -121,10 +128,12 @@ def find_ids_in_test_file(file_name):
def find_ids_in_test_files(file_names):
used_ids = set()
"""Returns a set containing all ids in tests"""
ids = set()
for file_name in file_names:
used_ids |= find_ids_in_test_file(file_name)
return used_ids
ids |= find_ids_in_test_file(file_name)
return ids
def find_ids_in_cmdline_test_err(file_name):
@ -142,7 +151,23 @@ def print_ids(ids):
print(id, end="")
def examine_id_coverage(top_dir, used_ids):
def print_ids_per_file(ids, id_to_file_names, top_dir):
file_name_to_ids = {}
for id in ids:
for file_name in id_to_file_names[id]:
relpath = path.relpath(file_name, top_dir)
if relpath not in file_name_to_ids:
file_name_to_ids[relpath] = []
file_name_to_ids[relpath].append(id)
for file_name in sorted(file_name_to_ids):
print(file_name)
for id in sorted(file_name_to_ids[file_name]):
print(f" {id}", end="")
print()
def examine_id_coverage(top_dir, source_id_to_file_names):
test_sub_dirs = [
path.join("test", "libsolidity", "errorRecoveryTests"),
path.join("test", "libsolidity", "smtCheckerTests"),
@ -153,27 +178,28 @@ def examine_id_coverage(top_dir, used_ids):
test_sub_dirs,
[".sol"]
)
covered_ids = find_ids_in_test_files(test_file_names)
source_ids = source_id_to_file_names.keys()
test_ids = find_ids_in_test_files(test_file_names)
# special case, we are interested in warnings which are ignored by regular tests:
# Warning (1878): SPDX license identifier not provided in source file. ....
# Warning (3420): Source file does not specify required compiler version!
covered_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err"))
test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err"))
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(f"IDs in source files: {len(source_ids)}")
print(f"IDs in test files : {len(test_ids)} ({len(test_ids) - len(source_ids)})")
print()
unused_covered_ids = covered_ids - used_ids
if len(unused_covered_ids) != 0:
test_only_ids = test_ids - source_ids
if len(test_only_ids) != 0:
print("Error. The following error codes found in tests, but not in sources:")
print_ids(unused_covered_ids)
print_ids(test_only_ids)
return 1
used_uncovered_ids = used_ids - covered_ids
if len(used_uncovered_ids) != 0:
source_only_ids = source_ids - test_ids
if len(source_only_ids) != 0:
print("The following error codes found in sources, but not in tests:")
print_ids(used_uncovered_ids)
print_ids_per_file(source_only_ids, source_id_to_file_names, top_dir)
print("\n\nPlease make sure to add appropriate tests.")
return 1
@ -187,22 +213,22 @@ def main(argv):
fix = False
no_confirm = False
examine_coverage = False
next = False
next_id = False
opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
for opt, arg in opts:
if opt == '--check':
if opt == "--check":
check = True
elif opt == "--fix":
fix = True
elif opt == '--no-confirm':
elif opt == "--no-confirm":
no_confirm = True
elif opt == '--examine-coverage':
elif opt == "--examine-coverage":
examine_coverage = True
elif opt == '--next':
next = True
elif opt == "--next":
next_id = True
if [check, fix, examine_coverage, next].count(True) != 1:
if [check, fix, examine_coverage, next_id].count(True) != 1:
print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next")
exit(1)
@ -213,32 +239,34 @@ def main(argv):
["libevmasm", "liblangutil", "libsolc", "libsolidity", "libsolutil", "libyul", "solc"],
[".h", ".cpp"]
)
used_ids = get_used_ids(source_file_names)
source_id_to_file_names = find_ids_in_source_files(source_file_names)
ok = True
for id in sorted(used_ids):
for id in sorted(source_id_to_file_names):
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")
if len(source_id_to_file_names[id]) > 1:
print(f"ID {id} appears {len(source_id_to_file_names[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(1)
res = examine_id_coverage(cwd, source_id_to_file_names)
exit(res)
random.seed()
if next:
if next_id:
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()
exit(1)
available_ids = {str(id) for id in range(1000, 10000)} - source_id_to_file_names.keys()
next_id = get_next_id(available_ids)
print(f"Next ID: {next_id}")
exit(0)
@ -263,7 +291,10 @@ def main(argv):
if answer not in "yY":
exit(1)
fix_ids(used_ids, source_file_names)
# number of appearances for every id
source_id_to_count = { id: len(file_names) for id, file_names in source_id_to_file_names.items() }
fix_ids_in_source_files(source_file_names, source_id_to_count)
print("Fixing completed")
exit(2)

View File

@ -38,6 +38,13 @@ TestCaseReader::TestCaseReader(string const& _filename):
m_unreadSettings = m_settings;
}
TestCaseReader::TestCaseReader(istringstream const& _str)
{
tie(m_sources, m_lineNumber) = parseSourcesAndSettingsWithLineNumber(
static_cast<istream&>(const_cast<istringstream&>(_str))
);
}
string const& TestCaseReader::source() const
{
if (m_sources.sources.size() != 1)

View File

@ -42,6 +42,7 @@ class TestCaseReader
public:
TestCaseReader() = default;
explicit TestCaseReader(std::string const& _filename);
explicit TestCaseReader(std::istringstream const& _testCode);
SourceMap const& sources() const { return m_sources; }
std::string const& source() const;

View File

@ -0,0 +1,17 @@
{
"language": "Yul",
"sources":
{
"A":
{
"content": "object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } }"
}
},
"settings":
{
"outputSelection":
{
"*": { "*": ["*"], "": [ "*" ] }
}
}
}

View File

@ -0,0 +1,4 @@
{"errors":[{"component":"general","formattedMessage":"A:1:40: TypeError: Unknown data object \"NamedObject.\".
object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } }
^--------^
","message":"Unknown data object \"NamedObject.\".","severity":"error","sourceLocation":{"end":49,"file":"A","start":39},"type":"TypeError"}]}

View File

@ -29,6 +29,7 @@
#include <string>
#include <tuple>
#include <memory>
#include <libyul/Exceptions.h>
using namespace std;
using namespace solidity::langutil;
@ -254,6 +255,24 @@ BOOST_AUTO_TEST_CASE(immutable)
);
}
BOOST_AUTO_TEST_CASE(subobject_encode_decode)
{
Assembly assembly;
shared_ptr<Assembly> subAsmPtr = make_shared<Assembly>();
shared_ptr<Assembly> subSubAsmPtr = make_shared<Assembly>();
assembly.appendSubroutine(subAsmPtr);
subAsmPtr->appendSubroutine(subSubAsmPtr);
BOOST_CHECK(assembly.encodeSubPath({0}) == 0);
BOOST_REQUIRE_THROW(assembly.encodeSubPath({1}), solidity::evmasm::AssemblyException);
BOOST_REQUIRE_THROW(assembly.decodeSubPath(1), solidity::evmasm::AssemblyException);
vector<size_t> subPath{0, 0};
BOOST_CHECK(assembly.decodeSubPath(assembly.encodeSubPath(subPath)) == subPath);
}
BOOST_AUTO_TEST_SUITE_END()
} // end namespaces

View File

@ -36,7 +36,8 @@ contract C {
return true;
}
}
// ====
// compileViaYul: also
// ----
// testRuntime() -> true
// testCreation() -> true

View File

@ -10,18 +10,18 @@ contract D {
}
}
contract C {
function test() public returns (uint256) {
D d = new D();
bytes32 hash;
assembly { hash := extcodehash(d) }
assert(hash == keccak256(type(D).runtimeCode));
return 42;
D d = new D();
bytes32 hash;
assembly { hash := extcodehash(d) }
assert(hash == keccak256(type(D).runtimeCode));
return 42;
}
}
// ====
// EVMVersion: >=constantinople
// compileViaYul: also
// ----
// test() -> 42

View File

@ -0,0 +1,9 @@
pragma experimental SMTChecker;
contract C {
int[][] array2d;
function l() public {
array2d.push().push();
assert(array2d.length > 0);
assert(array2d[array2d.length - 1].length > 0);
}
}

View File

@ -0,0 +1,12 @@
pragma experimental SMTChecker;
contract C {
int[][] array2d;
function l() public {
array2d.push().push();
assert(array2d.length > 2);
assert(array2d[array2d.length - 1].length > 3);
}
}
// ----
// Warning 4661: (113-139): Assertion violation happens here
// Warning 4661: (143-189): Assertion violation happens here

View File

@ -0,0 +1,11 @@
pragma experimental SMTChecker;
contract C {
int[][][] array2d;
function l() public {
array2d.push().push().push();
assert(array2d.length > 0);
uint last = array2d[array2d.length - 1].length;
assert(last > 0);
assert(array2d[array2d.length - 1][last - 1].length > 0);
}
}

View File

@ -0,0 +1,15 @@
pragma experimental SMTChecker;
contract C {
int[][][] array2d;
function l() public {
array2d.push().push().push();
assert(array2d.length > 2);
uint last = array2d[array2d.length - 1].length;
assert(last > 3);
assert(array2d[array2d.length - 1][last - 1].length > 4);
}
}
// ----
// Warning 4661: (122-148): Assertion violation happens here
// Warning 4661: (202-218): Assertion violation happens here
// Warning 4661: (222-278): Assertion violation happens here

View File

@ -0,0 +1,18 @@
pragma experimental SMTChecker;
contract C {
int[][] array2d;
function l() public {
s().push();
// False positive.
// Knowledge is erased because `s()` is a storage pointer.
assert(array2d[2].length > 0);
}
function s() internal returns (int[] storage) {
array2d.push();
array2d.push();
array2d.push();
return array2d[2];
}
}
// ----
// Warning 4661: (184-213): Assertion violation happens here

View File

@ -0,0 +1,15 @@
pragma experimental SMTChecker;
contract C {
int[][] array2d;
function l() public {
s();
array2d[2].push();
assert(array2d[2].length > 0);
}
function s() internal returns (int[] storage) {
array2d.push();
array2d.push();
array2d.push();
return array2d[2];
}
}

View File

@ -0,0 +1,8 @@
contract C {
receive() external payable { }
receive() external payable { }
receive() external payable { }
}
// ----
// DeclarationError 4046: (52-82): Only one receive function is allowed.
// DeclarationError 4046: (87-117): Only one receive function is allowed.

View File

@ -90,7 +90,7 @@ pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(
if (!parserResult->code || errorReporter.hasErrors())
return {};
shared_ptr<AsmAnalysisInfo> analysisInfo = make_shared<AsmAnalysisInfo>();
AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->dataNames());
AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->qualifiedDataNames());
// TODO this should be done recursively.
if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors())
return {};

View File

@ -99,7 +99,7 @@ string EwasmTranslationTest::interpret()
{
InterpreterState state;
state.maxTraceSize = 10000;
state.maxSteps = 100000;
state.maxSteps = 1000000;
try
{
Interpreter::run(state, WasmDialect{}, *m_object->code);

View File

@ -0,0 +1,104 @@
object "A" {
code {
pop(dataoffset("A"))
pop(datasize("A"))
pop(dataoffset("B"))
pop(datasize("B"))
pop(dataoffset("B.C"))
pop(datasize("B.C"))
pop(dataoffset("B.E"))
pop(datasize("B.E"))
pop(dataoffset("B.C.D"))
pop(datasize("B.C.D"))
}
data "data1" "Hello, World!"
object "B" {
code {
pop(dataoffset("C"))
pop(datasize("C"))
pop(dataoffset("E"))
pop(datasize("E"))
pop(dataoffset("C.D"))
pop(datasize("C.D"))
}
object "C" {
code {
pop(dataoffset("D"))
pop(datasize("D"))
}
object "D" {
code {
invalid()
}
}
}
object "E" {
code {
invalid()
}
}
}
}
// ----
// Assembly:
// /* "source":26:46 */
// pop(0x00)
// /* "source":51:69 */
// pop(bytecodeSize)
// /* "source":74:94 */
// pop(dataOffset(sub_0))
// /* "source":99:117 */
// pop(dataSize(sub_0))
// /* "source":122:144 */
// pop(dataOffset(sub_0.sub_0))
// /* "source":149:169 */
// pop(dataSize(sub_0.sub_0))
// /* "source":174:196 */
// pop(dataOffset(sub_0.sub_1))
// /* "source":201:221 */
// pop(dataSize(sub_0.sub_1))
// /* "source":226:250 */
// pop(dataOffset(sub_0.sub_0.sub_0))
// /* "source":255:277 */
// pop(dataSize(sub_0.sub_0.sub_0))
// stop
// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421
//
// sub_0: assembly {
// /* "source":347:367 */
// pop(dataOffset(sub_0))
// /* "source":374:392 */
// pop(dataSize(sub_0))
// /* "source":399:419 */
// pop(dataOffset(sub_1))
// /* "source":426:444 */
// pop(dataSize(sub_1))
// /* "source":451:473 */
// pop(dataOffset(sub_0.sub_0))
// /* "source":480:500 */
// pop(dataSize(sub_0.sub_0))
// stop
//
// sub_0: assembly {
// /* "source":545:565 */
// pop(dataOffset(sub_0))
// /* "source":574:592 */
// pop(dataSize(sub_0))
// stop
//
// sub_0: assembly {
// /* "source":645:654 */
// invalid
// }
// }
//
// sub_1: assembly {
// /* "source":717:726 */
// invalid
// }
// }
// Bytecode: 600050604650601f50601d50603e50600850603d50600150603c50600150fe601350600850601b50600150601c50600150fe600750600150fefefefefefe600750600150fefe
// Opcodes: PUSH1 0x0 POP PUSH1 0x46 POP PUSH1 0x1F POP PUSH1 0x1D POP PUSH1 0x3E POP PUSH1 0x8 POP PUSH1 0x3D POP PUSH1 0x1 POP PUSH1 0x3C POP PUSH1 0x1 POP INVALID PUSH1 0x13 POP PUSH1 0x8 POP PUSH1 0x1B POP PUSH1 0x1 POP PUSH1 0x1C POP PUSH1 0x1 POP INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID INVALID INVALID INVALID INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID
// SourceMappings: 26:20:0:-:0;;51:18;;74:20;;99:18;;122:22;;149:20;;174:22;;201:20;;226:24;;255:22;

View File

@ -72,16 +72,16 @@ void FuzzerUtil::testCompilerJsonInterface(string const& _input, bool _optimize,
runCompiler(jsonCompactPrint(config), _quiet);
}
void FuzzerUtil::testCompiler(string const& _input, bool _optimize)
void FuzzerUtil::testCompiler(StringMap const& _input, bool _optimize, unsigned _rand)
{
frontend::CompilerStack compiler;
EVMVersion evmVersion = s_evmVersions[_input.size() % s_evmVersions.size()];
EVMVersion evmVersion = s_evmVersions[_rand % s_evmVersions.size()];
frontend::OptimiserSettings optimiserSettings;
if (_optimize)
optimiserSettings = frontend::OptimiserSettings::standard();
else
optimiserSettings = frontend::OptimiserSettings::minimal();
compiler.setSources({{"", _input}});
compiler.setSources(_input);
compiler.setEVMVersion(evmVersion);
compiler.setOptimiserSettings(optimiserSettings);
try

View File

@ -16,6 +16,9 @@
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolutil/Common.h>
#include <map>
#include <string>
/**
@ -28,5 +31,9 @@ struct FuzzerUtil
static void testCompilerJsonInterface(std::string const& _input, bool _optimize, bool _quiet);
static void testConstantOptimizer(std::string const& _input, bool _quiet);
static void testStandardCompiler(std::string const& _input, bool _quiet);
static void testCompiler(std::string const& _input, bool _optimize);
/// Compiles @param _input which is a map of input file name to source code
/// string with optimisation turned on if @param _optimize is true
/// (off otherwise) and a pseudo-random @param _rand that selects the EVM
/// version to be compiled for.
static void testCompiler(solidity::StringMap const& _input, bool _optimize, unsigned _rand);
};

View File

@ -23,11 +23,11 @@ if (OSSFUZZ)
endif()
if (OSSFUZZ)
add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp)
add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp ../../TestCaseReader.cpp)
target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm)
set_target_properties(solc_opt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp)
add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp ../../TestCaseReader.cpp)
target_link_libraries(solc_noopt_ossfuzz PRIVATE libsolc evmasm)
set_target_properties(solc_noopt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})

View File

@ -18,6 +18,11 @@
#include <test/tools/fuzzer_common.h>
#include <test/TestCaseReader.h>
#include <sstream>
using namespace solidity::frontend::test;
using namespace std;
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
@ -25,7 +30,17 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
if (_size <= 600)
{
string input(reinterpret_cast<char const*>(_data), _size);
FuzzerUtil::testCompiler(input, /*optimize=*/false);
map<string, string> sourceCode;
try
{
TestCaseReader t = TestCaseReader(std::istringstream(input));
sourceCode = t.sources().sources;
}
catch (runtime_error const&)
{
return 0;
}
FuzzerUtil::testCompiler(sourceCode, /*optimize=*/false, /*_rand=*/_size);
}
return 0;
}

View File

@ -18,14 +18,29 @@
#include <test/tools/fuzzer_common.h>
#include <test/TestCaseReader.h>
#include <sstream>
using namespace solidity::frontend::test;
using namespace std;
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
{
if (_size <= 600)
{
string input(reinterpret_cast<char const *>(_data), _size);
FuzzerUtil::testCompiler(input, /*optimize=*/true);
string input(reinterpret_cast<char const*>(_data), _size);
map<string, string> sourceCode;
try
{
TestCaseReader t = TestCaseReader(std::istringstream(input));
sourceCode = t.sources().sources;
}
catch (runtime_error const&)
{
return 0;
}
FuzzerUtil::testCompiler(sourceCode, /*optimize=*/true, /*rand=*/_size);
}
return 0;
}

View File

@ -97,15 +97,17 @@ DEFINE_PROTO_FUZZER(Program const& _input)
EVMDialect::strictAssemblyForEVMObjects(version)
);
if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached)
if (
termReason == yulFuzzerUtil::TerminationReason::StepLimitReached ||
termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached
)
return;
stack.optimize();
termReason = yulFuzzerUtil::interpret(
yulFuzzerUtil::interpret(
os2,
stack.parserResult()->code,
EVMDialect::strictAssemblyForEVMObjects(version),
(yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4)
EVMDialect::strictAssemblyForEVMObjects(version)
);
bool isTraceEq = (os1.str() == os2.str());

View File

@ -145,6 +145,11 @@ void Interpreter::operator()(ForLoop const& _forLoop)
}
while (evaluate(*_forLoop.condition) != 0)
{
// Increment step for each loop iteration for loops with
// an empty body and post blocks to prevent a deadlock.
if (_forLoop.body.statements.size() == 0 && _forLoop.post.statements.size() == 0)
incrementStep();
m_state.controlFlowState = ControlFlowState::Default;
(*this)(_forLoop.body);
if (m_state.controlFlowState == ControlFlowState::Break || m_state.controlFlowState == ControlFlowState::Leave)
@ -176,12 +181,6 @@ void Interpreter::operator()(Leave const&)
void Interpreter::operator()(Block const& _block)
{
m_state.numSteps++;
if (m_state.maxSteps > 0 && m_state.numSteps >= m_state.maxSteps)
{
m_state.trace.emplace_back("Interpreter execution step limit reached.");
throw StepLimitReached();
}
enterScope(_block);
// Register functions.
for (auto const& statement: _block.statements)
@ -193,6 +192,7 @@ void Interpreter::operator()(Block const& _block)
for (auto const& statement: _block.statements)
{
incrementStep();
visit(statement);
if (m_state.controlFlowState != ControlFlowState::Default)
break;
@ -235,6 +235,16 @@ void Interpreter::leaveScope()
yulAssert(m_scope, "");
}
void Interpreter::incrementStep()
{
m_state.numSteps++;
if (m_state.maxSteps > 0 && m_state.numSteps >= m_state.maxSteps)
{
m_state.trace.emplace_back("Interpreter execution step limit reached.");
throw StepLimitReached();
}
}
void ExpressionEvaluator::operator()(Literal const& _literal)
{
static YulString const trueString("true");

View File

@ -154,6 +154,10 @@ private:
void enterScope(Block const& _block);
void leaveScope();
/// Increment interpreter step count, throwing exception if step limit
/// is reached.
void incrementStep();
Dialect const& m_dialect;
InterpreterState& m_state;
/// Values of variables.