Overflow-checked addition.

This commit is contained in:
chriseth 2019-03-18 11:21:41 +01:00 committed by Mathias Baumann
parent 826f2d9084
commit 18ab8aeb85
14 changed files with 309 additions and 8 deletions

View File

@ -69,6 +69,8 @@ set(sources
codegen/YulUtilFunctions.cpp
codegen/ir/IRGenerator.cpp
codegen/ir/IRGenerator.h
codegen/ir/IRGeneratorForStatements.cpp
codegen/ir/IRGeneratorForStatements.h
codegen/ir/IRGenerationContext.cpp
codegen/ir/IRGenerationContext.h
formal/SMTChecker.cpp

View File

@ -252,6 +252,36 @@ string YulUtilFunctions::roundUpFunction()
});
}
string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
{
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
string functionName = "checked_add_uint_" + to_string(_bits);
return m_functionCollector->createFunction(functionName, [&]() {
if (_bits < 256)
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
let mask := <mask>
sum := add(and(x, mask), and(y, mask))
if and(sum, not(mask)) { revert(0, 0) }
}
)")
("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
else
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := add(x, y)
if lt(sum, x) { revert(0, 0) }
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();

View File

@ -73,6 +73,8 @@ public:
/// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction();
std::string overflowCheckedUIntAddFunction(size_t _bits);
std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required
/// to store an array in memory given its length (internally encoded, not ABI encoded).

View File

@ -35,3 +35,24 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
{
solAssert(
m_localVariables.count(&_varDecl),
"Unknown variable: " + _varDecl.name()
);
return m_localVariables[&_varDecl];
}
string IRGenerationContext::newYulVariable()
{
return "_" + to_string(++m_varCounter);
}
string IRGenerationContext::variable(Expression const& _expression)
{
unsigned size = _expression.annotation().type->sizeOnStack();
solUnimplementedAssert(size == 1, "");
return "expr_" + to_string(_expression.id());
}

View File

@ -35,6 +35,7 @@ namespace solidity
{
class VariableDeclaration;
class Expression;
/**
* Class that contains contextual information during IR generation.
@ -51,12 +52,18 @@ public:
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
std::string addLocalVariable(VariableDeclaration const& _varDecl);
std::string variableName(VariableDeclaration const& _varDecl);
std::string newYulVariable();
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
private:
langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings;
std::map<VariableDeclaration const*, std::string> m_localVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
size_t m_varCounter = 0;
};
}

View File

@ -22,7 +22,10 @@
#include <libsolidity/codegen/ir/IRGenerator.h>
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
@ -43,7 +46,7 @@ using namespace dev::solidity;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
{
// TODO Would be nice to pretty-print this while retaining comments.
string ir = generateIR(_contract);
string ir = generate(_contract);
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -69,7 +72,7 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
return {warning + ir, warning + asmStack.print()};
}
string IRGenerator::generateIR(ContractDefinition const& _contract)
string IRGenerator::generate(ContractDefinition const& _contract)
{
Whiskers t(R"(
object "<CreationObject>" {
@ -103,11 +106,18 @@ string IRGenerator::generateIR(ContractDefinition const& _contract)
return t.render();
}
string IRGenerator::generateIRFunction(FunctionDefinition const& _function)
string IRGenerator::generate(Block const& _block)
{
IRGeneratorForStatements generator(m_context, m_utils);
_block.accept(generator);
return generator.code();
}
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = "fun_" + to_string(_function.id()) + "_" + _function.name();
return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t("function <functionName>(<params>) <returns> {}");
Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n");
t("functionName", functionName);
string params;
for (auto const& varDecl: _function.parameters())
@ -117,6 +127,7 @@ string IRGenerator::generateIRFunction(FunctionDefinition const& _function)
for (auto const& varDecl: _function.returnParameters())
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("body", generate(_function.body()));
return t.render();
});
}
@ -200,7 +211,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
templ["params"] = m_utils.suffixedVariableNameList("param_", 0, paramVars);
templ["retParams"] = m_utils.suffixedVariableNameList("ret_", retVars, 0);
templ["function"] = generateIRFunction(dynamic_cast<FunctionDefinition const&>(type->declaration()));
templ["function"] = generateFunction(dynamic_cast<FunctionDefinition const&>(type->declaration()));
templ["allocate"] = m_utils.allocationFunction();
templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false);
templ["comma"] = retVars == 0 ? "" : ", ";
@ -211,7 +222,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
string fallbackCode;
if (!fallback->isPayable())
fallbackCode += callValueCheck();
fallbackCode += generateIRFunction(*fallback) + "() stop()";
fallbackCode += generateFunction(*fallback) + "() stop()";
t("fallback", fallbackCode);
}

View File

@ -51,8 +51,11 @@ public:
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
private:
std::string generateIR(ContractDefinition const& _contract);
std::string generateIRFunction(FunctionDefinition const& _function);
std::string generate(ContractDefinition const& _contract);
std::string generate(Block const& _block);
/// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _function);
std::string constructorCode(FunctionDefinition const& _constructor);
std::string deployCode(ContractDefinition const& _contract);

View File

@ -0,0 +1,108 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component that translates Solidity code into Yul at statement level and below.
*/
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement)
{
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
m_context.addLocalVariable(*decl);
if (Expression const* expression = _varDeclStatement.initialValue())
{
solUnimplementedAssert(_varDeclStatement.declarations().size() == 1, "");
expression->accept(*this);
solUnimplementedAssert(
*expression->annotation().type == *_varDeclStatement.declarations().front()->type(),
"Type conversion not yet implemented"
);
m_code <<
"let " <<
m_context.variableName(*_varDeclStatement.declarations().front()) <<
" := " <<
m_context.variable(*expression) <<
"\n";
}
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
m_code << "let " << m_context.variableName(*decl) << "\n";
return false;
}
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
solUnimplementedAssert(_assignment.assignmentOperator() == Token::Assign, "");
_assignment.rightHandSide().accept(*this);
solUnimplementedAssert(
*_assignment.rightHandSide().annotation().type == *_assignment.leftHandSide().annotation().type,
"Type conversion not yet implemented"
);
// TODO proper lvalue handling
auto const& identifier = dynamic_cast<Identifier const&>(_assignment.leftHandSide());
string varName = m_context.variableName(dynamic_cast<VariableDeclaration const&>(*identifier.annotation().referencedDeclaration));
m_code << varName << " := " << m_context.variable(_assignment.rightHandSide()) << "\n";
m_code << "let " << m_context.variable(_assignment) << " := " << varName << "\n";
return false;
}
void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp)
{
solUnimplementedAssert(_binOp.getOperator() == Token::Add, "");
solUnimplementedAssert(*_binOp.leftExpression().annotation().type == *_binOp.rightExpression().annotation().type, "");
if (IntegerType const* type = dynamic_cast<IntegerType const*>(_binOp.annotation().commonType.get()))
{
solUnimplementedAssert(!type->isSigned(), "");
m_code <<
"let " <<
m_context.variable(_binOp) <<
" := " <<
m_utils.overflowCheckedUIntAddFunction(type->numBits()) <<
"(" <<
m_context.variable(_binOp.leftExpression()) <<
", " <<
m_context.variable(_binOp.rightExpression()) <<
")\n";
}
else
solUnimplementedAssert(false, "");
}
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
{
auto const& decl = dynamic_cast<VariableDeclaration const&>(
*_identifier.annotation().referencedDeclaration
);
m_code << "let " << m_context.variable(_identifier) << " := " << m_context.variableName(decl) << "\n";
return false;
}

