Add reason string for internal reverts

This commit is contained in:
Leonardo Alt 2020-01-22 11:48:56 -03:00
parent e8eb1f2d14
commit 36928c7a35
49 changed files with 555 additions and 131 deletions

View File

@ -7,6 +7,7 @@ Language Features:
Compiler Features:
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
Bugfixes:

View File

@ -244,7 +244,7 @@ Input Description
// "default", "strip", "debug" and "verboseDebug".
// "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
// "debug" injects strings for compiler-generated internal reverts (not yet implemented)
// "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now.
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
"revertStrings": "default"
}

View File

@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
Whiskers templ(R"(
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
<decodeElements>
}
)");
templ("functionName", functionName);
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
templ("minimumSize", to_string(headSize(decodingTypes)));
string decodeElements;
@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
R"(
{
let offset := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
if gt(offset, 0xffffffffffffffff) { <revertString> }
<values> := <abiDecode>(add(headStart, offset), dataEnd)
}
)" :
@ -222,6 +223,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
}
)"
);
// TODO add test
elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset"));
elementTempl("load", _fromMemory ? "mload" : "calldataload");
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
elementTempl("pos", to_string(headPos));
@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
else
templ("scaleLengthByStride",
Whiskers(R"(
if gt(length, <maxLength>) { revert(0, 0) }
if gt(length, <maxLength>) { <revertString> }
length := mul(length, <stride>)
)")
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
("revertString", revertReasonIfDebug("ABI encoding: array data too long"))
.render()
// TODO add revert test
);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
R"(
// <readableTypeName>
function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
let length := <retrieveLength>
array := <allocate>(<allocationSize>(length))
let dst := array
@ -1141,6 +1146,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
}
)"
);
// TODO add test
templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
}
else
{
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }");
templ("staticBoundsCheck", "if gt(add(src, mul(length, " +
calldataStride +
")), end) { " +
revertReasonIfDebug("ABI decoding: invalid calldata array stride") +
" }"
);
templ("retrieveElementPos", "src");
}
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
templ = R"(
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos, length {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
length := calldataload(offset)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
arrayPos := add(offset, 0x20)
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos> }
}
)";
else
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos {
arrayPos := offset
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos> }
}
)";
Whiskers w{templ};
// TODO add test
w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length"));
w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride"));
w("functionName", functionName);
w("readableTypeName", _type.toString(true));
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
Whiskers templ(
R"(
function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
let length := <load>(offset)
array := <allocate>(<allocationSize>(length))
mstore(array, length)
let src := add(offset, 0x20)
let dst := add(array, 0x20)
if gt(add(src, length), end) { revert(0, 0) }
if gt(add(src, length), end) { <revertStringLength> }
<copyToMemFun>(src, dst, length)
}
)"
);
// TODO add test
templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset"));
templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length"));
templ("functionName", functionName);
templ("load", _fromMemory ? "mload" : "calldataload");
templ("allocate", m_utils.allocationFunction());
@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
Whiskers w{R"(
// <readableTypeName>
function <functionName>(offset, end) -> value {
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
if slt(sub(end, offset), <minimumSize>) { <revertString> }
value := offset
}
)"};
// TODO add test
w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short"));
w("functionName", functionName);
w("readableTypeName", _type.toString(true));
w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true)));
@ -1277,7 +1298,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
Whiskers templ(R"(
// <readableTypeName>
function <functionName>(headStart, end) -> value {
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
value := <allocate>(<memorySize>)
<#members>
{
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
</members>
}
)");
// TODO add test
templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short"));
templ("functionName", functionName);
templ("readableTypeName", _type.toString(true));
templ("allocate", m_utils.allocationFunction());
@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
dynamic ?
R"(
let offset := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
if gt(offset, 0xffffffffffffffff) { <revertString> }
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)" :
R"(
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)"
);
// TODO add test
memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset"));
memberTempl("load", _fromMemory ? "mload" : "calldataload");
memberTempl("pos", to_string(headPos));
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
Whiskers w(R"(
function <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset> }
value := add(rel_offset_of_tail, base_ref)
<handleLength>
}
@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
w("handleLength", Whiskers(R"(
length := calldataload(value)
value := add(value, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
)")
("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride()))
// TODO add test
("revertStringLength", revertReasonIfDebug("Invalid calldata access length"))
// TODO add test
("revertStringStride", revertReasonIfDebug("Invalid calldata access stride"))
.render());
w("return", "value, length");
}
else
@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
}
w("neededLength", toCompactHexWithPrefix(tailSize));
w("functionName", functionName);
w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset"));
return w.render();
}
else if (_type.isValueType())
@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons
else
return _type.sizeOnStack();
}
std::string ABIFunctions::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -25,6 +25,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h>
#include <functional>
@ -55,11 +57,13 @@ class ABIFunctions
public:
explicit ABIFunctions(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)),
m_utils(_evmVersion, m_functionCollector)
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
{}
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
@ -249,7 +253,12 @@ private:
/// is true), for which it is two.
static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options);
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
std::set<std::string> m_externallyUsedFunctions;
YulUtilFunctions m_utils;

