Checked arithmetic by default.

This commit is contained in:
chriseth 2020-07-22 10:28:04 +02:00
parent 41000fea31
commit 527c073bb9
21 changed files with 405 additions and 98 deletions

View File

@ -15,6 +15,9 @@ Language Features:
* New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName`` * New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName``
AST Changes:
* New node type: unchecked block - used for ``unchecked { ... }``.
### 0.7.4 (unreleased) ### 0.7.4 (unreleased)
Important Bugfixes: Important Bugfixes:

View File

@ -191,6 +191,7 @@ namespace solidity::langutil
K(Throw, "throw", 0) \ K(Throw, "throw", 0) \
K(Try, "try", 0) \ K(Try, "try", 0) \
K(Type, "type", 0) \ K(Type, "type", 0) \
K(Unchecked, "unchecked", 0) \
K(Unicode, "unicode", 0) \ K(Unicode, "unicode", 0) \
K(Using, "using", 0) \ K(Using, "using", 0) \
K(View, "view", 0) \ K(View, "view", 0) \
@ -266,7 +267,6 @@ namespace solidity::langutil
K(Switch, "switch", 0) \ K(Switch, "switch", 0) \
K(Typedef, "typedef", 0) \ K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \ K(TypeOf, "typeof", 0) \
K(Unchecked, "unchecked", 0) \
K(Var, "var", 0) \ K(Var, "var", 0) \
\ \
/* Yul-specific tokens, but not keywords. */ \ /* Yul-specific tokens, but not keywords. */ \

View File

@ -190,6 +190,28 @@ void SyntaxChecker::endVisit(ForStatement const&)
m_inLoopDepth--; m_inLoopDepth--;
} }
bool SyntaxChecker::visit(Block const& _block)
{
if (_block.unchecked())
{
if (m_uncheckedArithmetic)
m_errorReporter.syntaxError(
1941_error,
_block.location(),
"\"unchecked\" blocks cannot be nested."
);
m_uncheckedArithmetic = true;
}
return true;
}
void SyntaxChecker::endVisit(Block const& _block)
{
if (_block.unchecked())
m_uncheckedArithmetic = false;
}
bool SyntaxChecker::visit(Continue const& _continueStatement) bool SyntaxChecker::visit(Continue const& _continueStatement)
{ {
if (m_inLoopDepth <= 0) if (m_inLoopDepth <= 0)
@ -288,8 +310,15 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
return false; return false;
} }
bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(PlaceholderStatement const& _placeholder)
{ {
if (m_uncheckedArithmetic)
m_errorReporter.syntaxError(
2573_error,
_placeholder.location(),
"The placeholder statement \"_\" cannot be used inside an \"unchecked\" block."
);
m_placeholderFound = true; m_placeholderFound = true;
return true; return true;
} }

View File

@ -71,6 +71,9 @@ private:
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
void endVisit(ForStatement const& _forStatement) override; void endVisit(ForStatement const& _forStatement) override;
bool visit(Block const& _block) override;
void endVisit(Block const& _block) override;
bool visit(Continue const& _continueStatement) override; bool visit(Continue const& _continueStatement) override;
bool visit(Break const& _breakStatement) override; bool visit(Break const& _breakStatement) override;
@ -100,6 +103,9 @@ private:
/// Flag that indicates whether some version pragma was present. /// Flag that indicates whether some version pragma was present.
bool m_versionPragmaFound = false; bool m_versionPragmaFound = false;
/// Flag that indicates whether we are inside an unchecked block.
bool m_uncheckedArithmetic = false;
int m_inLoopDepth = 0; int m_inLoopDepth = 0;
std::optional<ContractKind> m_currentContractKind; std::optional<ContractKind> m_currentContractKind;

View File

