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
6bb6783d39
34
Changelog.md
34
Changelog.md
@ -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)
|
||||
|
@ -1113,6 +1113,10 @@
|
||||
"bugs": [],
|
||||
"released": "2020-07-07"
|
||||
},
|
||||
"0.6.12": {
|
||||
"bugs": [],
|
||||
"released": "2020-07-22"
|
||||
},
|
||||
"0.6.2": {
|
||||
"bugs": [
|
||||
"MissingEscapingInFormatting",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef);
|
||||
toBigEndian(ret.bytecode.size(), r);
|
||||
ret.append(subAssemblyById(subIdPath)->assemble());
|
||||
}
|
||||
ret.append(m_subs[i]->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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
17
test/cmdlineTests/standard_yul_object_invalid_sub/input.json
Normal file
17
test/cmdlineTests/standard_yul_object_invalid_sub/input.json
Normal 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":
|
||||
{
|
||||
"*": { "*": ["*"], "": [ "*" ] }
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}]}
|
@ -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
|
||||
|
@ -36,7 +36,8 @@ contract C {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// testRuntime() -> true
|
||||
// testCreation() -> true
|
||||
|
@ -10,7 +10,6 @@ contract D {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract C {
|
||||
function test() public returns (uint256) {
|
||||
D d = new D();
|
||||
@ -23,5 +22,6 @@ contract C {
|
||||
|
||||
// ====
|
||||
// EVMVersion: >=constantinople
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 42
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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];
|
||||
}
|
||||
}
|
@ -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.
|
@ -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 {};
|
||||
|
@ -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);
|
||||
|
104
test/libyul/objectCompiler/subObjectAccess.yul
Normal file
104
test/libyul/objectCompiler/subObjectAccess.yul
Normal 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;
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user