mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
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:
parent
102fd7ee5d
commit
9bcbd93ac5
@ -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 } },
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
|
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user