View File

@ -35,7 +35,7 @@ void Compiler::compileContract(
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings);
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata);
@ -45,7 +45,7 @@ void Compiler::compileContract(
// The creation code will be executed at most once, so we modify the optimizer
// settings accordingly.
creationSettings.expectedExecutionsPerDeployment = 1;
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings);
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimiserSettings);

View File

@ -37,9 +37,8 @@ class Compiler
public:
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeContext(_evmVersion),
m_context(_evmVersion, &m_runtimeContext)
m_runtimeContext(_evmVersion, _revertStrings),
m_context(_evmVersion, _revertStrings, &m_runtimeContext)
{ }
/// Compiles a contract.
@ -80,7 +79,6 @@ public:
private:
OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
CompilerContext m_runtimeContext;
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
CompilerContext m_context;

View File

@ -37,6 +37,8 @@
#include <libyul/Object.h>
#include <libyul/YulString.h>
#include <libsolutil/Whiskers.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/SourceReferenceFormatter.h>
@ -55,6 +57,7 @@
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm;
using namespace solidity::frontend;
using namespace solidity::langutil;
@ -296,12 +299,13 @@ CompilerContext& CompilerContext::appendConditionalInvalid()
return *this;
}
CompilerContext& CompilerContext::appendRevert()
CompilerContext& CompilerContext::appendRevert(string const& _message)
{
return *this << u256(0) << u256(0) << Instruction::REVERT;
appendInlineAssembly("{ " + revertReasonIfDebug(_message) + " }");
return *this;
}
CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData, string const& _message)
{
if (_forwardReturnData && m_evmVersion.supportsReturndata())
appendInlineAssembly(R"({
@ -311,9 +315,7 @@ CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnDat
}
})", {"condition"});
else
appendInlineAssembly(R"({
if condition { revert(0, 0) }
})", {"condition"});
appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"});
*this << Instruction::POP;
return *this;
}
@ -488,6 +490,11 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::superContract
return ++it;
}
string CompilerContext::revertReasonIfDebug(string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}
void CompilerContext::updateSourceLocation()
{
m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location());

View File