@ -1384,18 +1384,24 @@ public:
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _docString, ASTPointer<ASTString> const& _docString,
bool _unchecked,
std::vector<ASTPointer<Statement>> _statements std::vector<ASTPointer<Statement>> _statements
): ):
Statement(_id, _location, _docString), m_statements(std::move(_statements)) {} Statement(_id, _location, _docString),
m_statements(std::move(_statements)),
m_unchecked(_unchecked)
{}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; } std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; }
bool unchecked() const { return m_unchecked; }
BlockAnnotation& annotation() const override; BlockAnnotation& annotation() const override;
private: private:
std::vector<ASTPointer<Statement>> m_statements; std::vector<ASTPointer<Statement>> m_statements;
bool m_unchecked;
}; };
/** /**

View File

@ -39,6 +39,8 @@ enum class StateMutability { Pure, View, NonPayable, Payable };
/// Visibility ordered from restricted to unrestricted. /// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Internal, Public, External }; enum class Visibility { Default, Private, Internal, Public, External };
enum class Arithmetic { Checked, Wrapping };
inline std::string stateMutabilityToString(StateMutability const& _stateMutability) inline std::string stateMutabilityToString(StateMutability const& _stateMutability)
{ {
switch (_stateMutability) switch (_stateMutability)

View File

@ -596,7 +596,7 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
bool ASTJsonConverter::visit(Block const& _node) bool ASTJsonConverter::visit(Block const& _node)
{ {
setJsonNode(_node, "Block", { setJsonNode(_node, _node.unchecked() ? "UncheckedBlock" : "Block", {
make_pair("statements", toJson(_node.statements())) make_pair("statements", toJson(_node.statements()))
}); });
return false; return false;

View File

@ -154,7 +154,9 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
if (nodeType == "InlineAssembly") if (nodeType == "InlineAssembly")
return createInlineAssembly(_json); return createInlineAssembly(_json);
if (nodeType == "Block") if (nodeType == "Block")
return createBlock(_json); return createBlock(_json, false);
if (nodeType == "UncheckedBlock")
return createBlock(_json, true);
if (nodeType == "PlaceholderStatement") if (nodeType == "PlaceholderStatement")
return createPlaceholderStatement(_json); return createPlaceholderStatement(_json);
if (nodeType == "IfStatement") if (nodeType == "IfStatement")
@ -439,7 +441,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
modifiers, modifiers,
createParameterList(member(_node, "returnParameters")), createParameterList(member(_node, "returnParameters")),
memberAsBool(_node, "implemented") ? createBlock(member(_node, "body")) : nullptr memberAsBool(_node, "implemented") ? createBlock(member(_node, "body"), false) : nullptr
); );
} }
@ -489,7 +491,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
memberAsBool(_node, "virtual"), memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body")) _node["body"].isNull() ? nullptr: createBlock(member(_node, "body"), false)
); );
} }
@ -589,7 +591,7 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
); );
} }
ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node) ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node, bool _unchecked)
{ {
std::vector<ASTPointer<Statement>> statements; std::vector<ASTPointer<Statement>> statements;
for (auto& stat: member(_node, "statements")) for (auto& stat: member(_node, "statements"))
@ -597,6 +599,7 @@ ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node)
return createASTNode<Block>( return createASTNode<Block>(
_node, _node,
nullOrASTString(_node, "documentation"), nullOrASTString(_node, "documentation"),
_unchecked,
statements statements
); );
} }

View File

@ -93,7 +93,7 @@ private:
ASTPointer<Mapping> createMapping(Json::Value const& _node); ASTPointer<Mapping> createMapping(Json::Value const& _node);
ASTPointer<ArrayTypeName> createArrayTypeName(Json::Value const& _node); ASTPointer<ArrayTypeName> createArrayTypeName(Json::Value const& _node);
ASTPointer<InlineAssembly> createInlineAssembly(Json::Value const& _node); ASTPointer<InlineAssembly> createInlineAssembly(Json::Value const& _node);
ASTPointer<Block> createBlock(Json::Value const& _node); ASTPointer<Block> createBlock(Json::Value const& _node, bool _unchecked);
ASTPointer<PlaceholderStatement> createPlaceholderStatement(Json::Value const& _node); ASTPointer<PlaceholderStatement> createPlaceholderStatement(Json::Value const& _node);
ASTPointer<IfStatement> createIfStatement(Json::Value const& _node); ASTPointer<IfStatement> createIfStatement(Json::Value const& _node);
ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node); ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node);

View File

@ -123,6 +123,9 @@ public:
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; } void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const; ContractDefinition const& mostDerivedContract() const;
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
Arithmetic arithmetic() const { return m_arithmetic; }
/// @returns the next function in the queue of functions that are still to be compiled /// @returns the next function in the queue of functions that are still to be compiled
/// (i.e. that were referenced during compilation but where we did not yet generate code for). /// (i.e. that were referenced during compilation but where we did not yet generate code for).
/// Returns nullptr if the queue is empty. Does not remove the function from the queue, /// Returns nullptr if the queue is empty. Does not remove the function from the queue,
@ -380,6 +383,8 @@ private:
std::map<Declaration const*, std::vector<unsigned>> m_localVariables; std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
/// The contract currently being compiled. Virtual function lookup starts from this contarct. /// The contract currently being compiled. Virtual function lookup starts from this contarct.
ContractDefinition const* m_mostDerivedContract = nullptr; ContractDefinition const* m_mostDerivedContract = nullptr;
/// Whether to use checked arithmetic.
Arithmetic m_arithmetic = Arithmetic::Checked;
/// Stack of current visited AST nodes, used for location attachment /// Stack of current visited AST nodes, used for location attachment
std::stack<ASTNode const*> m_visitedNodes; std::stack<ASTNode const*> m_visitedNodes;
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.

View File

@ -1247,19 +1247,31 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Placeholder cannot be used inside checked block.");
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Arithmetic not reset to 'checked'.");
checker.check(); checker.check();
return true; return true;
} }
bool ContractCompiler::visit(Block const& _block) bool ContractCompiler::visit(Block const& _block)
{ {
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
m_context.setArithmetic(Arithmetic::Wrapping);
}
storeStackHeight(&_block); storeStackHeight(&_block);
return true; return true;
} }
void ContractCompiler::endVisit(Block const& _block) void ContractCompiler::endVisit(Block const& _block)
{ {
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Wrapping, "");
m_context.setArithmetic(Arithmetic::Checked);
}
// Frees local variables declared in the scope of this block. // Frees local variables declared in the scope of this block.
popScopedVariables(&_block); popScopedVariables(&_block);
} }
@ -1327,6 +1339,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
if (codeBlock) if (codeBlock)
{ {
m_context.setArithmetic(Arithmetic::Checked);
std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive(); std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive();
m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures); m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures);

View File

@ -275,7 +275,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
solAssert(*_assignment.annotation().type == leftType, ""); solAssert(*_assignment.annotation().type == leftType, "");
bool cleanupNeeded = false; bool cleanupNeeded = false;
if (op != Token::Assign) if (op != Token::Assign)
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp, m_context.arithmetic());
_assignment.rightHandSide().accept(*this); _assignment.rightHandSide().accept(*this);
// Perform some conversion already. This will convert storage types to memory and literals // Perform some conversion already. This will convert storage types to memory and literals
// to their actual type, but will not convert e.g. memory to storage. // to their actual type, but will not convert e.g. memory to storage.
@ -381,9 +381,10 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
if (_unaryOperation.annotation().type->category() == Type::Category::RationalNumber) Type const& type = *_unaryOperation.annotation().type;
if (type.category() == Type::Category::RationalNumber)
{ {
m_context << _unaryOperation.annotation().type->literalValue(nullptr); m_context << type.literalValue(nullptr);
return false; return false;
} }
@ -406,24 +407,39 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Dec: // -- (pre- or postfix) case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved."); solAssert(!!m_currentLValue, "LValue not retrieved.");
solUnimplementedAssert( solUnimplementedAssert(
_unaryOperation.annotation().type->category() != Type::Category::FixedPoint, type.category() != Type::Category::FixedPoint,
"Not yet implemented - FixedPointType." "Not yet implemented - FixedPointType."
); );
m_currentLValue->retrieveValue(_unaryOperation.location()); m_currentLValue->retrieveValue(_unaryOperation.location());
if (!_unaryOperation.isPrefixOperation()) if (!_unaryOperation.isPrefixOperation())
{ {
// store value for later // store value for later
solUnimplementedAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); solUnimplementedAssert(type.sizeOnStack() == 1, "Stack size != 1 not implemented.");
m_context << Instruction::DUP1; m_context << Instruction::DUP1;
if (m_currentLValue->sizeOnStack() > 0) if (m_currentLValue->sizeOnStack() > 0)
for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i); m_context << swapInstruction(i);
} }
m_context << u256(1);
if (_unaryOperation.getOperator() == Token::Inc) if (_unaryOperation.getOperator() == Token::Inc)
m_context << Instruction::ADD; {
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().incrementCheckedFunction(type), 1, 1);
else
{
m_context << u256(1);
m_context << Instruction::ADD;
}
}
else else
m_context << Instruction::SWAP1 << Instruction::SUB; {
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().decrementCheckedFunction(type), 1, 1);
else
{
m_context << u256(1);
m_context << Instruction::SWAP1 << Instruction::SUB;
}
}
// Stack for prefix: [ref...] (*ref)+-1 // Stack for prefix: [ref...] (*ref)+-1
// Stack for postfix: *ref [ref...] (*ref)+-1 // Stack for postfix: *ref [ref...] (*ref)+-1
for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
@ -437,7 +453,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
// unary add, so basically no-op // unary add, so basically no-op
break; break;
case Token::Sub: // - case Token::Sub: // -
m_context << u256(0) << Instruction::SUB; if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().negateNumberCheckedFunction(type), 1, 1);
else
m_context << u256(0) << Instruction::SUB;
break; break;
default: default:
solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator()))); solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator())));
@ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
m_context << commonType->literalValue(nullptr); m_context << commonType->literalValue(nullptr);
else else
{ {
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op, m_context.arithmetic());
TypePointer leftTargetType = commonType; TypePointer leftTargetType = commonType;
TypePointer rightTargetType = TypePointer rightTargetType =
@ -2112,34 +2131,65 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
solUnimplemented("Not yet implemented - FixedPointType."); solUnimplemented("Not yet implemented - FixedPointType.");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
bool const c_isSigned = type.isSigned(); if (m_context.arithmetic() == Arithmetic::Checked)
switch (_operator)
{ {
case Token::Add: string functionName;
m_context << Instruction::ADD; switch (_operator)
break; {
case Token::Sub: case Token::Add:
m_context << Instruction::SUB; functionName = m_context.utilFunctions().overflowCheckedIntAddFunction(type);
break; break;
case Token::Mul: case Token::Sub:
m_context << Instruction::MUL; functionName = m_context.utilFunctions().overflowCheckedIntSubFunction(type);
break; break;
case Token::Div: case Token::Mul:
case Token::Mod: functionName = m_context.utilFunctions().overflowCheckedIntMulFunction(type);
{ break;
// Test for division by zero case Token::Div:
m_context << Instruction::DUP2 << Instruction::ISZERO; functionName = m_context.utilFunctions().overflowCheckedIntDivFunction(type);
m_context.appendConditionalInvalid(); break;
case Token::Mod:
if (_operator == Token::Div) functionName = m_context.utilFunctions().intModFunction(type);
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); break;
else case Token::Exp:
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); // EXP is handled in a different function.
break; default:
solAssert(false, "Unknown arithmetic operator.");
}
// TODO Maybe we want to force-inline this?
m_context.callYulFunction(functionName, 2, 1);
} }
default: else
solAssert(false, "Unknown arithmetic operator."); {
bool const c_isSigned = type.isSigned();
switch (_operator)
{
case Token::Add:
m_context << Instruction::ADD;
break;
case Token::Sub:
m_context << Instruction::SUB;
break;
case Token::Mul:
m_context << Instruction::MUL;
break;
case Token::Div:
case Token::Mod:
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
else
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD);
break;
}
default:
solAssert(false, "Unknown arithmetic operator.");
}
} }
} }
@ -2237,7 +2287,14 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons
solAssert(_valueType.category() == Type::Category::Integer, ""); solAssert(_valueType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), ""); solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), "");
m_context << Instruction::EXP;
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().overflowCheckedIntExpFunction(
dynamic_cast<IntegerType const&>(_valueType),
dynamic_cast<IntegerType const&>(_exponentType)
), 2, 1);
else
m_context << Instruction::EXP;
} }
void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExternalFunctionCall(
@ -2561,11 +2618,15 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
setLValue<StorageItem>(_expression, *_expression.annotation().type); setLValue<StorageItem>(_expression, *_expression.annotation().type);
} }
bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op) bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op, Arithmetic _arithmetic)
{ {
if (TokenTraits::isCompareOp(_op) || TokenTraits::isShiftOp(_op)) if (TokenTraits::isCompareOp(_op) || TokenTraits::isShiftOp(_op))
return true; return true;
else if (_type == Type::Category::Integer && (_op == Token::Div || _op == Token::Mod || _op == Token::Exp)) else if (
_arithmetic == Arithmetic::Wrapping &&
_type == Type::Category::Integer &&
(_op == Token::Div || _op == Token::Mod || _op == Token::Exp)
)
// We need cleanup for EXP because 0**0 == 1, but 0**0x100 == 0 // We need cleanup for EXP because 0**0 == 1, but 0**0x100 == 0
// It would suffice to clean the exponent, though. // It would suffice to clean the exponent, though.
return true; return true;

View File

@ -132,7 +132,7 @@ private:
/// @returns true if the operator applied to the given type requires a cleanup prior to the /// @returns true if the operator applied to the given type requires a cleanup prior to the
/// operation. /// operation.
static bool cleanupNeededForOp(Type::Category _type, Token _op); static bool cleanupNeededForOp(Type::Category _type, Token _op, Arithmetic _arithmetic);
void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false); void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false);

View File

@ -483,6 +483,22 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type)
{
string functionName = "wrapping_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := <cleanupFunction>(add(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{ {
string functionName = "checked_mul_" + _type.identifier(); string functionName = "checked_mul_" + _type.identifier();
@ -519,6 +535,22 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntMulFunction(IntegerType const& _type)
{
string functionName = "wrapping_mul_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> product {
product := <cleanupFunction>(mul(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{ {
string functionName = "checked_div_" + _type.identifier(); string functionName = "checked_div_" + _type.identifier();
@ -548,9 +580,30 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) string YulUtilFunctions::wrappingIntDivFunction(IntegerType const& _type)
{ {
string functionName = "checked_mod_" + _type.identifier(); string functionName = "wrapping_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <error>() }
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
("signed", _type.isSigned())
("error", panicFunction())
.render();
});
}
string YulUtilFunctions::intModFunction(IntegerType const& _type)
{
string functionName = "mod_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
@ -599,6 +652,22 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntSubFunction(IntegerType const& _type)
{
string functionName = "wrapping_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
diff := <cleanupFunction>(sub(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntExpFunction( string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type, IntegerType const& _type,
IntegerType const& _exponentType IntegerType const& _exponentType
@ -894,6 +963,30 @@ string YulUtilFunctions::overflowCheckedExpLoopFunction()
}); });
} }
string YulUtilFunctions::wrappingIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "wrapping_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
power := <baseCleanupFunction>(exp(base, exponent))
}
)")
("functionName", functionName)
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction() string YulUtilFunctions::extractByteArrayLengthFunction()
{ {
string functionName = "extract_byte_array_length"; string functionName = "extract_byte_array_length";
@ -2951,30 +3044,39 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
string const functionName = "decrement_" + _type.identifier(); string const functionName = "decrement_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 minintval;
// Smallest admissible value to decrement
if (type.isSigned())
minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
else
minintval = 1;
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if <lt>(value, <minval>) { <panic>() } if eq(value, <minval>) { <panic>() }
ret := sub(value, 1) ret := sub(value, 1)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("panic", panicFunction()) ("panic", panicFunction())
("minval", toCompactHexWithPrefix(minintval)) ("minval", toCompactHexWithPrefix(type.min()))
("lt", type.isSigned() ? "slt" : "lt")
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
.render(); .render();
}); });
} }
std::string YulUtilFunctions::decrementWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "decrement_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(sub(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
{ {
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
@ -2982,55 +3084,79 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
string const functionName = "increment_" + _type.identifier(); string const functionName = "increment_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 maxintval;
// Biggest admissible value to increment
if (type.isSigned())
maxintval = (u256(1) << (type.numBits() - 1)) - 2;
else
maxintval = (u256(1) << type.numBits()) - 2;
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if <gt>(value, <maxval>) { <panic>() } if eq(value, <maxval>) { <panic>() }
ret := add(value, 1) ret := add(value, 1)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("maxval", toCompactHexWithPrefix(maxintval)) ("maxval", toCompactHexWithPrefix(type.max()))
("gt", type.isSigned() ? "sgt" : "gt")
("panic", panicFunction()) ("panic", panicFunction())
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
.render(); .render();
}); });
} }
std::string YulUtilFunctions::incrementWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "increment_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(add(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
{ {
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!"); solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier(); string const functionName = "negate_" + _type.identifier();
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if slt(value, <minval>) { <panic>() } if eq(value, <minval>) { <panic>() }
ret := sub(0, value) ret := sub(0, value)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval)) ("minval", toCompactHexWithPrefix(type.min()))
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction()) ("panic", panicFunction())
.render(); .render();
}); });
} }
string YulUtilFunctions::negateNumberWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(sub(0, value)))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes) string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{ {
solAssert(_type.category() != Type::Category::Mapping, ""); solAssert(_type.category() != Type::Category::Mapping, "");

View File

@ -106,24 +106,35 @@ public:
/// signature: (x, y) -> sum /// signature: (x, y) -> sum
std::string overflowCheckedIntAddFunction(IntegerType const& _type); std::string overflowCheckedIntAddFunction(IntegerType const& _type);
/// signature: (x, y) -> sum
std::string wrappingIntAddFunction(IntegerType const& _type);
/// signature: (x, y) -> product /// signature: (x, y) -> product
std::string overflowCheckedIntMulFunction(IntegerType const& _type); std::string overflowCheckedIntMulFunction(IntegerType const& _type);
/// signature: (x, y) -> product
std::string wrappingIntMulFunction(IntegerType const& _type);
/// @returns name of function to perform division on integers. /// @returns name of function to perform division on integers.
/// Checks for division by zero and the special case of /// Checks for division by zero and the special case of
/// signed division of the smallest number by -1. /// signed division of the smallest number by -1.
std::string overflowCheckedIntDivFunction(IntegerType const& _type); std::string overflowCheckedIntDivFunction(IntegerType const& _type);
/// @returns name of function to perform division on integers.
/// Checks for division by zero.
std::string wrappingIntDivFunction(IntegerType const& _type);
/// @returns name of function to perform modulo on integers. /// @returns name of function to perform modulo on integers.
/// Reverts for modulo by zero. /// Reverts for modulo by zero.
std::string checkedIntModFunction(IntegerType const& _type); std::string intModFunction(IntegerType const& _type);
/// @returns computes the difference between two values. /// @returns computes the difference between two values.
/// Assumes the input to be in range for the type. /// Assumes the input to be in range for the type.
/// signature: (x, y) -> diff /// signature: (x, y) -> diff
std::string overflowCheckedIntSubFunction(IntegerType const& _type); std::string overflowCheckedIntSubFunction(IntegerType const& _type);
/// @returns computes the difference between two values.
/// signature: (x, y) -> diff
std::string wrappingIntSubFunction(IntegerType const& _type);
/// @returns the name of the exponentiation function. /// @returns the name of the exponentiation function.
/// signature: (base, exponent) -> power /// signature: (base, exponent) -> power
std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType); std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
@ -151,6 +162,10 @@ public:
/// signature: (power, base, exponent, max) -> power /// signature: (power, base, exponent, max) -> power
std::string overflowCheckedExpLoopFunction(); std::string overflowCheckedExpLoopFunction();
/// @returns the name of the exponentiation function.
/// signature: (base, exponent) -> power
std::string wrappingIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
/// @returns the name of a function that fetches the length of the given /// @returns the name of a function that fetches the length of the given
/// array /// array
/// signature: (array) -> length /// signature: (array) -> length
@ -367,9 +382,12 @@ public:
std::string forwardingRevertFunction(); std::string forwardingRevertFunction();
std::string incrementCheckedFunction(Type const& _type); std::string incrementCheckedFunction(Type const& _type);
std::string incrementWrappingFunction(Type const& _type);
std::string decrementCheckedFunction(Type const& _type); std::string decrementCheckedFunction(Type const& _type);
std::string decrementWrappingFunction(Type const& _type);
std::string negateNumberCheckedFunction(Type const& _type); std::string negateNumberCheckedFunction(Type const& _type);
std::string negateNumberWrappingFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the /// @returns the name of a function that returns the zero value for the
/// provided type. /// provided type.

View File

@ -132,6 +132,9 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; }; langutil::EVMVersion evmVersion() const { return m_evmVersion; };
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
Arithmetic arithmetic() const { return m_arithmetic; }
ABIFunctions abiFunctions(); ABIFunctions abiFunctions();
/// @returns code that stores @param _message for revert reason /// @returns code that stores @param _message for revert reason
@ -161,6 +164,8 @@ private:
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
MultiUseYulFunctionCollector m_functions; MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0; size_t m_varCounter = 0;
/// Whether to use checked or wrapping arithmetic.
Arithmetic m_arithmetic = Arithmetic::Checked;
/// Flag indicating whether any inline assembly block was seen. /// Flag indicating whether any inline assembly block was seen.
bool m_inlineAssemblySeen = false; bool m_inlineAssemblySeen = false;

View File

@ -474,6 +474,25 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
return false; return false;
} }
bool IRGeneratorForStatements::visit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
m_context.setArithmetic(Arithmetic::Wrapping);
}
return true;
}
void IRGeneratorForStatements::endVisit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Wrapping, "");
m_context.setArithmetic(Arithmetic::Checked);
}
}
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
{ {
_ifStatement.condition().accept(*this); _ifStatement.condition().accept(*this);
@ -618,11 +637,11 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
else if (op == Token::Sub) else if (op == Token::Sub)
{ {
IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType); IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType);
define(_unaryOperation) << define(_unaryOperation) << (
m_utils.negateNumberCheckedFunction(intType) << m_context.arithmetic() == Arithmetic::Checked ?
"(" << m_utils.negateNumberCheckedFunction(intType) :
IRVariable(_unaryOperation.subExpression()).name() << m_utils.negateNumberWrappingFunction(intType)
")\n"; ) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n";
} }
else else
solUnimplementedAssert(false, "Unary operator not yet implemented"); solUnimplementedAssert(false, "Unary operator not yet implemented");
@ -2560,23 +2579,23 @@ string IRGeneratorForStatements::binaryOperation(
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
{ {
string fun; string fun;
// TODO: Implement all operations for signed and unsigned types. bool checked = m_context.arithmetic() == Arithmetic::Checked;
switch (_operator) switch (_operator)
{ {
case Token::Add: case Token::Add:
fun = m_utils.overflowCheckedIntAddFunction(*type); fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
break; break;
case Token::Sub: case Token::Sub:
fun = m_utils.overflowCheckedIntSubFunction(*type); fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
break; break;
case Token::Mul: case Token::Mul:
fun = m_utils.overflowCheckedIntMulFunction(*type); fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
break; break;
case Token::Div: case Token::Div:
fun = m_utils.overflowCheckedIntDivFunction(*type); fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
break; break;
case Token::Mod: case Token::Mod:
fun = m_utils.checkedIntModFunction(*type); fun = m_utils.intModFunction(*type);
break; break;
case Token::BitOr: case Token::BitOr:
fun = "or"; fun = "or";

View File

@ -66,6 +66,8 @@ public:
bool visit(Conditional const& _conditional) override; bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override; bool visit(Assignment const& _assignment) override;
bool visit(TupleExpression const& _tuple) override; bool visit(TupleExpression const& _tuple) override;
bool visit(Block const& _block) override;
void endVisit(Block const& _block) override;
bool visit(IfStatement const& _ifStatement) override; bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override; bool visit(WhileStatement const& _whileStatement) override;

View File

@ -1096,16 +1096,23 @@ ASTPointer<ParameterList> Parser::parseParameterList(
return nodeFactory.createNode<ParameterList>(parameters); return nodeFactory.createNode<ParameterList>(parameters);
} }
ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString) ASTPointer<Block> Parser::parseBlock(bool _allowUnchecked, ASTPointer<ASTString> const& _docString)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
bool const unchecked = m_scanner->currentToken() == Token::Unchecked;
if (unchecked)
{
if (!_allowUnchecked)
parserError(5296_error, "\"unchecked\" blocks can only be used inside regular blocks.");
m_scanner->next();
}
expectToken(Token::LBrace); expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements; vector<ASTPointer<Statement>> statements;
try try
{ {
while (m_scanner->currentToken() != Token::RBrace) while (m_scanner->currentToken() != Token::RBrace)
statements.push_back(parseStatement()); statements.push_back(parseStatement(true));
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
} }
catch (FatalError const&) catch (FatalError const&)
@ -1122,10 +1129,10 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
expectTokenOrConsumeUntil(Token::RBrace, "Block"); expectTokenOrConsumeUntil(Token::RBrace, "Block");
else else
expectToken(Token::RBrace); expectToken(Token::RBrace);
return nodeFactory.createNode<Block>(_docString, statements); return nodeFactory.createNode<Block>(_docString, unchecked, statements);
} }
ASTPointer<Statement> Parser::parseStatement() ASTPointer<Statement> Parser::parseStatement(bool _allowUnchecked)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTPointer<ASTString> docString; ASTPointer<ASTString> docString;
@ -1144,9 +1151,9 @@ ASTPointer<Statement> Parser::parseStatement()
return parseDoWhileStatement(docString); return parseDoWhileStatement(docString);
case Token::For: case Token::For:
return parseForStatement(docString); return parseForStatement(docString);
case Token::Unchecked:
case Token::LBrace: case Token::LBrace:
return parseBlock(docString); return parseBlock(_allowUnchecked, docString);
// starting from here, all statements must be terminated by a semicolon
case Token::Continue: case Token::Continue:
statement = ASTNodeFactory(*this).createNode<Continue>(docString); statement = ASTNodeFactory(*this).createNode<Continue>(docString);
m_scanner->next(); m_scanner->next();

View File

@ -115,8 +115,8 @@ private:
VarDeclParserOptions const& _options = {}, VarDeclParserOptions const& _options = {},
bool _allowEmpty = true bool _allowEmpty = true
); );
ASTPointer<Block> parseBlock(ASTPointer<ASTString> const& _docString = {}); ASTPointer<Block> parseBlock(bool _allowUncheckedBlock = false, ASTPointer<ASTString> const& _docString = {});
ASTPointer<Statement> parseStatement(); ASTPointer<Statement> parseStatement(bool _allowUncheckedBlock = false);
ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {}); ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {});
ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString); ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString); ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString);

View File

@ -139,6 +139,7 @@ bytes compileFirstExpression(
); );
context.resetVisitedNodes(contract); context.resetVisitedNodes(contract);
context.setMostDerivedContract(*contract); context.setMostDerivedContract(*contract);
context.setArithmetic(Arithmetic::Wrapping);
size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack
context.adjustStackOffset(static_cast<int>(parametersSize)); context.adjustStackOffset(static_cast<int>(parametersSize));
for (vector<string> const& variable: _localVariables) for (vector<string> const& variable: _localVariables)