Merge pull request #1598 from wuestholz/develop

Change translation of implicit throws
This commit is contained in:
chriseth 2017-01-27 14:10:22 +01:00 committed by GitHub
commit b2c35fb41a
14 changed files with 85 additions and 23 deletions

View File

@ -9,6 +9,8 @@ Features:
* Metadata: Do not include platform in the version number.
* Metadata: Add option to store sources as literal content.
* Code generator: Extract array utils into low-level functions.
* Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid
instruction (0xfe) instead of an invalid jump. Invalid jump is still kept for explicit throws.
Bugfixes:
* Code generator: Allow recursive structs.

View File

@ -394,8 +394,13 @@ Currently, Solidity automatically generates a runtime exception in the following
#. If you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
#. If your contract receives Ether via a public accessor function.
#. If you call a zero-initialized variable of internal function type.
Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation
(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes
the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect
did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction
(or at least call) without effect.
.. index:: ! assembly, ! asm, ! evmasm

View File

@ -159,6 +159,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CALLCODE", Instruction::CALLCODE },
{ "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL },
{ "INVALID", Instruction::INVALID },
{ "SUICIDE", Instruction::SUICIDE }
};
@ -293,6 +294,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
{ Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } },
{ Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::Special } },
{ Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
{ Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::Zero } }
};

View File

@ -176,6 +176,8 @@ enum class Instruction: uint8_t
CALLCODE, ///< message-call with another account's code only
RETURN, ///< halt execution returning output data
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender
INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
SUICIDE = 0xff ///< halt execution and register account for later deletion
};

View File

@ -199,6 +199,7 @@ struct UnreachableCode
it[0] != Instruction::JUMP &&
it[0] != Instruction::RETURN &&
it[0] != Instruction::STOP &&
it[0] != Instruction::INVALID &&
it[0] != Instruction::SUICIDE
)
return false;

View File

@ -118,6 +118,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
case Instruction::RETURN:
case Instruction::SUICIDE:
case Instruction::STOP:
case Instruction::INVALID:
return true;
default:
return false;

View File

@ -901,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
// check out-of-bounds access
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
}
if (location == DataLocation::CallData && _arrayType.isDynamicallySized())
// remove length if present

View File

@ -70,19 +70,30 @@ void CompilerContext::callLowLevelFunction(
eth::AssemblyItem retTag = pushNewTag();
CompilerUtils(*this).moveIntoStack(_inArgs);
*this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator);
appendJump(eth::AssemblyItem::JumpType::IntoFunction);
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
*this << retTag.tag();
}
eth::AssemblyItem CompilerContext::lowLevelFunctionTag(
string const& _name,
unsigned _inArgs,
unsigned _outArgs,
function<void(CompilerContext&)> const& _generator
)
{
auto it = m_lowLevelFunctions.find(_name);
if (it == m_lowLevelFunctions.end())
{
eth::AssemblyItem tag = newTag().pushTag();
m_lowLevelFunctions.insert(make_pair(_name, tag));
m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
*this << tag;
return tag;
}
else
*this << it->second;
appendJump(eth::AssemblyItem::JumpType::IntoFunction);
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
*this << retTag.tag();
return it->second;
}
void CompilerContext::appendMissingLowLevelFunctions()
@ -215,6 +226,20 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
return *this << item;
}
CompilerContext& CompilerContext::appendInvalid()
{
return *this << Instruction::INVALID;
}
CompilerContext& CompilerContext::appendConditionalInvalid()
{
*this << Instruction::ISZERO;
eth::AssemblyItem afterTag = appendConditionalJump();
*this << Instruction::INVALID;
*this << afterTag;
return *this;
}
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;

View File

@ -104,6 +104,16 @@ public:
unsigned _outArgs,
std::function<void(CompilerContext&)> const& _generator
);
/// Returns the tag of the named low-level function and inserts the generator into the
/// list of low-level-functions to be generated, unless it already exists.
/// Note that the generator should not assume that objects are still alive when it is called,
/// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
eth::AssemblyItem lowLevelFunctionTag(
std::string const& _name,
unsigned _inArgs,
unsigned _outArgs,
std::function<void(CompilerContext&)> const& _generator
);
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
void appendMissingLowLevelFunctions();
@ -127,6 +137,10 @@ public:
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
/// Appends an INVALID instruction
CompilerContext& appendInvalid();
/// Appends a conditional INVALID instruction
CompilerContext& appendConditionalInvalid();
/// Returns an "ErrorTag"
eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
/// Appends a JUMP to a specific tag

View File

@ -468,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
else if (targetTypeCategory == Type::Category::FixedPoint)
@ -807,7 +807,9 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{
if (funType->location() == FunctionType::Location::Internal)
{
m_context << m_context.errorTag();
m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
_context.appendInvalid();
});
return;
}
}

View File

@ -106,7 +106,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@ -271,7 +271,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary());
}
else
m_context.appendJumpTo(m_context.errorTag());
m_context.appendInvalid();
for (auto const& it: interfaceFunctions)
{
@ -918,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
a.appendJumpI(a.errorTag());
a << Instruction::ISZERO;
eth::AssemblyItem afterTag = a.appendJumpI().tag();
a << Instruction::INVALID << afterTag;
//@todo adjust for larger return values, make this dynamic.
a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a);

View File

@ -585,7 +585,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@ -1234,7 +1234,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
@ -1416,7 +1416,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
@ -1477,7 +1477,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
if (c_amountSigned)
{
m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
}
switch (_operator)
@ -1663,7 +1663,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
existenceChecked = true;
}
@ -1699,7 +1699,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(m_context.errorTag());
m_context.appendConditionalInvalid();
}
utils().popStackSlots(remainsSize);

View File

@ -116,8 +116,8 @@ BOOST_AUTO_TEST_CASE(location_test)
shared_ptr<string const> n = make_shared<string>("");
AssemblyItems items = compileContract(sourceCode);
vector<SourceLocation> locations =
vector<SourceLocation>(18, SourceLocation(2, 75, n)) +
vector<SourceLocation>(27, SourceLocation(20, 72, n)) +
vector<SourceLocation>(17, SourceLocation(2, 75, n)) +
vector<SourceLocation>(30, SourceLocation(20, 72, n)) +
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} +
vector<SourceLocation>(2, SourceLocation(58, 67, n)) +
vector<SourceLocation>(3, SourceLocation(20, 72, n));

View File

@ -337,13 +337,19 @@ BOOST_AUTO_TEST_CASE(arithmetics)
byte(Instruction::ADD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x1d,
byte(Instruction::JUMPI),
byte(Instruction::INVALID),
byte(Instruction::JUMPDEST),
byte(Instruction::MOD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x26,
byte(Instruction::JUMPI),
byte(Instruction::INVALID),
byte(Instruction::JUMPDEST),
byte(Instruction::DIV),
byte(Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());