@ -27,6 +27,7 @@
#include <libsolidity/ast/Types.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libevmasm/Assembly.h>
@ -51,11 +52,16 @@ class Compiler;
class CompilerContext
{
public:
explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr):
explicit CompilerContext(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
CompilerContext* _runtimeContext = nullptr
):
m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion)
m_abiFunctions(m_evmVersion, m_revertStrings)
{
if (m_runtimeContext)
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
@ -160,12 +166,14 @@ public:
/// Appends a conditional INVALID instruction
CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call
CompilerContext& appendRevert();
/// @param _message is an optional revert message used in debug mode
CompilerContext& appendRevert(std::string const& _message = "");
/// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
/// empty string. Consumes the condition.
/// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
/// the data.
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false);
/// @param _message is an optional revert message used in debug mode
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false, std::string const& _message = "");
/// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(
evmasm::AssemblyItem const& _tag,
@ -219,6 +227,11 @@ public:
OptimiserSettings const& _optimiserSettings = OptimiserSettings::none()
);
/// If m_revertStrings is debug, @returns inline assembly code that
/// stores @param _message in memory position 0 and reverts.
/// Otherwise returns "revert(0, 0)".
std::string revertReasonIfDebug(std::string const& _message = "");
/// Appends arbitrary data to the end of the bytecode.
void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); }
@ -263,6 +276,8 @@ public:
void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; }
RevertStrings revertStrings() const { return m_revertStrings; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.
@ -312,6 +327,7 @@ private:
evmasm::AssemblyPointer m_asm;
/// Version of the EVM to compile against.
langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
/// Activated experimental features.
std::set<ExperimentalFeature> m_experimentalFeatures;
/// Other already compiled contracts to be used in contract creation calls.

View File

@ -130,9 +130,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
// returns the absolute offset of the tail in "base_ref"
m_context.appendInlineAssembly(Whiskers(R"({
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
base_ref := add(base_ref, rel_offset_of_tail)
})")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"});
})")
("neededLength", toCompactHexWithPrefix(tailSize))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
.render(), {"base_ref", "ptr_to_tail"});
// stack layout: <absolute_offset_of_tail> <garbage>
if (!_type.isDynamicallySized())
@ -158,9 +161,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
Whiskers(R"({
length := calldataload(base_ref)
base_ref := add(base_ref, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if gt(length, 0xffffffffffffffff) { <revertString> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
})")
("calldataStride", toCompactHexWithPrefix(calldataStride))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
.render(),
{"base_ref", "length"}
);
// stack layout: <absolute_offset_of_tail> <length>
@ -277,7 +283,13 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
size_t encodedSize = 0;
for (auto const& t: _typeParameters)
encodedSize += t->decodingType()->calldataHeadSize();
m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
Whiskers templ(R"({
if lt(len, <encodedSize>) { <revertString> }
})");
templ("encodedSize", to_string(encodedSize));
templ("revertString", m_context.revertReasonIfDebug("Calldata too short"));
m_context.appendInlineAssembly(templ.render(), {"len"});
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
@ -319,19 +331,23 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
// Check that the data pointer is valid and that length times
// item size is still inside the range.
Whiskers templ(R"({
if gt(ptr, 0x100000000) { revert(0, 0) }
if gt(ptr, 0x100000000) { <revertStringPointer> }
ptr := add(ptr, base_offset)
let array_data_start := add(ptr, 0x20)
if gt(array_data_start, input_end) { revert(0, 0) }
if gt(array_data_start, input_end) { <revertStringStart> }
let array_length := mload(ptr)
if or(
gt(array_length, 0x100000000),
gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
) { revert(0, 0) }
) { <revertStringLength> }
mstore(dst, array_length)
dst := add(dst, 0x20)
})");
templ("item_size", to_string(arrayType.calldataStride()));
// TODO add test
templ("revertStringPointer", m_context.revertReasonIfDebug("ABI memory decoding: invalid data pointer"));
templ("revertStringStart", m_context.revertReasonIfDebug("ABI memory decoding: invalid data start"));
templ("revertStringLength", m_context.revertReasonIfDebug("ABI memory decoding: invalid data length"));
m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"});
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata
m_context << Instruction::SWAP1;
@ -359,24 +375,33 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory);
m_context << Instruction::SWAP1;
// stack: input_end base_offset next_pointer data_offset
m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
m_context.appendInlineAssembly(Whiskers(R"({
if gt(data_offset, 0x100000000) { <revertString> }
})")
// TODO add test
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data offset"))
.render(), {"data_offset"});
m_context << Instruction::DUP3 << Instruction::ADD;
// stack: input_end base_offset next_pointer array_head_ptr
m_context.appendInlineAssembly(
"{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
{"input_end", "base_offset", "next_ptr", "array_head_ptr"}
);
m_context.appendInlineAssembly(Whiskers(R"({
if gt(add(array_head_ptr, 0x20), input_end) { <revertString> }
})")
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer"))
.render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"});
// retrieve length
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true);
// stack: input_end base_offset next_pointer array_length data_pointer
m_context << Instruction::SWAP2;
// stack: input_end base_offset data_pointer array_length next_pointer
m_context.appendInlineAssembly(R"({
m_context.appendInlineAssembly(Whiskers(R"({
if or(
gt(array_length, 0x100000000),
gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end)
) { revert(0, 0) }
})", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
) { <revertString> }
})")
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer"))
.render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
}
else
{
@ -792,8 +817,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding)
// TODO: error message?
m_context.appendConditionalRevert();
m_context.appendConditionalRevert(false, "Enum out of range");
else
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;

View File

@ -24,6 +24,7 @@
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerContext.h>
@ -34,7 +35,8 @@ class Type; // forward
class CompilerUtils
{
public:
explicit CompilerUtils(CompilerContext& _context): m_context(_context) {}
explicit CompilerUtils(CompilerContext& _context): m_context(_context)
{}
/// Stores the initial value of the free-memory-pointer at its position;
void initialiseFreeMemoryPointer();

View File

@ -128,8 +128,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
// TODO: error message?
m_context.appendConditionalRevert();
m_context.appendConditionalRevert(false, "Ether sent to non-payable function");
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@ -409,7 +408,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << notFoundOrReceiveEther;
if (!fallback && !etherReceiver)
m_context.appendRevert();
m_context.appendRevert("Contract does not have fallback nor receive functions");
else
{
if (etherReceiver)
@ -440,8 +439,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP;
}
else
// TODO: error message here?
m_context.appendRevert();
m_context.appendRevert("Unknown signature and no fallback defined");
}
@ -457,7 +455,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
// If the function is not a view function and is called without DELEGATECALL,
// we revert.
m_context << dupInstruction(2);
m_context.appendConditionalRevert();
m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL");
}
m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous
@ -517,7 +515,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
}
bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
@ -530,10 +528,10 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
m_continueTags.clear();
if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration);
else
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration);
return false;
@ -954,7 +952,8 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
revert(0, returndatasize())
})");
else
m_context.appendRevert();
// Since both returndata and revert are >=byzantium, this should be unreachable.
solAssert(false, "");
}
m_context << endTag;
}
@ -1316,7 +1315,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con
void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType)
{
ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals);
ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals);
expressionCompiler.compile(_expression);
if (_targetType)
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);

