Skip to content

Commit

Permalink
Checked arithmetics by default.
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseth committed Jul 22, 2020
1 parent 50c3daf commit fc31877
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 35 deletions.
4 changes: 2 additions & 2 deletions liblangutil/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ namespace solidity::langutil
K(Struct, "struct", 0) \
K(Throw, "throw", 0) \
K(Type, "type", 0) \
K(Unchecked, "unchecked", 0) \
K(Using, "using", 0) \
K(View, "view", 0) \
K(Virtual, "virtual", 0) \
Expand Down Expand Up @@ -263,7 +264,6 @@ namespace solidity::langutil
K(Try, "try", 0) \
K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \
K(Unchecked, "unchecked", 0) \
K(Var, "var", 0) \
\
/* Illegal token - not able to scan. */ \
Expand Down Expand Up @@ -312,7 +312,7 @@ namespace TokenTraits

constexpr bool isEtherSubdenomination(Token op) { return op >= Token::SubWei && op <= Token::SubEther; }
constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Var); }

inline Token AssignmentToBinaryOp(Token op)
{
Expand Down
7 changes: 6 additions & 1 deletion libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -1335,18 +1335,23 @@ class Block: public Statement, public Scopable
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;
};

/**
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,8 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
bool ASTJsonConverter::visit(Block const& _node)
{
setJsonNode(_node, "Block", {
make_pair("statements", toJson(_node.statements()))
make_pair("statements", toJson(_node.statements())),
make_pair("unchecked", _node.unchecked())
});
return false;
}
Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node)
return createASTNode<Block>(
_node,
nullOrASTString(_node, "documentation"),
member(_node, "unchecked") && memberAsBool(_node, "unchecked"),
statements
);
}
Expand Down
5 changes: 5 additions & 0 deletions libsolidity/codegen/CompilerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ class CompilerContext
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const;

void setCheckedArithmetics(bool _value) { m_checkedArithmetics = _value; }
bool checkedArithmetics() const { return m_checkedArithmetics; }

/// @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,
Expand Down Expand Up @@ -373,6 +376,8 @@ class CompilerContext
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 or not to use checked arithmatics.
bool m_checkedArithmetics = true;
/// 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.
Expand Down
6 changes: 6 additions & 0 deletions libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,10 @@ void ContractCompiler::appendModifierOrFunctionCode()

if (codeBlock)
{
bool previousChecked = m_context.checkedArithmetics();
// TODO test that checks are also applied for initializing state variables
m_context.setCheckedArithmetics(true);

m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight());
codeBlock->accept(*this);

Expand All @@ -1342,6 +1346,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
CompilerUtils(m_context).popStackSlots(stackSurplus);
for (auto var: addedVariables)
m_context.removeVariable(*var);

m_context.setCheckedArithmetics(previousChecked);
}
m_modifierDepth--;
m_context.setModifierDepth(m_modifierDepth);
Expand Down
92 changes: 63 additions & 29 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
m_context << commonType->literalValue(nullptr);
else
{
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op);
bool cleanupNeeded = m_context.checkedArithmetics() || cleanupNeededForOp(commonType->category(), c_op);

TypePointer leftTargetType = commonType;
TypePointer rightTargetType = TokenTraits::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType;
Expand Down Expand Up @@ -2054,37 +2054,71 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
solUnimplemented("Not yet implemented - FixedPointType.");

IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
bool const c_isSigned = type.isSigned();

switch (_operator)
if (m_context.checkedArithmetics())
{
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:
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().checkedIntModFunction(type);
break;
case Token::Exp:
// TODO
m_context << Instruction::EXP;
break;
default:
solAssert(false, "Unknown arithmetic operator.");
}
// TODO Maybe we want to force-inline this?
// TODO revert with special error
m_context.callYulFunction(functionName, 2, 1);
}
else
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
m_context.appendConditionalInvalid();
bool const c_isSigned = type.isSigned();

if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
else
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD);
break;
}
case Token::Exp:
m_context << Instruction::EXP;
break;
default:
solAssert(false, "Unknown arithmetic operator.");
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;
}
case Token::Exp:
m_context << Instruction::EXP;
break;
default:
solAssert(false, "Unknown arithmetic operator.");
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions libsolidity/parsing/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,9 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
bool const unchecked = m_scanner->currentToken() == Token::Unchecked;
if (unchecked)
m_scanner->next();
expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements;
try
Expand All @@ -1106,7 +1109,7 @@ 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()
Expand All @@ -1128,9 +1131,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
case Token::Continue:
statement = ASTNodeFactory(*this).createNode<Continue>(docString);
m_scanner->next();
Expand Down

0 comments on commit fc31877

Please sign in to comment.