View File

@ -0,0 +1,59 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component that translates Solidity code into Yul at statement level and below.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
namespace dev
{
namespace solidity
{
class IRGenerationContext;
class YulUtilFunctions;
/**
* Component that translates Solidity's AST into Yul at statement level and below.
* It is an AST visitor that appends to an internal string buffer.
*/
class IRGeneratorForStatements: public ASTConstVisitor
{
public:
IRGeneratorForStatements(IRGenerationContext& _context, YulUtilFunctions& _utils):
m_context(_context),
m_utils(_utils)
{}
std::string code() const { return m_code.str(); }
bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Assignment const& _assignment) override;
void endVisit(BinaryOperation const& _binOp) override;
bool visit(Identifier const& _identifier) override;
private:
std::ostringstream m_code;
IRGenerationContext& m_context;
YulUtilFunctions& m_utils;
};
}
}

View File

@ -0,0 +1,18 @@
contract C {
function f(uint a, uint b) public pure returns (uint x) {
x = a + b;
}
function g(uint8 a, uint8 b) public pure returns (uint8 x) {
x = a + b;
}
}
// ====
// compileViaYul: true
// ----
// f(uint256,uint256): 5, 6 -> 11
// f(uint256,uint256): -2, 1 -> -1
// f(uint256,uint256): -2, 2 -> FAILURE
// f(uint256,uint256): 2, -2 -> FAILURE
// g(uint8,uint8): 128, 64 -> 192
// g(uint8,uint8): 128, 127 -> 255
// g(uint8,uint8): 128, 128 -> FAILURE

View File

@ -0,0 +1,10 @@
contract C {
function f(address a) public pure returns (address x) {
address b = a;
x = b;
}
}
// ====
// compileViaYul: true
// ----
// f(address): 0x1234 -> 0x1234

View File

@ -0,0 +1,10 @@
contract C {
function f(uint a) public pure returns (uint x) {
uint b = a;
x = b;
}
}
// ====
// compileViaYul: true
// ----
// f(uint256): 6 -> 6

View File

@ -0,0 +1,10 @@
contract C {
function f(bool a) public pure returns (bool x) {
bool b = a;
x = b;
}
}
// ====
// compileViaYul: true
// ----
// f(bool): true -> true

View File

@ -0,0 +1,10 @@
contract C {
function f(uint a, uint b) public pure returns (uint x, uint y) {
x = a;
y = b;
}
}
// ====
// compileViaYul: true
// ----
// f(uint256,uint256): 5, 6 -> 5, 6