View File

@ -43,11 +43,9 @@ public:
explicit ContractCompiler(
ContractCompiler* _runtimeCompiler,
CompilerContext& _context,
OptimiserSettings _optimiserSettings,
RevertStrings _revertStrings
OptimiserSettings _optimiserSettings
):
m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeCompiler(_runtimeCompiler),
m_context(_context)
{
@ -140,7 +138,6 @@ private:
void storeStackHeight(ASTNode const* _node);
OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;

View File

@ -677,7 +677,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << errorCase;
}
else
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
break;
}
@ -734,8 +733,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer)
{
// Check if zero (out of stack or not enough balance).
// TODO: bubble up here, but might also be different error.
m_context << Instruction::ISZERO;
// Revert message bubbles up.
m_context.appendConditionalRevert(true);
}
break;
@ -752,7 +751,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, "");
if (m_revertStrings == RevertStrings::Strip)
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.front()->annotation().isPure)
{
@ -1032,7 +1031,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip;
bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip;
if (arguments.size() > 1)
{
@ -1041,7 +1040,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function call.
solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, "");
if (m_revertStrings == RevertStrings::Strip)
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.at(1)->annotation().isPure)
{
@ -1821,12 +1820,16 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
m_context.appendInlineAssembly(
Whiskers(R"({
if gt(sliceStart, sliceEnd) { revert(0, 0) }
if gt(sliceEnd, length) { revert(0, 0) }
if gt(sliceStart, sliceEnd) { <revertStringStartEnd> }
if gt(sliceEnd, length) { <revertStringEndLength> }
offset := add(offset, mul(sliceStart, <stride>))
length := sub(sliceEnd, sliceStart)
})")("stride", toString(arrayType->calldataStride())).render(),
})")
("stride", toString(arrayType->calldataStride()))
("revertStringStartEnd", m_context.revertReasonIfDebug("Slice starts after end"))
("revertStringEndLength", m_context.revertReasonIfDebug("Slice is greater than length"))
.render(),
{"offset", "length", "sliceStart", "sliceEnd"}
);
@ -2299,8 +2302,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
m_context.appendConditionalRevert();
m_context.appendConditionalRevert(false, "Target contract does not contain code");
existenceChecked = true;
}

View File

@ -58,10 +58,8 @@ class ExpressionCompiler: private ASTConstVisitor
public:
ExpressionCompiler(
CompilerContext& _compilerContext,
RevertStrings _revertStrings,
bool _optimiseOrderLiterals
):
m_revertStrings(_revertStrings),
m_optimiseOrderLiterals(_optimiseOrderLiterals),
m_context(_compilerContext)
{}
@ -139,7 +137,6 @@ private:
/// @returns the CompilerUtils object containing the current context.
CompilerUtils utils();
RevertStrings m_revertStrings;
bool m_optimiseOrderLiterals;
CompilerContext& m_context;
std::unique_ptr<LValue> m_currentLValue;

View File

