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 Aug 6, 2020
1 parent 12d0d6a commit b367415
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 32 deletions.
4 changes: 2 additions & 2 deletions liblangutil/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,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) \
Expand Down Expand Up @@ -265,7 +266,6 @@ namespace solidity::langutil
K(Switch, "switch", 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 @@ -314,7 +314,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
8 changes: 7 additions & 1 deletion libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -1335,18 +1335,24 @@ 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
6 changes: 6 additions & 0 deletions libsolidity/codegen/CompilerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ class CompilerContext
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const;

void pushCheckedArithmetics(bool _value) { m_checkedArithmetics.push(_value); }
void popCheckedArithmetics() { m_checkedArithmetics.pop(); }
bool checkedArithmetics() const { return m_checkedArithmetics.empty() ? true : m_checkedArithmetics.top(); }

/// @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 +377,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 to use checked arithmetics.
std::stack<bool> m_checkedArithmetics;
/// 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
7 changes: 7 additions & 0 deletions libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1253,12 +1253,14 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)

bool ContractCompiler::visit(Block const& _block)
{
m_context.pushCheckedArithmetics(!_block.unchecked());
storeStackHeight(&_block);
return true;
}

void ContractCompiler::endVisit(Block const& _block)
{
m_context.popCheckedArithmetics();
// Frees local variables declared in the scope of this block.
popScopedVariables(&_block);
}
Expand Down Expand Up @@ -1332,6 +1334,9 @@ void ContractCompiler::appendModifierOrFunctionCode()

if (codeBlock)
{
m_context.pushCheckedArithmetics(true);
// TODO test that checks are also applied for initializing state variables

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

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

m_context.popCheckedArithmetics();
}
m_modifierDepth--;
m_context.setModifierDepth(m_modifierDepth);
Expand Down
86 changes: 60 additions & 26 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,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 =
Expand Down Expand Up @@ -2060,34 +2060,68 @@ 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;
}
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;
}
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 b367415

Please sign in to comment.