mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Checked arithmetic by default.
This commit is contained in:
parent
41000fea31
commit
527c073bb9
@ -15,6 +15,9 @@ Language Features:
|
||||
* New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName``
|
||||
|
||||
|
||||
AST Changes:
|
||||
* New node type: unchecked block - used for ``unchecked { ... }``.
|
||||
|
||||
### 0.7.4 (unreleased)
|
||||
|
||||
Important Bugfixes:
|
||||
|
@ -191,6 +191,7 @@ namespace solidity::langutil
|
||||
K(Throw, "throw", 0) \
|
||||
K(Try, "try", 0) \
|
||||
K(Type, "type", 0) \
|
||||
K(Unchecked, "unchecked", 0) \
|
||||
K(Unicode, "unicode", 0) \
|
||||
K(Using, "using", 0) \
|
||||
K(View, "view", 0) \
|
||||
@ -266,7 +267,6 @@ namespace solidity::langutil
|
||||
K(Switch, "switch", 0) \
|
||||
K(Typedef, "typedef", 0) \
|
||||
K(TypeOf, "typeof", 0) \
|
||||
K(Unchecked, "unchecked", 0) \
|
||||
K(Var, "var", 0) \
|
||||
\
|
||||
/* Yul-specific tokens, but not keywords. */ \
|
||||
|
@ -190,6 +190,28 @@ void SyntaxChecker::endVisit(ForStatement const&)
|
||||
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)
|
||||
{
|
||||
if (m_inLoopDepth <= 0)
|
||||
@ -288,8 +310,15 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ private:
|
||||
bool visit(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(Break const& _breakStatement) override;
|
||||
|
||||
@ -100,6 +103,9 @@ private:
|
||||
/// Flag that indicates whether some version pragma was present.
|
||||
bool m_versionPragmaFound = false;
|
||||
|
||||
/// Flag that indicates whether we are inside an unchecked block.
|
||||
bool m_uncheckedArithmetic = false;
|
||||
|
||||
int m_inLoopDepth = 0;
|
||||
std::optional<ContractKind> m_currentContractKind;
|
||||
|
||||
|
@ -1384,18 +1384,24 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
bool _unchecked,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; }
|
||||
bool unchecked() const { return m_unchecked; }
|
||||
|
||||
BlockAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
std::vector<ASTPointer<Statement>> m_statements;
|
||||
bool m_unchecked;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,8 @@ enum class StateMutability { Pure, View, NonPayable, Payable };
|
||||
/// Visibility ordered from restricted to unrestricted.
|
||||
enum class Visibility { Default, Private, Internal, Public, External };
|
||||
|
||||
enum class Arithmetic { Checked, Wrapping };
|
||||
|
||||
inline std::string stateMutabilityToString(StateMutability const& _stateMutability)
|
||||
{
|
||||
switch (_stateMutability)
|
||||
|
@ -596,7 +596,7 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
||||
|
||||
bool ASTJsonConverter::visit(Block const& _node)
|
||||
{
|
||||
setJsonNode(_node, "Block", {
|
||||
setJsonNode(_node, _node.unchecked() ? "UncheckedBlock" : "Block", {
|
||||
make_pair("statements", toJson(_node.statements()))
|
||||
});
|
||||
return false;
|
||||
|
@ -154,7 +154,9 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
|
||||
if (nodeType == "InlineAssembly")
|
||||
return createInlineAssembly(_json);
|
||||
if (nodeType == "Block")
|
||||
return createBlock(_json);
|
||||
return createBlock(_json, false);
|
||||
if (nodeType == "UncheckedBlock")
|
||||
return createBlock(_json, true);
|
||||
if (nodeType == "PlaceholderStatement")
|
||||
return createPlaceholderStatement(_json);
|
||||
if (nodeType == "IfStatement")
|
||||
@ -439,7 +441,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
|
||||
createParameterList(member(_node, "parameters")),
|
||||
modifiers,
|
||||
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")),
|
||||
memberAsBool(_node, "virtual"),
|
||||
_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;
|
||||
for (auto& stat: member(_node, "statements"))
|
||||
@ -597,6 +599,7 @@ ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node)
|
||||
return createASTNode<Block>(
|
||||
_node,
|
||||
nullOrASTString(_node, "documentation"),
|
||||
_unchecked,
|
||||
statements
|
||||
);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ private:
|
||||
ASTPointer<Mapping> createMapping(Json::Value const& _node);
|
||||
ASTPointer<ArrayTypeName> createArrayTypeName(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<IfStatement> createIfStatement(Json::Value const& _node);
|
||||
ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node);
|
||||
|
@ -123,6 +123,9 @@ public:
|
||||
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
|
||||
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
|
||||
/// (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,
|
||||
@ -380,6 +383,8 @@ private:
|
||||
std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
|
||||
/// The contract currently being compiled. Virtual function lookup starts from this contarct.
|
||||
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
|
||||
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.
|
||||
|
@ -1247,19 +1247,31 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
|
||||
{
|
||||
StackHeightChecker checker(m_context);
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
|
||||
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Placeholder cannot be used inside checked block.");
|
||||
appendModifierOrFunctionCode();
|
||||
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Arithmetic not reset to 'checked'.");
|
||||
checker.check();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContractCompiler::visit(Block const& _block)
|
||||
{
|
||||
if (_block.unchecked())
|
||||
{
|
||||
solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
|
||||
m_context.setArithmetic(Arithmetic::Wrapping);
|
||||
}
|
||||
storeStackHeight(&_block);
|
||||
return true;
|
||||
}
|
||||
|
||||
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.
|
||||
popScopedVariables(&_block);
|
||||
}
|
||||
@ -1327,6 +1339,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
|
||||
|
||||
if (codeBlock)
|
||||
{
|
||||
m_context.setArithmetic(Arithmetic::Checked);
|
||||
|
||||
std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive();
|
||||
m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures);
|
||||
|
||||
|
@ -275,7 +275,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
solAssert(*_assignment.annotation().type == leftType, "");
|
||||
bool cleanupNeeded = false;
|
||||
if (op != Token::Assign)
|
||||
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp);
|
||||
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp, m_context.arithmetic());
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
// 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.
|
||||
@ -381,9 +381,10 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
bool ExpressionCompiler::visit(UnaryOperation const& _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;
|
||||
}
|
||||
|
||||
@ -406,24 +407,39 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
||||
case Token::Dec: // -- (pre- or postfix)
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
solUnimplementedAssert(
|
||||
_unaryOperation.annotation().type->category() != Type::Category::FixedPoint,
|
||||
type.category() != Type::Category::FixedPoint,
|
||||
"Not yet implemented - FixedPointType."
|
||||
);
|
||||
m_currentLValue->retrieveValue(_unaryOperation.location());
|
||||
if (!_unaryOperation.isPrefixOperation())
|
||||
{
|
||||
// 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;
|
||||
if (m_currentLValue->sizeOnStack() > 0)
|
||||
for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
|
||||
m_context << swapInstruction(i);
|
||||
}
|
||||
m_context << u256(1);
|
||||
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
|
||||
{
|
||||
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 postfix: *ref [ref...] (*ref)+-1
|
||||
for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
|
||||
@ -437,6 +453,9 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
||||
// unary add, so basically no-op
|
||||
break;
|
||||
case Token::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;
|
||||
default:
|
||||
@ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
|
||||
m_context << commonType->literalValue(nullptr);
|
||||
else
|
||||
{
|
||||
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op);
|
||||
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op, m_context.arithmetic());
|
||||
|
||||
TypePointer leftTargetType = commonType;
|
||||
TypePointer rightTargetType =
|
||||
@ -2112,6 +2131,36 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
|
||||
solUnimplemented("Not yet implemented - FixedPointType.");
|
||||
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (m_context.arithmetic() == Arithmetic::Checked)
|
||||
{
|
||||
string functionName;
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::Add:
|
||||
functionName = m_context.utilFunctions().overflowCheckedIntAddFunction(type);
|
||||
break;
|
||||
case Token::Sub:
|
||||
functionName = m_context.utilFunctions().overflowCheckedIntSubFunction(type);
|
||||
break;
|
||||
case Token::Mul:
|
||||
functionName = m_context.utilFunctions().overflowCheckedIntMulFunction(type);
|
||||
break;
|
||||
case Token::Div:
|
||||
functionName = m_context.utilFunctions().overflowCheckedIntDivFunction(type);
|
||||
break;
|
||||
case Token::Mod:
|
||||
functionName = m_context.utilFunctions().intModFunction(type);
|
||||
break;
|
||||
case Token::Exp:
|
||||
// EXP is handled in a different function.
|
||||
default:
|
||||
solAssert(false, "Unknown arithmetic operator.");
|
||||
}
|
||||
// TODO Maybe we want to force-inline this?
|
||||
m_context.callYulFunction(functionName, 2, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool const c_isSigned = type.isSigned();
|
||||
|
||||
switch (_operator)
|
||||
@ -2142,6 +2191,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
|
||||
solAssert(false, "Unknown arithmetic operator.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendBitOperatorCode(Token _operator)
|
||||
{
|
||||
@ -2237,6 +2287,13 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons
|
||||
solAssert(_valueType.category() == Type::Category::Integer, "");
|
||||
solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), "");
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -2561,11 +2618,15 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
|
||||
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))
|
||||
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
|
||||
// It would suffice to clean the exponent, though.
|
||||
return true;
|
||||
|
@ -132,7 +132,7 @@ private:
|
||||
|
||||
/// @returns true if the operator applied to the given type requires a cleanup prior to the
|
||||
/// 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);
|
||||
|
||||
|
@ -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 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 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
|
||||
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(
|
||||
IntegerType const& _type,
|
||||
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 functionName = "extract_byte_array_length";
|
||||
@ -2951,30 +3044,39 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
||||
string const functionName = "decrement_" + _type.identifier();
|
||||
|
||||
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"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if <lt>(value, <minval>) { <panic>() }
|
||||
if eq(value, <minval>) { <panic>() }
|
||||
ret := sub(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
("lt", type.isSigned() ? "slt" : "lt")
|
||||
("minval", toCompactHexWithPrefix(type.min()))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
.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)
|
||||
{
|
||||
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();
|
||||
|
||||
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"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if <gt>(value, <maxval>) { <panic>() }
|
||||
if eq(value, <maxval>) { <panic>() }
|
||||
ret := add(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("maxval", toCompactHexWithPrefix(maxintval))
|
||||
("gt", type.isSigned() ? "sgt" : "gt")
|
||||
("maxval", toCompactHexWithPrefix(type.max()))
|
||||
("panic", panicFunction())
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
.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)
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
solAssert(type.isSigned(), "Expected signed type!");
|
||||
|
||||
string const functionName = "negate_" + _type.identifier();
|
||||
|
||||
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if slt(value, <minval>) { <panic>() }
|
||||
if eq(value, <minval>) { <panic>() }
|
||||
ret := sub(0, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
("minval", toCompactHexWithPrefix(type.min()))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.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)
|
||||
{
|
||||
solAssert(_type.category() != Type::Category::Mapping, "");
|
||||
|
@ -106,24 +106,35 @@ public:
|
||||
|
||||
/// signature: (x, y) -> sum
|
||||
std::string overflowCheckedIntAddFunction(IntegerType const& _type);
|
||||
/// signature: (x, y) -> sum
|
||||
std::string wrappingIntAddFunction(IntegerType const& _type);
|
||||
|
||||
/// signature: (x, y) -> product
|
||||
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.
|
||||
/// Checks for division by zero and the special case of
|
||||
/// signed division of the smallest number by -1.
|
||||
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.
|
||||
/// Reverts for modulo by zero.
|
||||
std::string checkedIntModFunction(IntegerType const& _type);
|
||||
std::string intModFunction(IntegerType const& _type);
|
||||
|
||||
/// @returns computes the difference between two values.
|
||||
/// Assumes the input to be in range for the type.
|
||||
/// signature: (x, y) -> diff
|
||||
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.
|
||||
/// signature: (base, exponent) -> power
|
||||
std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
|
||||
@ -151,6 +162,10 @@ public:
|
||||
/// signature: (power, base, exponent, max) -> power
|
||||
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
|
||||
/// array
|
||||
/// signature: (array) -> length
|
||||
@ -367,9 +382,12 @@ public:
|
||||
std::string forwardingRevertFunction();
|
||||
|
||||
std::string incrementCheckedFunction(Type const& _type);
|
||||
std::string incrementWrappingFunction(Type const& _type);
|
||||
std::string decrementCheckedFunction(Type const& _type);
|
||||
std::string decrementWrappingFunction(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
|
||||
/// provided type.
|
||||
|
@ -132,6 +132,9 @@ public:
|
||||
|
||||
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
|
||||
|
||||
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
|
||||
Arithmetic arithmetic() const { return m_arithmetic; }
|
||||
|
||||
ABIFunctions abiFunctions();
|
||||
|
||||
/// @returns code that stores @param _message for revert reason
|
||||
@ -161,6 +164,8 @@ private:
|
||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||
MultiUseYulFunctionCollector m_functions;
|
||||
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.
|
||||
bool m_inlineAssemblySeen = false;
|
||||
|
@ -474,6 +474,25 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
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)
|
||||
{
|
||||
_ifStatement.condition().accept(*this);
|
||||
@ -618,11 +637,11 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
|
||||
else if (op == Token::Sub)
|
||||
{
|
||||
IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType);
|
||||
define(_unaryOperation) <<
|
||||
m_utils.negateNumberCheckedFunction(intType) <<
|
||||
"(" <<
|
||||
IRVariable(_unaryOperation.subExpression()).name() <<
|
||||
")\n";
|
||||
define(_unaryOperation) << (
|
||||
m_context.arithmetic() == Arithmetic::Checked ?
|
||||
m_utils.negateNumberCheckedFunction(intType) :
|
||||
m_utils.negateNumberWrappingFunction(intType)
|
||||
) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n";
|
||||
}
|
||||
else
|
||||
solUnimplementedAssert(false, "Unary operator not yet implemented");
|
||||
@ -2560,23 +2579,23 @@ string IRGeneratorForStatements::binaryOperation(
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
||||
{
|
||||
string fun;
|
||||
// TODO: Implement all operations for signed and unsigned types.
|
||||
bool checked = m_context.arithmetic() == Arithmetic::Checked;
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::Add:
|
||||
fun = m_utils.overflowCheckedIntAddFunction(*type);
|
||||
fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
|
||||
break;
|
||||
case Token::Sub:
|
||||
fun = m_utils.overflowCheckedIntSubFunction(*type);
|
||||
fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
|
||||
break;
|
||||
case Token::Mul:
|
||||
fun = m_utils.overflowCheckedIntMulFunction(*type);
|
||||
fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
|
||||
break;
|
||||
case Token::Div:
|
||||
fun = m_utils.overflowCheckedIntDivFunction(*type);
|
||||
fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
|
||||
break;
|
||||
case Token::Mod:
|
||||
fun = m_utils.checkedIntModFunction(*type);
|
||||
fun = m_utils.intModFunction(*type);
|
||||
break;
|
||||
case Token::BitOr:
|
||||
fun = "or";
|
||||
|
@ -66,6 +66,8 @@ public:
|
||||
bool visit(Conditional const& _conditional) override;
|
||||
bool visit(Assignment const& _assignment) 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(ForStatement const& _forStatement) override;
|
||||
bool visit(WhileStatement const& _whileStatement) override;
|
||||
|
@ -1096,16 +1096,23 @@ ASTPointer<ParameterList> Parser::parseParameterList(
|
||||
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);
|
||||
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);
|
||||
vector<ASTPointer<Statement>> statements;
|
||||
try
|
||||
{
|
||||
while (m_scanner->currentToken() != Token::RBrace)
|
||||
statements.push_back(parseStatement());
|
||||
statements.push_back(parseStatement(true));
|
||||
nodeFactory.markEndPosition();
|
||||
}
|
||||
catch (FatalError const&)
|
||||
@ -1122,10 +1129,10 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
|
||||
expectTokenOrConsumeUntil(Token::RBrace, "Block");
|
||||
else
|
||||
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);
|
||||
ASTPointer<ASTString> docString;
|
||||
@ -1144,9 +1151,9 @@ ASTPointer<Statement> Parser::parseStatement()
|
||||
return parseDoWhileStatement(docString);
|
||||
case Token::For:
|
||||
return parseForStatement(docString);
|
||||
case Token::Unchecked:
|
||||
case Token::LBrace:
|
||||
return parseBlock(docString);
|
||||
// starting from here, all statements must be terminated by a semicolon
|
||||
return parseBlock(_allowUnchecked, docString);
|
||||
case Token::Continue:
|
||||
statement = ASTNodeFactory(*this).createNode<Continue>(docString);
|
||||
m_scanner->next();
|
||||
|
@ -115,8 +115,8 @@ private:
|
||||
VarDeclParserOptions const& _options = {},
|
||||
bool _allowEmpty = true
|
||||
);
|
||||
ASTPointer<Block> parseBlock(ASTPointer<ASTString> const& _docString = {});
|
||||
ASTPointer<Statement> parseStatement();
|
||||
ASTPointer<Block> parseBlock(bool _allowUncheckedBlock = false, ASTPointer<ASTString> const& _docString = {});
|
||||
ASTPointer<Statement> parseStatement(bool _allowUncheckedBlock = false);
|
||||
ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {});
|
||||
ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString);
|
||||
ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString);
|
||||
|
@ -139,6 +139,7 @@ bytes compileFirstExpression(
|
||||
);
|
||||
context.resetVisitedNodes(contract);
|
||||
context.setMostDerivedContract(*contract);
|
||||
context.setArithmetic(Arithmetic::Wrapping);
|
||||
size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack
|
||||
context.adjustStackOffset(static_cast<int>(parametersSize));
|
||||
for (vector<string> const& variable: _localVariables)
|
||||
|
Loading…
Reference in New Issue
Block a user