@ -134,7 +134,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
FixedHash<hashHeaderSize>(keccak256("Error(string)"))
)) << (256 - hashHeaderSize * byteSize);
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
@ -1627,7 +1627,7 @@ string YulUtilFunctions::packedHashFunction(
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
templ("comma", sizeOnStack > 0 ? "," : "");
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
templ("packedEncode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
return templ.render();
});
}
@ -1905,3 +1905,41 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
.render();
});
}
string YulUtilFunctions::revertReasonIfDebug(RevertStrings revertStrings, string const& _message)
{
if (revertStrings >= RevertStrings::Debug && !_message.empty())
{
Whiskers templ(R"({
mstore(0, <sig>)
mstore(4, 0x20)
mstore(add(4, 0x20), <length>)
let reasonPos := add(4, 0x40)
<#word>
mstore(add(reasonPos, <offset>), <wordValue>)
</word>
revert(0, add(reasonPos, <end>))
})");
templ("sig", (u256(util::FixedHash<4>::Arith(util::FixedHash<4>(util::keccak256("Error(string)")))) << (256 - 32)).str());
templ("length", to_string(_message.length()));
size_t words = (_message.length() + 31) / 32;
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_message.substr(32 * i, 32));
}
templ("word", wordParams);
templ("end", to_string(words * 32));
return templ.render();
}
else
return "revert(0, 0)";
}
string YulUtilFunctions::revertReasonIfDebug(string const& _message)
{
return revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -24,6 +24,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/interface/DebugSettings.h>
#include <memory>
#include <string>
#include <vector>
@ -44,9 +46,11 @@ class YulUtilFunctions
public:
explicit YulUtilFunctions(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector))
{}
@ -281,6 +285,13 @@ public:
/// zero
/// signature: (slot, offset) ->
std::string storageSetToZeroFunction(Type const& _type);
/// If revertStrings is debug, @returns inline assembly code that
/// stores @param _message in memory position 0 and reverts.
/// Otherwise returns "revert(0, 0)".
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
std::string revertReasonIfDebug(std::string const& _message = "");
private:
/// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value.
@ -289,6 +300,7 @@ private:
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
};

View File

@ -133,7 +133,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
)");
templ("functionName", funName);
templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_functions);
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
templ("in", suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
@ -161,5 +161,10 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
YulUtilFunctions IRGenerationContext::utils()
{
return YulUtilFunctions(m_evmVersion, m_functions);
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
}
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -21,6 +21,7 @@
#pragma once
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
@ -47,8 +48,13 @@ class YulUtilFunctions;
class IRGenerationContext
{
public:
IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
IRGenerationContext(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_optimiserSettings(std::move(_optimiserSettings)),
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
{}
@ -92,8 +98,15 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
RevertStrings revertStrings() const { return m_revertStrings; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables;

View File

@ -333,7 +333,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ";
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := ";
ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector());
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
templ["params"] = suffixedVariableNameList("param_", 0, paramVars);
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
@ -386,8 +386,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
m_context.functionCollector()->requestedFunctions().empty(),
"Reset context while it still had functions."
);
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables())

View File

@ -37,11 +37,15 @@ class SourceUnit;
class IRGenerator
{
public:
IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
IRGenerator(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings
):
m_evmVersion(_evmVersion),
m_optimiserSettings(_optimiserSettings),
m_context(_evmVersion, std::move(_optimiserSettings)),
m_utils(_evmVersion, m_context.functionCollector())
m_context(_evmVersion, _revertStrings, std::move(_optimiserSettings)),
m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())
{}
/// Generates and returns the IR code, in unoptimized and optimized form

View File

@ -534,7 +534,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
auto const& event = dynamic_cast<EventDefinition const&>(functionType->declaration());
TypePointers paramTypes = functionType->parameterTypes();
ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector());
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
vector<string> indexedArgs;
string nonIndexedArgs;
@ -1151,7 +1151,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n";
}
ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector());
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
solUnimplementedAssert(!funType.isBareCall(), "");
Whiskers templ(R"(

View File

@ -165,7 +165,7 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
{
if (m_stackState >= ParsingPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing."));
solUnimplementedAssert(_revertStrings == RevertStrings::Default || _revertStrings == RevertStrings::Strip, "");
solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug, "");
m_revertStrings = _revertStrings;
}
@ -1112,7 +1112,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
for (auto const* dependency: _contract.annotation().contractDependencies)
generateIR(*dependency);
IRGenerator generator(m_evmVersion, m_optimiserSettings);
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
}

View File

@ -661,10 +661,10 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString());
if (!revertStrings)
return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings.");
if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip)
if (*revertStrings == RevertStrings::VerboseDebug)
return formatFatalError(
"UnimplementedFeatureError",
"Only \"default\" and \"strip\" are implemented for settings.debug.revertStrings for now."
"Only \"default\", \"strip\" and \"debug\" are implemented for settings.debug.revertStrings for now."
);
ret.revertStrings = *revertStrings;
}

View File

@ -875,9 +875,9 @@ Allowed options)",
serr() << "Invalid option for --" << g_strRevertStrings << ": " << revertStringsString << endl;
return false;
}
if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip)
if (*revertStrings == RevertStrings::VerboseDebug)
{
serr() << "Only \"default\" and \"strip\" are implemented for --" << g_strRevertStrings << " for now." << endl;
serr() << "Only \"default\", \"strip\" and \"debug\" are implemented for --" << g_strRevertStrings << " for now." << endl;
return false;
}
m_revertStrings = *revertStrings;

