Change translation of implicit throws (issue #1589).

This adds a new invalid instruction that is used for encoding
implicit throws that are emitted by the compiler. This makes it
possible to distinguish such runtime errors from user-provided,
explicit throws.
This commit is contained in:
Valentin Wüstholz 2017-01-22 20:49:12 +01:00 committed by chriseth
parent 102fd7ee5d
commit 9bcbd93ac5
10 changed files with 49 additions and 17 deletions

View File

@ -154,6 +154,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "LOG2", Instruction::LOG2 }, { "LOG2", Instruction::LOG2 },
{ "LOG3", Instruction::LOG3 }, { "LOG3", Instruction::LOG3 },
{ "LOG4", Instruction::LOG4 }, { "LOG4", Instruction::LOG4 },
{ "INVALID", Instruction::INVALID },
{ "CREATE", Instruction::CREATE }, { "CREATE", Instruction::CREATE },
{ "CALL", Instruction::CALL }, { "CALL", Instruction::CALL },
{ "CALLCODE", Instruction::CALLCODE }, { "CALLCODE", Instruction::CALLCODE },
@ -288,6 +289,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } }, { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } },
{ Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } }, { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } },
{ Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } }, { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } },
{ Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
{ Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } }, { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } },
{ Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } },
{ Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },

View File

@ -171,6 +171,8 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics. LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics. LOG4, ///< Makes a log entry; 4 topics.
INVALID = 0xef, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
CREATE = 0xf0, ///< create a new account with associated code CREATE = 0xf0, ///< create a new account with associated code
CALL, ///< message-call into an account CALL, ///< message-call into an account
CALLCODE, ///< message-call with another account's code only CALLCODE, ///< message-call with another account's code only

View File

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

View File

@ -215,6 +215,18 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
return *this << item; return *this << item;
} }
CompilerContext& CompilerContext::appendInvalid()
{
return *this << Instruction::INVALID;
}
CompilerContext& CompilerContext::appendConditionalInvalid()
{
eth::AssemblyItem falseTag = appendConditionalJump();
eth::AssemblyItem endTag = appendJumpToNew();
return *this << falseTag << Instruction::INVALID << endTag;
}
void CompilerContext::resetVisitedNodes(ASTNode const* _node) void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{ {
stack<ASTNode const*> newStack; stack<ASTNode const*> newStack;

View File

@ -127,6 +127,10 @@ public:
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack /// Appends a JUMP to a tag already on the stack
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); 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" /// Returns an "ErrorTag"
eth::AssemblyItem errorTag() { return m_asm->errorTag(); } eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
/// Appends a JUMP to a specific tag /// 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); EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalJumpTo(m_context.errorTag()); m_context.appendConditionalInvalid();
enumOverflowCheckPending = false; enumOverflowCheckPending = false;
} }
break; break;
@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType); EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalJumpTo(m_context.errorTag()); m_context.appendConditionalInvalid();
enumOverflowCheckPending = false; enumOverflowCheckPending = false;
} }
else if (targetTypeCategory == Type::Category::FixedPoint) else if (targetTypeCategory == Type::Category::FixedPoint)
@ -807,7 +807,7 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{ {
if (funType->location() == FunctionType::Location::Internal) if (funType->location() == FunctionType::Location::Internal)
{ {
m_context << m_context.errorTag(); m_context.appendInvalid();
return; return;
} }
} }

View File

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

View File

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

View File

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

View File

@ -337,13 +337,23 @@ BOOST_AUTO_TEST_CASE(arithmetics)
byte(Instruction::ADD), byte(Instruction::ADD),
byte(Instruction::DUP2), byte(Instruction::DUP2),
byte(Instruction::ISZERO), byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x0, byte(Instruction::PUSH1), 0x1e,
byte(Instruction::JUMPI), byte(Instruction::JUMPI),
byte(Instruction::PUSH1), 0x20,
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
byte(Instruction::INVALID),
byte(Instruction::JUMPDEST),
byte(Instruction::MOD), byte(Instruction::MOD),
byte(Instruction::DUP2), byte(Instruction::DUP2),
byte(Instruction::ISZERO), byte(Instruction::ISZERO),
byte(Instruction::PUSH1), 0x0, byte(Instruction::PUSH1), 0x2a,
byte(Instruction::JUMPI), byte(Instruction::JUMPI),
byte(Instruction::PUSH1), 0x2c,
byte(Instruction::JUMP),
byte(Instruction::JUMPDEST),
byte(Instruction::INVALID),
byte(Instruction::JUMPDEST),
byte(Instruction::DIV), byte(Instruction::DIV),
byte(Instruction::MUL)}); byte(Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());