Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change translation of implicit throws #1598

Merged
merged 9 commits into from
Jan 27, 2017
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
};

Expand Down Expand Up @@ -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 } }
};

Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
1 change: 1 addition & 0 deletions libevmasm/PeepholeOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/codegen/ArrayUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 30 additions & 5 deletions libsolidity/codegen/CompilerContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions libsolidity/codegen/CompilerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ class CompilerContext
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();

Expand All @@ -127,6 +137,10 @@ class CompilerContext
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
Expand Down
8 changes: 5 additions & 3 deletions libsolidity/codegen/CompilerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
}
Expand Down
8 changes: 5 additions & 3 deletions libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 6 additions & 6 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions test/libsolidity/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
10 changes: 8 additions & 2 deletions test/libsolidity/SolidityExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down