View File

@ -169,26 +169,32 @@ BOOST_AUTO_TEST_CASE(location_test)
if (solidity::test::CommonOptions::get().optimize)
locations =
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 14, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{2, 4, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(19, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{24, 25, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{17, 29, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{2, 4, codegenCharStream}) +
vector<SourceLocation>(16, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{12, 13, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{9, 10, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{2, 14, codegenCharStream}) +
vector<SourceLocation>(21, SourceLocation{20, 79, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
vector<SourceLocation>(2, SourceLocation{20, 79, sourceCode});
else
locations =
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 14, codegenCharStream}) +
vector<SourceLocation>(3, SourceLocation{2, 4, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
vector<SourceLocation>(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{24, 25, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{17, 29, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{2, 4, codegenCharStream}) +
vector<SourceLocation>(hasShifts ? 16 : 17, SourceLocation{2, 82, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{12, 13, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{9, 10, codegenCharStream}) +
vector<SourceLocation>(1, SourceLocation{2, 14, codegenCharStream}) +
vector<SourceLocation>(24, SourceLocation{20, 79, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{49, 58, sourceCode}) +
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +

View File

@ -70,6 +70,16 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
}
m_settings.erase("ABIEncoderV1Only");
}
if (m_settings.count("revertStrings"))
{
auto revertStrings = revertStringsFromString(m_settings["revertStrings"]);
if (revertStrings)
m_revertStrings = *revertStrings;
m_validatedSettings["revertStrings"] = revertStringsToString(m_revertStrings);
m_settings.erase("revertStrings");
}
parseExpectations(file);
soltestAssert(!m_tests.empty(), "No tests specified in " + _filename);
}

View File

@ -51,6 +51,7 @@ bytes SolidityExecutionFramework::compileContract(
m_compiler.setEVMVersion(m_evmVersion);
m_compiler.setOptimiserSettings(m_optimiserSettings);
m_compiler.enableIRGeneration(m_compileViaYul);
m_compiler.setRevertStringBehaviour(m_revertStrings);
if (!m_compiler.compile())
{
langutil::SourceReferenceFormatter formatter(std::cerr);

View File

@ -27,6 +27,7 @@
#include <test/ExecutionFramework.h>
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libyul/AssemblyStack.h>
@ -67,6 +68,8 @@ public:
protected:
solidity::frontend::CompilerStack m_compiler;
bool m_compileViaYul = false;
RevertStrings m_revertStrings = RevertStrings::Default;
};
} // end namespaces

View File

@ -139,7 +139,10 @@ bytes compileFirstExpression(
FirstExpressionExtractor extractor(*contract);
BOOST_REQUIRE(extractor.expression() != nullptr);
CompilerContext context(solidity::test::CommonOptions::get().evmVersion());
CompilerContext context(
solidity::test::CommonOptions::get().evmVersion(),
RevertStrings::Default
);
context.resetVisitedNodes(contract);
context.setInheritanceHierarchy(inheritanceHierarchy);
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
@ -152,7 +155,6 @@ bytes compileFirstExpression(
ExpressionCompiler(
context,
RevertStrings::Default,
solidity::test::CommonOptions::get().optimize
).compile(*extractor.expression());

View File

@ -366,14 +366,15 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(contract["evm"]["assembly"].isString());
BOOST_CHECK(contract["evm"]["assembly"].asString().find(
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n "
"callvalue\n /* \"--CODEGEN--\":8:17 */\n dup1\n "
"/* \"--CODEGEN--\":5:7 */\n iszero\n tag_1\n jumpi\n "
"/* \"--CODEGEN--\":30:31 */\n 0x00\n /* \"--CODEGEN--\":27:28 */\n "
"dup1\n /* \"--CODEGEN--\":20:32 */\n revert\n /* \"--CODEGEN--\":5:7 */\n"
"callvalue\n /* \"--CODEGEN--\":5:14 */\n dup1\n "
"/* \"--CODEGEN--\":2:4 */\n iszero\n tag_1\n jumpi\n "
"/* \"--CODEGEN--\":27:28 */\n 0x00\n /* \"--CODEGEN--\":24:25 */\n "
"dup1\n /* \"--CODEGEN--\":17:29 */\n revert\n /* \"--CODEGEN--\":2:4 */\n"
"tag_1:\n /* \"fileA\":0:14 contract A { } */\n pop\n dataSize(sub_0)\n dup1\n "
"dataOffset(sub_0)\n 0x00\n codecopy\n 0x00\n return\nstop\n\nsub_0: assembly {\n "
"/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n 0x00\n "
"dup1\n revert\n\n auxdata: 0xa26469706673582212"
"/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n "
"/* \"--CODEGEN--\":12:13 */\n 0x00\n /* \"--CODEGEN--\":9:10 */\n "
"dup1\n /* \"--CODEGEN--\":2:14 */\n revert\n\n auxdata: 0xa26469706673582212"
) == 0);
BOOST_CHECK(contract["evm"]["gasEstimates"].isObject());
BOOST_CHECK_EQUAL(contract["evm"]["gasEstimates"].size(), 1);
@ -397,15 +398,15 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
"{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"value\":\"40\"},"
"{\"begin\":0,\"end\":14,\"name\":\"MSTORE\"},"
"{\"begin\":0,\"end\":14,\"name\":\"CALLVALUE\"},"
"{\"begin\":8,\"end\":17,\"name\":\"DUP1\"},"
"{\"begin\":5,\"end\":7,\"name\":\"ISZERO\"},"
"{\"begin\":5,\"end\":7,\"name\":\"PUSH [tag]\",\"value\":\"1\"},"
"{\"begin\":5,\"end\":7,\"name\":\"JUMPI\"},"
"{\"begin\":30,\"end\":31,\"name\":\"PUSH\",\"value\":\"0\"},"
"{\"begin\":27,\"end\":28,\"name\":\"DUP1\"},"
"{\"begin\":20,\"end\":32,\"name\":\"REVERT\"},"
"{\"begin\":5,\"end\":7,\"name\":\"tag\",\"value\":\"1\"},"
"{\"begin\":5,\"end\":7,\"name\":\"JUMPDEST\"},"
"{\"begin\":5,\"end\":14,\"name\":\"DUP1\"},"
"{\"begin\":2,\"end\":4,\"name\":\"ISZERO\"},"
"{\"begin\":2,\"end\":4,\"name\":\"PUSH [tag]\",\"value\":\"1\"},"
"{\"begin\":2,\"end\":4,\"name\":\"JUMPI\"},"
"{\"begin\":27,\"end\":28,\"name\":\"PUSH\",\"value\":\"0\"},"
"{\"begin\":24,\"end\":25,\"name\":\"DUP1\"},"
"{\"begin\":17,\"end\":29,\"name\":\"REVERT\"},"
"{\"begin\":2,\"end\":4,\"name\":\"tag\",\"value\":\"1\"},"
"{\"begin\":2,\"end\":4,\"name\":\"JUMPDEST\"},"
"{\"begin\":0,\"end\":14,\"name\":\"POP\"},"
"{\"begin\":0,\"end\":14,\"name\":\"PUSH #[$]\",\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":0,\"end\":14,\"name\":\"DUP1\"},"

View File

@ -0,0 +1,11 @@
contract C {
function f(uint256 start, uint256 end, uint256[] calldata arr) external pure {
arr[start:end];
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256,uint256,uint256[]): 2, 1, 0x80, 3, 1, 2, 3 -> FAILURE, hex"08c379a0", 0x20, 22, "Slice starts after end"
// f(uint256,uint256,uint256[]): 1, 5, 0x80, 3, 1, 2, 3 -> FAILURE, hex"08c379a0", 0x20, 28, "Slice is greater than length"

View File

@ -0,0 +1,15 @@
contract A {
function g() public { revert("fail"); }
}
contract C {
A a = new A();
function f() public {
a.g();
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f() -> FAILURE, hex"08c379a0", 0x20, 4, "fail"

View File

@ -0,0 +1,11 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][] calldata a) external returns (uint) {
return 42;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256[][]): 0x20, 1 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray stride"

View File

@ -0,0 +1,12 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][2][] calldata x) external returns (uint256) {
x[0];
return 23;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE, hex"08c379a0", 0x20, 28, "Invalid calldata tail offset"

View File

@ -0,0 +1,14 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][2][] calldata x) external returns (uint256) {
return 42;
}
function g(uint256[][2][] calldata x) external returns (uint256) {
return this.f(x);
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE, hex"08c379a0", 0x20, 30, "Invalid calldata access offset"

View File

@ -0,0 +1,11 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][] calldata x) external returns (uint256) {
return x[0].length;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256[][]): 0x20, 1, 0x20, 0x0100000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 28, "Invalid calldata tail length"

View File

@ -0,0 +1,11 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint a, uint[] calldata b, uint c) external pure returns (uint) {
return 7;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256,uint256[],uint256): 6, 0x60, 9, 0x1000000000000000000000000000000000000000000000000000000000000002, 1, 2 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray length"

View File

@ -0,0 +1,11 @@
contract C {
function d(bytes memory _data) public pure returns (uint8) {
return abi.decode(_data, (uint8));
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ABIEncoderV1Only: true
// ----
// d(bytes): 0x20, 0x01, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 18, "Calldata too short"

View File

@ -0,0 +1,12 @@
contract C {
function f() external {}
function g() external {
C c = C(0x0000000000000000000000000000000000000000000000000000000000000000);
c.f();
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// g() -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code"

View File

@ -0,0 +1,12 @@
contract C {
enum E {X, Y}
function f(E[] calldata arr) external {
arr[1];
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ABIEncoderV1Only: true
// ----
// f(uint8[]): 0x20, 2, 3, 3 -> FAILURE, hex"08c379a0", 0x20, 17, "Enum out of range"

View File

@ -0,0 +1,9 @@
contract C {
function f() public {}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(), 1 ether -> FAILURE, hex"08c379a0", 0x20, 34, "Ether sent to non-payable functi", "on"
// () -> FAILURE, hex"08c379a0", 0x20, 53, "Contract does not have fallback ", "nor receive functions"

View File

@ -0,0 +1,9 @@
contract C {
function t(uint) public pure {}
}
// ====
// EVMVersion: >=byzantium
// compileViaYul: true
// revertStrings: debug
// ----
// t(uint256) -> FAILURE, hex"08c379a0", 0x20, 34, "ABI decoding: tuple data too sho", "rt"

View File

@ -0,0 +1,13 @@
contract C {
function d(bytes memory _data) public pure returns (uint8) {
return abi.decode(_data, (uint8));
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ABIEncoderV1Only: true
// ----
// d(bytes): 0x20, 0x20, 0x0000000000000000000000000000000000000000000000000000000000000000 -> 0
// d(bytes): 0x100, 0x20, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI calldata decoding: invalid h", "ead pointer"
// d(bytes): 0x20, 0x100, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI calldata decoding: invalid d", "ata pointer"

View File

@ -0,0 +1,20 @@
contract C {
function dyn(uint ptr, uint start, uint x) public returns (bytes memory a) {
assembly {
mstore(0, start)
mstore(start, add(start, 1))
return(ptr, x)
}
}
function f(uint ptr, uint start, uint x) public returns (bool) {
this.dyn(ptr, start, x);
return true;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ABIEncoderV1Only: true
// ----
// f(uint256,uint256,uint256): 0, 0x200, 0x60 -> FAILURE, hex"08c379a0", 0x20, 39, "ABI memory decoding: invalid dat", "a start"
// f(uint256,uint256,uint256): 0, 0x20, 0x60 -> FAILURE, hex"08c379a0", 0x20, 40, "ABI memory decoding: invalid dat", "a length"

View File

@ -0,0 +1,16 @@
library L {
function g() external {}
}
contract C {
function f() public returns (bytes memory) {
(bool success, bytes memory result) = address(L).call(abi.encodeWithSignature("g()"));
assert(!success);
return result;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// library: L
// f() -> 32, 132, 3963877391197344453575983046348115674221700746820753546331534351508065746944, 862718293348820473429344482784628181556388621521298319395315527974912, 1518017211910606845658622928256476421055725129218887721595913401102969, 14649601406562900601407788686537400806574002225747213573947654179243427889152, 0

View File

@ -0,0 +1,9 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint[] memory a) public pure returns (uint) { return 7; }
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// f(uint256[]): 0x20, 1 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray stride"

View File

@ -0,0 +1,9 @@
pragma experimental ABIEncoderV2;
contract C {
function e(bytes memory a) public pure returns (uint) { return 7; }
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// e(bytes): 0x20, 7 -> FAILURE, hex"08c379a0", 0x20, 39, "ABI decoding: invalid byte array", " length"

View File

@ -0,0 +1,27 @@
contract A {
receive() external payable {
revert("no_receive");
}
}
contract C {
A a = new A();
receive() external payable {}
function f() public {
address(a).transfer(1 wei);
}
function h() public {
address(a).transfer(100 ether);
}
function g() public view returns (uint) {
return address(this).balance;
}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// (), 10 ether ->
// g() -> 10
// f() -> FAILURE, hex"08c379a0", 0x20, 10, "no_receive"
// h() -> FAILURE

View File

@ -0,0 +1,8 @@
contract A {
receive () external payable {}
}
// ====
// EVMVersion: >=byzantium
// revertStrings: debug
// ----
// (): hex"00" -> FAILURE, hex"08c379a0", 0x20, 41, "Unknown signature and no fallbac", "k defined"