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``
AST Changes:
* New node type: unchecked block - used for ``unchecked { ... }``.
### 0.7.4 (unreleased)
Important Bugfixes:

View File

@ -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. */ \

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};
/**

View File

@ -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)

View File

@ -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;

View File

@ -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
);
}

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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);

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 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, "");

View File

@ -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.

View File

@ -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;

View File

@ -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";

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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)