mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
1f30982ab5
Using libraries leaves behind a library link reference in the binary which the linker must later resolve. These link references were still being generated by name and not by fully-qualified name. This would lead to a link-time collision between two libraries having the same name but in different source units. This change changes linker symbols over to fully-qualified names, which resolves that issue. This does potentially introduce a new problem, which is that linker symbols appear to be limited to 36 characters and are truncated. Storing paths extends the average symbol size, and it would be great if truncation was from the tail rather than the head.
1786 lines
62 KiB
C++
1786 lines
62 KiB
C++
/*
|
|
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/>.
|
|
*/
|
|
/**
|
|
* @author Christian <c@ethdev.com>
|
|
* @date 2014
|
|
* Solidity AST to EVM bytecode compiler for expressions.
|
|
*/
|
|
|
|
#include <utility>
|
|
#include <numeric>
|
|
#include <boost/range/adaptor/reversed.hpp>
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <libdevcore/Common.h>
|
|
#include <libdevcore/SHA3.h>
|
|
#include <libsolidity/ast/AST.h>
|
|
#include <libsolidity/codegen/ExpressionCompiler.h>
|
|
#include <libsolidity/codegen/CompilerContext.h>
|
|
#include <libsolidity/codegen/CompilerUtils.h>
|
|
#include <libsolidity/codegen/LValue.h>
|
|
#include <libevmasm/GasMeter.h>
|
|
using namespace std;
|
|
|
|
namespace dev
|
|
{
|
|
namespace solidity
|
|
{
|
|
|
|
void ExpressionCompiler::compile(Expression const& _expression)
|
|
{
|
|
_expression.accept(*this);
|
|
}
|
|
|
|
void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl)
|
|
{
|
|
if (!_varDecl.value())
|
|
return;
|
|
TypePointer type = _varDecl.value()->annotation().type;
|
|
solAssert(!!type, "Type information not available.");
|
|
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
|
|
_varDecl.value()->accept(*this);
|
|
|
|
if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage))
|
|
{
|
|
// reference type, only convert value to mobile type and do final conversion in storeValue.
|
|
auto mt = type->mobileType();
|
|
solAssert(mt, "");
|
|
utils().convertType(*type, *mt);
|
|
type = mt;
|
|
}
|
|
else
|
|
{
|
|
utils().convertType(*type, *_varDecl.annotation().type);
|
|
type = _varDecl.annotation().type;
|
|
}
|
|
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
|
|
}
|
|
|
|
void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl)
|
|
{
|
|
solAssert(_varDecl.isConstant(), "");
|
|
_varDecl.value()->accept(*this);
|
|
utils().convertType(*_varDecl.value()->annotation().type, *_varDecl.annotation().type);
|
|
|
|
// append return
|
|
m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1);
|
|
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
|
|
}
|
|
|
|
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
|
|
{
|
|
solAssert(!_varDecl.isConstant(), "");
|
|
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
|
|
FunctionType accessorType(_varDecl);
|
|
|
|
TypePointers paramTypes = accessorType.parameterTypes();
|
|
|
|
// retrieve the position of the variable
|
|
auto const& location = m_context.storageLocationOfVariable(_varDecl);
|
|
m_context << location.first << u256(location.second);
|
|
|
|
TypePointer returnType = _varDecl.annotation().type;
|
|
|
|
for (size_t i = 0; i < paramTypes.size(); ++i)
|
|
{
|
|
if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get()))
|
|
{
|
|
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
|
|
solUnimplementedAssert(
|
|
!paramTypes[i]->isDynamicallySized(),
|
|
"Accessors for mapping with dynamically-sized keys not yet implemented."
|
|
);
|
|
// pop offset
|
|
m_context << Instruction::POP;
|
|
// move storage offset to memory.
|
|
utils().storeInMemory(32);
|
|
// move key to memory.
|
|
utils().copyToStackTop(paramTypes.size() - i, 1);
|
|
utils().storeInMemory(0);
|
|
m_context << u256(64) << u256(0) << Instruction::SHA3;
|
|
// push offset
|
|
m_context << u256(0);
|
|
returnType = mappingType->valueType();
|
|
}
|
|
else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get()))
|
|
{
|
|
// pop offset
|
|
m_context << Instruction::POP;
|
|
utils().copyToStackTop(paramTypes.size() - i + 1, 1);
|
|
ArrayUtils(m_context).accessIndex(*arrayType);
|
|
returnType = arrayType->baseType();
|
|
}
|
|
else
|
|
solAssert(false, "Index access is allowed only for \"mapping\" and \"array\" types.");
|
|
}
|
|
// remove index arguments.
|
|
if (paramTypes.size() == 1)
|
|
m_context << Instruction::SWAP2 << Instruction::POP << Instruction::SWAP1;
|
|
else if (paramTypes.size() >= 2)
|
|
{
|
|
m_context << swapInstruction(paramTypes.size());
|
|
m_context << Instruction::POP;
|
|
m_context << swapInstruction(paramTypes.size());
|
|
utils().popStackSlots(paramTypes.size() - 1);
|
|
}
|
|
unsigned retSizeOnStack = 0;
|
|
solAssert(accessorType.returnParameterTypes().size() >= 1, "");
|
|
auto const& returnTypes = accessorType.returnParameterTypes();
|
|
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
|
|
{
|
|
// remove offset
|
|
m_context << Instruction::POP;
|
|
auto const& names = accessorType.returnParameterNames();
|
|
// struct
|
|
for (size_t i = 0; i < names.size(); ++i)
|
|
{
|
|
if (returnTypes[i]->category() == Type::Category::Mapping)
|
|
continue;
|
|
if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i].get()))
|
|
if (!arrayType->isByteArray())
|
|
continue;
|
|
pair<u256, unsigned> const& offsets = structType->storageOffsetsOfMember(names[i]);
|
|
m_context << Instruction::DUP1 << u256(offsets.first) << Instruction::ADD << u256(offsets.second);
|
|
TypePointer memberType = structType->memberType(names[i]);
|
|
StorageItem(m_context, *memberType).retrieveValue(SourceLocation(), true);
|
|
utils().convertType(*memberType, *returnTypes[i]);
|
|
utils().moveToStackTop(returnTypes[i]->sizeOnStack());
|
|
retSizeOnStack += returnTypes[i]->sizeOnStack();
|
|
}
|
|
// remove slot
|
|
m_context << Instruction::POP;
|
|
}
|
|
else
|
|
{
|
|
// simple value or array
|
|
solAssert(returnTypes.size() == 1, "");
|
|
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
|
|
utils().convertType(*returnType, *returnTypes.front());
|
|
retSizeOnStack = returnTypes.front()->sizeOnStack();
|
|
}
|
|
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
|
|
solAssert(retSizeOnStack <= 15, "Stack is too deep.");
|
|
m_context << dupInstruction(retSizeOnStack + 1);
|
|
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(Conditional const& _condition)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _condition);
|
|
_condition.condition().accept(*this);
|
|
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
|
|
_condition.falseExpression().accept(*this);
|
|
utils().convertType(*_condition.falseExpression().annotation().type, *_condition.annotation().type);
|
|
eth::AssemblyItem endTag = m_context.appendJumpToNew();
|
|
m_context << trueTag;
|
|
int offset = _condition.annotation().type->sizeOnStack();
|
|
m_context.adjustStackOffset(-offset);
|
|
_condition.trueExpression().accept(*this);
|
|
utils().convertType(*_condition.trueExpression().annotation().type, *_condition.annotation().type);
|
|
m_context << endTag;
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(Assignment const& _assignment)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _assignment);
|
|
Token::Value op = _assignment.assignmentOperator();
|
|
Token::Value binOp = op == Token::Assign ? op : Token::AssignmentToBinaryOp(op);
|
|
Type const& leftType = *_assignment.leftHandSide().annotation().type;
|
|
if (leftType.category() == Type::Category::Tuple)
|
|
{
|
|
solAssert(*_assignment.annotation().type == TupleType(), "");
|
|
solAssert(op == Token::Assign, "");
|
|
}
|
|
else
|
|
solAssert(*_assignment.annotation().type == leftType, "");
|
|
bool cleanupNeeded = false;
|
|
if (op != Token::Assign)
|
|
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp);
|
|
_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.
|
|
TypePointer rightIntermediateType;
|
|
if (op != Token::Assign && Token::isShiftOp(binOp))
|
|
rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType();
|
|
else
|
|
rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType(
|
|
_assignment.leftHandSide().annotation().type
|
|
);
|
|
utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded);
|
|
|
|
_assignment.leftHandSide().accept(*this);
|
|
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
|
|
|
if (op == Token::Assign)
|
|
m_currentLValue->storeValue(*rightIntermediateType, _assignment.location());
|
|
else // compound assignment
|
|
{
|
|
solAssert(leftType.isValueType(), "Compound operators only available for value types.");
|
|
unsigned lvalueSize = m_currentLValue->sizeOnStack();
|
|
unsigned itemSize = _assignment.annotation().type->sizeOnStack();
|
|
if (lvalueSize > 0)
|
|
{
|
|
utils().copyToStackTop(lvalueSize + itemSize, itemSize);
|
|
utils().copyToStackTop(itemSize + lvalueSize, lvalueSize);
|
|
// value lvalue_ref value lvalue_ref
|
|
}
|
|
m_currentLValue->retrieveValue(_assignment.location(), true);
|
|
utils().convertType(leftType, leftType, cleanupNeeded);
|
|
|
|
if (Token::isShiftOp(binOp))
|
|
appendShiftOperatorCode(binOp, leftType, *rightIntermediateType);
|
|
else
|
|
{
|
|
solAssert(leftType == *rightIntermediateType, "");
|
|
appendOrdinaryBinaryOperatorCode(binOp, leftType);
|
|
}
|
|
if (lvalueSize > 0)
|
|
{
|
|
solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables.");
|
|
// value [lvalue_ref] updated_value
|
|
for (unsigned i = 0; i < itemSize; ++i)
|
|
m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP;
|
|
}
|
|
m_currentLValue->storeValue(*_assignment.annotation().type, _assignment.location());
|
|
}
|
|
m_currentLValue.reset();
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
|
{
|
|
if (_tuple.isInlineArray())
|
|
{
|
|
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
|
|
|
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
|
|
m_context << max(u256(32u), arrayType.memorySize());
|
|
utils().allocateMemory();
|
|
m_context << Instruction::DUP1;
|
|
|
|
for (auto const& component: _tuple.components())
|
|
{
|
|
component->accept(*this);
|
|
utils().convertType(*component->annotation().type, *arrayType.baseType(), true);
|
|
utils().storeInMemoryDynamic(*arrayType.baseType(), true);
|
|
}
|
|
|
|
m_context << Instruction::POP;
|
|
}
|
|
else
|
|
{
|
|
vector<unique_ptr<LValue>> lvalues;
|
|
for (auto const& component: _tuple.components())
|
|
if (component)
|
|
{
|
|
component->accept(*this);
|
|
if (_tuple.annotation().lValueRequested)
|
|
{
|
|
solAssert(!!m_currentLValue, "");
|
|
lvalues.push_back(move(m_currentLValue));
|
|
}
|
|
}
|
|
else if (_tuple.annotation().lValueRequested)
|
|
lvalues.push_back(unique_ptr<LValue>());
|
|
if (_tuple.annotation().lValueRequested)
|
|
{
|
|
if (_tuple.components().size() == 1)
|
|
m_currentLValue = move(lvalues[0]);
|
|
else
|
|
m_currentLValue.reset(new TupleObject(m_context, move(lvalues)));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
|
|
if (_unaryOperation.annotation().type->category() == Type::Category::RationalNumber)
|
|
{
|
|
m_context << _unaryOperation.annotation().type->literalValue(nullptr);
|
|
return false;
|
|
}
|
|
|
|
_unaryOperation.subExpression().accept(*this);
|
|
|
|
switch (_unaryOperation.getOperator())
|
|
{
|
|
case Token::Not: // !
|
|
m_context << Instruction::ISZERO;
|
|
break;
|
|
case Token::BitNot: // ~
|
|
m_context << Instruction::NOT;
|
|
break;
|
|
case Token::Delete: // delete
|
|
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
|
m_currentLValue->setToZero(_unaryOperation.location());
|
|
m_currentLValue.reset();
|
|
break;
|
|
case Token::Inc: // ++ (pre- or postfix)
|
|
case Token::Dec: // -- (pre- or postfix)
|
|
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
|
m_currentLValue->retrieveValue(_unaryOperation.location());
|
|
if (!_unaryOperation.isPrefixOperation())
|
|
{
|
|
// store value for later
|
|
solUnimplementedAssert(_unaryOperation.annotation().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;
|
|
else
|
|
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)
|
|
m_context << swapInstruction(i);
|
|
m_currentLValue->storeValue(
|
|
*_unaryOperation.annotation().type, _unaryOperation.location(),
|
|
!_unaryOperation.isPrefixOperation());
|
|
m_currentLValue.reset();
|
|
break;
|
|
case Token::Add: // +
|
|
// unary add, so basically no-op
|
|
break;
|
|
case Token::Sub: // -
|
|
m_context << u256(0) << Instruction::SUB;
|
|
break;
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " +
|
|
string(Token::toString(_unaryOperation.getOperator()))));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation);
|
|
Expression const& leftExpression = _binaryOperation.leftExpression();
|
|
Expression const& rightExpression = _binaryOperation.rightExpression();
|
|
solAssert(!!_binaryOperation.annotation().commonType, "");
|
|
TypePointer const& commonType = _binaryOperation.annotation().commonType;
|
|
Token::Value const c_op = _binaryOperation.getOperator();
|
|
|
|
if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting
|
|
appendAndOrOperatorCode(_binaryOperation);
|
|
else if (commonType->category() == Type::Category::RationalNumber)
|
|
m_context << commonType->literalValue(nullptr);
|
|
else
|
|
{
|
|
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op);
|
|
|
|
TypePointer leftTargetType = commonType;
|
|
TypePointer rightTargetType = Token::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType;
|
|
|
|
// for commutative operators, push the literal as late as possible to allow improved optimization
|
|
auto isLiteral = [](Expression const& _e)
|
|
{
|
|
return dynamic_cast<Literal const*>(&_e) || _e.annotation().type->category() == Type::Category::RationalNumber;
|
|
};
|
|
bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression);
|
|
if (swap)
|
|
{
|
|
leftExpression.accept(*this);
|
|
utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
|
|
rightExpression.accept(*this);
|
|
utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
|
|
}
|
|
else
|
|
{
|
|
rightExpression.accept(*this);
|
|
utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
|
|
leftExpression.accept(*this);
|
|
utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
|
|
}
|
|
if (Token::isShiftOp(c_op))
|
|
// shift only cares about the signedness of both sides
|
|
appendShiftOperatorCode(c_op, *leftTargetType, *rightTargetType);
|
|
else if (Token::isCompareOp(c_op))
|
|
appendCompareOperatorCode(c_op, *commonType);
|
|
else
|
|
appendOrdinaryBinaryOperatorCode(c_op, *commonType);
|
|
}
|
|
|
|
// do not visit the child nodes, we already did that explicitly
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
|
|
using Location = FunctionType::Location;
|
|
if (_functionCall.annotation().isTypeConversion)
|
|
{
|
|
solAssert(_functionCall.arguments().size() == 1, "");
|
|
solAssert(_functionCall.names().empty(), "");
|
|
Expression const& firstArgument = *_functionCall.arguments().front();
|
|
firstArgument.accept(*this);
|
|
utils().convertType(*firstArgument.annotation().type, *_functionCall.annotation().type);
|
|
return false;
|
|
}
|
|
|
|
FunctionTypePointer functionType;
|
|
if (_functionCall.annotation().isStructConstructorCall)
|
|
{
|
|
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
|
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
|
functionType = structType.constructorType();
|
|
}
|
|
else
|
|
functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
|
|
|
|
TypePointers parameterTypes = functionType->parameterTypes();
|
|
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
|
|
vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.names();
|
|
if (!functionType->takesArbitraryParameters())
|
|
solAssert(callArguments.size() == parameterTypes.size(), "");
|
|
|
|
vector<ASTPointer<Expression const>> arguments;
|
|
if (callArgumentNames.empty())
|
|
// normal arguments
|
|
arguments = callArguments;
|
|
else
|
|
// named arguments
|
|
for (auto const& parameterName: functionType->parameterNames())
|
|
{
|
|
bool found = false;
|
|
for (size_t j = 0; j < callArgumentNames.size() && !found; j++)
|
|
if ((found = (parameterName == *callArgumentNames[j])))
|
|
// we found the actual parameter position
|
|
arguments.push_back(callArguments[j]);
|
|
solAssert(found, "");
|
|
}
|
|
|
|
if (_functionCall.annotation().isStructConstructorCall)
|
|
{
|
|
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
|
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
|
|
|
m_context << max(u256(32u), structType.memorySize());
|
|
utils().allocateMemory();
|
|
m_context << Instruction::DUP1;
|
|
|
|
for (unsigned i = 0; i < arguments.size(); ++i)
|
|
{
|
|
arguments[i]->accept(*this);
|
|
utils().convertType(*arguments[i]->annotation().type, *functionType->parameterTypes()[i]);
|
|
utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]);
|
|
}
|
|
m_context << Instruction::POP;
|
|
}
|
|
else
|
|
{
|
|
FunctionType const& function = *functionType;
|
|
if (function.bound())
|
|
// Only delegatecall and internal functions can be bound, this might be lifted later.
|
|
solAssert(function.location() == Location::DelegateCall || function.location() == Location::Internal, "");
|
|
switch (function.location())
|
|
{
|
|
case Location::Internal:
|
|
{
|
|
// Calling convention: Caller pushes return address and arguments
|
|
// Callee removes them and pushes return values
|
|
|
|
eth::AssemblyItem returnLabel = m_context.pushNewTag();
|
|
for (unsigned i = 0; i < arguments.size(); ++i)
|
|
{
|
|
arguments[i]->accept(*this);
|
|
utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]);
|
|
}
|
|
_functionCall.expression().accept(*this);
|
|
unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes());
|
|
if (function.bound())
|
|
{
|
|
// stack: arg2, ..., argn, label, arg1
|
|
unsigned depth = parameterSize + 1;
|
|
utils().moveIntoStack(depth, function.selfType()->sizeOnStack());
|
|
parameterSize += function.selfType()->sizeOnStack();
|
|
}
|
|
|
|
if (m_context.runtimeContext())
|
|
// We have a runtime context, so we need the creation part.
|
|
m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
|
|
else
|
|
// Extract the runtime part.
|
|
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
|
|
|
m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction);
|
|
m_context << returnLabel;
|
|
|
|
unsigned returnParametersSize = CompilerUtils::sizeOnStack(function.returnParameterTypes());
|
|
// callee adds return parameters, but removes arguments and return label
|
|
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
|
|
break;
|
|
}
|
|
case Location::External:
|
|
case Location::CallCode:
|
|
case Location::DelegateCall:
|
|
case Location::Bare:
|
|
case Location::BareCallCode:
|
|
case Location::BareDelegateCall:
|
|
_functionCall.expression().accept(*this);
|
|
appendExternalFunctionCall(function, arguments);
|
|
break;
|
|
case Location::Creation:
|
|
{
|
|
_functionCall.expression().accept(*this);
|
|
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
|
|
solAssert(function.returnParameterTypes().size() == 1, "");
|
|
TypePointers argumentTypes;
|
|
for (auto const& arg: arguments)
|
|
{
|
|
arg->accept(*this);
|
|
argumentTypes.push_back(arg->annotation().type);
|
|
}
|
|
ContractDefinition const& contract =
|
|
dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
|
|
// copy the contract's code into memory
|
|
eth::Assembly const& assembly = m_context.compiledContract(contract);
|
|
utils().fetchFreeMemoryPointer();
|
|
// TODO we create a copy here, which is actually what we want.
|
|
// This should be revisited at the point where we fix
|
|
// https://github.com/ethereum/solidity/issues/1092
|
|
// pushes size
|
|
auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly));
|
|
m_context << Instruction::DUP1 << subroutine;
|
|
m_context << Instruction::DUP4 << Instruction::CODECOPY;
|
|
|
|
m_context << Instruction::ADD;
|
|
utils().encodeToMemory(argumentTypes, function.parameterTypes());
|
|
// now on stack: memory_end_ptr
|
|
// need: size, offset, endowment
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
if (function.valueSet())
|
|
m_context << dupInstruction(3);
|
|
else
|
|
m_context << u256(0);
|
|
m_context << Instruction::CREATE;
|
|
// Check if zero (out of stack or not enough balance).
|
|
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
|
m_context.appendConditionalJumpTo(m_context.errorTag());
|
|
if (function.valueSet())
|
|
m_context << swapInstruction(1) << Instruction::POP;
|
|
break;
|
|
}
|
|
case Location::SetGas:
|
|
{
|
|
// stack layout: contract_address function_id [gas] [value]
|
|
_functionCall.expression().accept(*this);
|
|
|
|
arguments.front()->accept(*this);
|
|
utils().convertType(*arguments.front()->annotation().type, IntegerType(256), true);
|
|
// Note that function is not the original function, but the ".gas" function.
|
|
// Its values of gasSet and valueSet is equal to the original function's though.
|
|
unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0);
|
|
if (stackDepth > 0)
|
|
m_context << swapInstruction(stackDepth);
|
|
if (function.gasSet())
|
|
m_context << Instruction::POP;
|
|
break;
|
|
}
|
|
case Location::SetValue:
|
|
// stack layout: contract_address function_id [gas] [value]
|
|
_functionCall.expression().accept(*this);
|
|
// Note that function is not the original function, but the ".value" function.
|
|
// Its values of gasSet and valueSet is equal to the original function's though.
|
|
if (function.valueSet())
|
|
m_context << Instruction::POP;
|
|
arguments.front()->accept(*this);
|
|
break;
|
|
case Location::Send:
|
|
_functionCall.expression().accept(*this);
|
|
// Provide the gas stipend manually at first because we may send zero ether.
|
|
// Will be zeroed if we send more than zero ether.
|
|
m_context << u256(eth::GasCosts::callStipend);
|
|
arguments.front()->accept(*this);
|
|
utils().convertType(
|
|
*arguments.front()->annotation().type,
|
|
*function.parameterTypes().front(), true
|
|
);
|
|
// gas <- gas * !value
|
|
m_context << Instruction::SWAP1 << Instruction::DUP2;
|
|
m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1;
|
|
appendExternalFunctionCall(
|
|
FunctionType(
|
|
TypePointers{},
|
|
TypePointers{},
|
|
strings(),
|
|
strings(),
|
|
Location::Bare,
|
|
false,
|
|
nullptr,
|
|
false,
|
|
false,
|
|
true,
|
|
true
|
|
),
|
|
{}
|
|
);
|
|
break;
|
|
case Location::Selfdestruct:
|
|
arguments.front()->accept(*this);
|
|
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
|
|
m_context << Instruction::SUICIDE;
|
|
break;
|
|
case Location::SHA3:
|
|
{
|
|
TypePointers argumentTypes;
|
|
for (auto const& arg: arguments)
|
|
{
|
|
arg->accept(*this);
|
|
argumentTypes.push_back(arg->annotation().type);
|
|
}
|
|
utils().fetchFreeMemoryPointer();
|
|
utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true);
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
m_context << Instruction::SHA3;
|
|
break;
|
|
}
|
|
case Location::Log0:
|
|
case Location::Log1:
|
|
case Location::Log2:
|
|
case Location::Log3:
|
|
case Location::Log4:
|
|
{
|
|
unsigned logNumber = int(function.location()) - int(Location::Log0);
|
|
for (unsigned arg = logNumber; arg > 0; --arg)
|
|
{
|
|
arguments[arg]->accept(*this);
|
|
utils().convertType(*arguments[arg]->annotation().type, *function.parameterTypes()[arg], true);
|
|
}
|
|
arguments.front()->accept(*this);
|
|
utils().fetchFreeMemoryPointer();
|
|
utils().encodeToMemory(
|
|
{arguments.front()->annotation().type},
|
|
{function.parameterTypes().front()},
|
|
false,
|
|
true);
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
m_context << logInstruction(logNumber);
|
|
break;
|
|
}
|
|
case Location::Event:
|
|
{
|
|
_functionCall.expression().accept(*this);
|
|
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
|
|
unsigned numIndexed = 0;
|
|
// All indexed arguments go to the stack
|
|
for (unsigned arg = arguments.size(); arg > 0; --arg)
|
|
if (event.parameters()[arg - 1]->isIndexed())
|
|
{
|
|
++numIndexed;
|
|
arguments[arg - 1]->accept(*this);
|
|
if (auto const& arrayType = dynamic_pointer_cast<ArrayType const>(function.parameterTypes()[arg - 1]))
|
|
{
|
|
utils().fetchFreeMemoryPointer();
|
|
utils().encodeToMemory(
|
|
{arguments[arg - 1]->annotation().type},
|
|
{arrayType},
|
|
false,
|
|
true
|
|
);
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
m_context << Instruction::SHA3;
|
|
}
|
|
else
|
|
utils().convertType(
|
|
*arguments[arg - 1]->annotation().type,
|
|
*function.parameterTypes()[arg - 1],
|
|
true
|
|
);
|
|
}
|
|
if (!event.isAnonymous())
|
|
{
|
|
m_context << u256(h256::Arith(dev::keccak256(function.externalSignature())));
|
|
++numIndexed;
|
|
}
|
|
solAssert(numIndexed <= 4, "Too many indexed arguments.");
|
|
// Copy all non-indexed arguments to memory (data)
|
|
// Memory position is only a hack and should be removed once we have free memory pointer.
|
|
TypePointers nonIndexedArgTypes;
|
|
TypePointers nonIndexedParamTypes;
|
|
for (unsigned arg = 0; arg < arguments.size(); ++arg)
|
|
if (!event.parameters()[arg]->isIndexed())
|
|
{
|
|
arguments[arg]->accept(*this);
|
|
nonIndexedArgTypes.push_back(arguments[arg]->annotation().type);
|
|
nonIndexedParamTypes.push_back(function.parameterTypes()[arg]);
|
|
}
|
|
utils().fetchFreeMemoryPointer();
|
|
utils().encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes);
|
|
// need: topic1 ... topicn memsize memstart
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
m_context << logInstruction(numIndexed);
|
|
break;
|
|
}
|
|
case Location::BlockHash:
|
|
{
|
|
arguments[0]->accept(*this);
|
|
utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true);
|
|
m_context << Instruction::BLOCKHASH;
|
|
break;
|
|
}
|
|
case Location::AddMod:
|
|
case Location::MulMod:
|
|
{
|
|
for (unsigned i = 0; i < 3; i ++)
|
|
{
|
|
arguments[2 - i]->accept(*this);
|
|
utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256));
|
|
}
|
|
if (function.location() == Location::AddMod)
|
|
m_context << Instruction::ADDMOD;
|
|
else
|
|
m_context << Instruction::MULMOD;
|
|
break;
|
|
}
|
|
case Location::ECRecover:
|
|
case Location::SHA256:
|
|
case Location::RIPEMD160:
|
|
{
|
|
_functionCall.expression().accept(*this);
|
|
static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
|
|
{Location::SHA256, 2},
|
|
{Location::RIPEMD160, 3}};
|
|
m_context << contractAddresses.find(function.location())->second;
|
|
for (unsigned i = function.sizeOnStack(); i > 0; --i)
|
|
m_context << swapInstruction(i);
|
|
appendExternalFunctionCall(function, arguments);
|
|
break;
|
|
}
|
|
case Location::ByteArrayPush:
|
|
case Location::ArrayPush:
|
|
{
|
|
_functionCall.expression().accept(*this);
|
|
solAssert(function.parameterTypes().size() == 1, "");
|
|
solAssert(!!function.parameterTypes()[0], "");
|
|
TypePointer paramType = function.parameterTypes()[0];
|
|
shared_ptr<ArrayType> arrayType =
|
|
function.location() == Location::ArrayPush ?
|
|
make_shared<ArrayType>(DataLocation::Storage, paramType) :
|
|
make_shared<ArrayType>(DataLocation::Storage);
|
|
// get the current length
|
|
ArrayUtils(m_context).retrieveLength(*arrayType);
|
|
m_context << Instruction::DUP1;
|
|
// stack: ArrayReference currentLength currentLength
|
|
m_context << u256(1) << Instruction::ADD;
|
|
// stack: ArrayReference currentLength newLength
|
|
m_context << Instruction::DUP3 << Instruction::DUP2;
|
|
ArrayUtils(m_context).resizeDynamicArray(*arrayType);
|
|
m_context << Instruction::SWAP2 << Instruction::SWAP1;
|
|
// stack: newLength ArrayReference oldLength
|
|
ArrayUtils(m_context).accessIndex(*arrayType, false);
|
|
|
|
// stack: newLength storageSlot slotOffset
|
|
arguments[0]->accept(*this);
|
|
// stack: newLength storageSlot slotOffset argValue
|
|
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
|
|
utils().convertType(*arguments[0]->annotation().type, *type);
|
|
utils().moveToStackTop(1 + type->sizeOnStack());
|
|
utils().moveToStackTop(1 + type->sizeOnStack());
|
|
// stack: newLength argValue storageSlot slotOffset
|
|
if (function.location() == Location::ArrayPush)
|
|
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
|
|
else
|
|
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
|
|
break;
|
|
}
|
|
case Location::ObjectCreation:
|
|
{
|
|
// Will allocate at the end of memory (MSIZE) and not write at all unless the base
|
|
// type is dynamically sized.
|
|
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
|
|
_functionCall.expression().accept(*this);
|
|
solAssert(arguments.size() == 1, "");
|
|
|
|
// Fetch requested length.
|
|
arguments[0]->accept(*this);
|
|
utils().convertType(*arguments[0]->annotation().type, IntegerType(256));
|
|
|
|
// Stack: requested_length
|
|
// Allocate at max(MSIZE, freeMemoryPointer)
|
|
utils().fetchFreeMemoryPointer();
|
|
m_context << Instruction::DUP1 << Instruction::MSIZE;
|
|
m_context << Instruction::LT;
|
|
auto initialise = m_context.appendConditionalJump();
|
|
// Free memory pointer does not point to empty memory, use MSIZE.
|
|
m_context << Instruction::POP;
|
|
m_context << Instruction::MSIZE;
|
|
m_context << initialise;
|
|
|
|
// Stack: requested_length memptr
|
|
m_context << Instruction::SWAP1;
|
|
// Stack: memptr requested_length
|
|
// store length
|
|
m_context << Instruction::DUP1 << Instruction::DUP3 << Instruction::MSTORE;
|
|
// Stack: memptr requested_length
|
|
// update free memory pointer
|
|
m_context << Instruction::DUP1 << arrayType.baseType()->memoryHeadSize();
|
|
m_context << Instruction::MUL << u256(32) << Instruction::ADD;
|
|
m_context << Instruction::DUP3 << Instruction::ADD;
|
|
utils().storeFreeMemoryPointer();
|
|
// Stack: memptr requested_length
|
|
|
|
// Check if length is zero
|
|
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
|
auto skipInit = m_context.appendConditionalJump();
|
|
|
|
// We only have to initialise if the base type is a not a value type.
|
|
if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get()))
|
|
{
|
|
m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
|
|
utils().zeroInitialiseMemoryArray(arrayType);
|
|
}
|
|
m_context << skipInit;
|
|
m_context << Instruction::POP;
|
|
break;
|
|
}
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type."));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(NewExpression const&)
|
|
{
|
|
// code is created for the function call (CREATION) only
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _memberAccess);
|
|
// Check whether the member is a bound function.
|
|
ASTString const& member = _memberAccess.memberName();
|
|
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
|
|
if (funType->bound())
|
|
{
|
|
_memberAccess.expression().accept(*this);
|
|
utils().convertType(
|
|
*_memberAccess.expression().annotation().type,
|
|
*funType->selfType(),
|
|
true
|
|
);
|
|
if (funType->location() == FunctionType::Location::Internal)
|
|
{
|
|
FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration());
|
|
utils().pushCombinedFunctionEntryLabel(funDef);
|
|
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1);
|
|
}
|
|
else
|
|
{
|
|
solAssert(funType->location() == FunctionType::Location::DelegateCall, "");
|
|
auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope());
|
|
solAssert(contract && contract->isLibrary(), "");
|
|
m_context.appendLibraryAddress(contract->fullyQualifiedName());
|
|
m_context << funType->externalIdentifier();
|
|
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Special processing for TypeType because we do not want to visit the library itself
|
|
// for internal functions, or enum/struct definitions.
|
|
if (TypeType const* type = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type.get()))
|
|
{
|
|
if (dynamic_cast<ContractType const*>(type->actualType().get()))
|
|
{
|
|
solAssert(_memberAccess.annotation().type, "_memberAccess has no type");
|
|
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
|
|
{
|
|
switch (funType->location())
|
|
{
|
|
case FunctionType::Location::Internal:
|
|
// We do not visit the expression here on purpose, because in the case of an
|
|
// internal library function call, this would push the library address forcing
|
|
// us to link against it although we actually do not need it.
|
|
if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
|
|
utils().pushCombinedFunctionEntryLabel(*function);
|
|
else
|
|
solAssert(false, "Function not found in member access");
|
|
break;
|
|
case FunctionType::Location::Event:
|
|
if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration))
|
|
solAssert(false, "event not found");
|
|
// no-op, because the parent node will do the job
|
|
break;
|
|
case FunctionType::Location::External:
|
|
case FunctionType::Location::Creation:
|
|
case FunctionType::Location::DelegateCall:
|
|
case FunctionType::Location::CallCode:
|
|
case FunctionType::Location::Send:
|
|
case FunctionType::Location::Bare:
|
|
case FunctionType::Location::BareCallCode:
|
|
case FunctionType::Location::BareDelegateCall:
|
|
_memberAccess.expression().accept(*this);
|
|
m_context << funType->externalIdentifier();
|
|
break;
|
|
case FunctionType::Location::Log0:
|
|
case FunctionType::Location::Log1:
|
|
case FunctionType::Location::Log2:
|
|
case FunctionType::Location::Log3:
|
|
case FunctionType::Location::Log4:
|
|
case FunctionType::Location::ECRecover:
|
|
case FunctionType::Location::SHA256:
|
|
case FunctionType::Location::RIPEMD160:
|
|
default:
|
|
solAssert(false, "unsupported member function");
|
|
}
|
|
}
|
|
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type.get()))
|
|
{
|
|
// no-op
|
|
}
|
|
else if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
|
appendVariable(*variable, static_cast<Expression const&>(_memberAccess));
|
|
else
|
|
_memberAccess.expression().accept(*this);
|
|
}
|
|
else if (auto enumType = dynamic_cast<EnumType const*>(type->actualType().get()))
|
|
{
|
|
_memberAccess.expression().accept(*this);
|
|
m_context << enumType->memberValue(_memberAccess.memberName());
|
|
}
|
|
else
|
|
_memberAccess.expression().accept(*this);
|
|
return false;
|
|
}
|
|
|
|
_memberAccess.expression().accept(*this);
|
|
switch (_memberAccess.expression().annotation().type->category())
|
|
{
|
|
case Type::Category::Contract:
|
|
{
|
|
bool alsoSearchInteger = false;
|
|
ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
|
|
if (type.isSuper())
|
|
{
|
|
solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
|
|
utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
|
|
dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
|
|
type.contractDefinition()
|
|
));
|
|
}
|
|
else
|
|
{
|
|
// ordinary contract type
|
|
if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
|
|
{
|
|
u256 identifier;
|
|
if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
|
|
identifier = FunctionType(*variable).externalIdentifier();
|
|
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
|
|
identifier = FunctionType(*function).externalIdentifier();
|
|
else
|
|
solAssert(false, "Contract member is neither variable nor function.");
|
|
utils().convertType(type, IntegerType(0, IntegerType::Modifier::Address), true);
|
|
m_context << identifier;
|
|
}
|
|
else
|
|
// not found in contract, search in members inherited from address
|
|
alsoSearchInteger = true;
|
|
}
|
|
if (!alsoSearchInteger)
|
|
break;
|
|
}
|
|
case Type::Category::Integer:
|
|
if (member == "balance")
|
|
{
|
|
utils().convertType(
|
|
*_memberAccess.expression().annotation().type,
|
|
IntegerType(0, IntegerType::Modifier::Address),
|
|
true
|
|
);
|
|
m_context << Instruction::BALANCE;
|
|
}
|
|
else if ((set<string>{"send", "call", "callcode", "delegatecall"}).count(member))
|
|
utils().convertType(
|
|
*_memberAccess.expression().annotation().type,
|
|
IntegerType(0, IntegerType::Modifier::Address),
|
|
true
|
|
);
|
|
else
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer."));
|
|
break;
|
|
case Type::Category::Function:
|
|
solAssert(!!_memberAccess.expression().annotation().type->memberType(member),
|
|
"Invalid member access to function.");
|
|
break;
|
|
case Type::Category::Magic:
|
|
// we can ignore the kind of magic and only look at the name of the member
|
|
if (member == "coinbase")
|
|
m_context << Instruction::COINBASE;
|
|
else if (member == "timestamp")
|
|
m_context << Instruction::TIMESTAMP;
|
|
else if (member == "difficulty")
|
|
m_context << Instruction::DIFFICULTY;
|
|
else if (member == "number")
|
|
m_context << Instruction::NUMBER;
|
|
else if (member == "gaslimit")
|
|
m_context << Instruction::GASLIMIT;
|
|
else if (member == "sender")
|
|
m_context << Instruction::CALLER;
|
|
else if (member == "value")
|
|
m_context << Instruction::CALLVALUE;
|
|
else if (member == "origin")
|
|
m_context << Instruction::ORIGIN;
|
|
else if (member == "gas")
|
|
m_context << Instruction::GAS;
|
|
else if (member == "gasprice")
|
|
m_context << Instruction::GASPRICE;
|
|
else if (member == "data")
|
|
m_context << u256(0) << Instruction::CALLDATASIZE;
|
|
else if (member == "sig")
|
|
m_context << u256(0) << Instruction::CALLDATALOAD
|
|
<< (u256(0xffffffff) << (256 - 32)) << Instruction::AND;
|
|
else
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
|
|
break;
|
|
case Type::Category::Struct:
|
|
{
|
|
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.expression().annotation().type);
|
|
switch (type.location())
|
|
{
|
|
case DataLocation::Storage:
|
|
{
|
|
pair<u256, unsigned> const& offsets = type.storageOffsetsOfMember(member);
|
|
m_context << offsets.first << Instruction::ADD << u256(offsets.second);
|
|
setLValueToStorageItem(_memberAccess);
|
|
break;
|
|
}
|
|
case DataLocation::Memory:
|
|
{
|
|
m_context << type.memoryOffsetOfMember(member) << Instruction::ADD;
|
|
setLValue<MemoryItem>(_memberAccess, *_memberAccess.annotation().type);
|
|
break;
|
|
}
|
|
default:
|
|
solAssert(false, "Illegal data location for struct.");
|
|
}
|
|
break;
|
|
}
|
|
case Type::Category::Enum:
|
|
{
|
|
EnumType const& type = dynamic_cast<EnumType const&>(*_memberAccess.expression().annotation().type);
|
|
m_context << type.memberValue(_memberAccess.memberName());
|
|
break;
|
|
}
|
|
case Type::Category::Array:
|
|
{
|
|
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
|
|
if (member == "length")
|
|
{
|
|
if (!type.isDynamicallySized())
|
|
{
|
|
utils().popStackElement(type);
|
|
m_context << type.length();
|
|
}
|
|
else
|
|
switch (type.location())
|
|
{
|
|
case DataLocation::CallData:
|
|
m_context << Instruction::SWAP1 << Instruction::POP;
|
|
break;
|
|
case DataLocation::Storage:
|
|
setLValue<StorageArrayLength>(_memberAccess, type);
|
|
break;
|
|
case DataLocation::Memory:
|
|
m_context << Instruction::MLOAD;
|
|
break;
|
|
}
|
|
}
|
|
else if (member == "push")
|
|
{
|
|
solAssert(
|
|
type.isDynamicallySized() && type.location() == DataLocation::Storage,
|
|
"Tried to use .push() on a non-dynamically sized array"
|
|
);
|
|
}
|
|
else
|
|
solAssert(false, "Illegal array member.");
|
|
break;
|
|
}
|
|
case Type::Category::FixedBytes:
|
|
{
|
|
auto const& type = dynamic_cast<FixedBytesType const&>(*_memberAccess.expression().annotation().type);
|
|
utils().popStackElement(type);
|
|
if (member == "length")
|
|
m_context << u256(type.numBytes());
|
|
else
|
|
solAssert(false, "Illegal fixed bytes member.");
|
|
break;
|
|
}
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type."));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
|
|
_indexAccess.baseExpression().accept(*this);
|
|
|
|
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
|
|
|
|
if (baseType.category() == Type::Category::Mapping)
|
|
{
|
|
// stack: storage_base_ref
|
|
TypePointer keyType = dynamic_cast<MappingType const&>(baseType).keyType();
|
|
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
|
if (keyType->isDynamicallySized())
|
|
{
|
|
_indexAccess.indexExpression()->accept(*this);
|
|
utils().fetchFreeMemoryPointer();
|
|
// stack: base index mem
|
|
// note: the following operations must not allocate memory!
|
|
utils().encodeToMemory(
|
|
TypePointers{_indexAccess.indexExpression()->annotation().type},
|
|
TypePointers{keyType},
|
|
false,
|
|
true
|
|
);
|
|
m_context << Instruction::SWAP1;
|
|
utils().storeInMemoryDynamic(IntegerType(256));
|
|
utils().toSizeAfterFreeMemoryPointer();
|
|
}
|
|
else
|
|
{
|
|
m_context << u256(0); // memory position
|
|
appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression());
|
|
m_context << Instruction::SWAP1;
|
|
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
|
|
utils().storeInMemoryDynamic(IntegerType(256));
|
|
m_context << u256(0);
|
|
}
|
|
m_context << Instruction::SHA3;
|
|
m_context << u256(0);
|
|
setLValueToStorageItem(_indexAccess);
|
|
}
|
|
else if (baseType.category() == Type::Category::Array)
|
|
{
|
|
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
|
|
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
|
|
|
_indexAccess.indexExpression()->accept(*this);
|
|
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType(256), true);
|
|
// stack layout: <base_ref> [<length>] <index>
|
|
ArrayUtils(m_context).accessIndex(arrayType);
|
|
switch (arrayType.location())
|
|
{
|
|
case DataLocation::Storage:
|
|
if (arrayType.isByteArray())
|
|
{
|
|
solAssert(!arrayType.isString(), "Index access to string is not allowed.");
|
|
setLValue<StorageByteArrayElement>(_indexAccess);
|
|
}
|
|
else
|
|
setLValueToStorageItem(_indexAccess);
|
|
break;
|
|
case DataLocation::Memory:
|
|
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
|
|
break;
|
|
case DataLocation::CallData:
|
|
//@todo if we implement this, the value in calldata has to be added to the base offset
|
|
solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
|
|
if (arrayType.baseType()->isValueType())
|
|
CompilerUtils(m_context).loadFromMemoryDynamic(
|
|
*arrayType.baseType(),
|
|
true,
|
|
!arrayType.isByteArray(),
|
|
false
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
else if (baseType.category() == Type::Category::FixedBytes)
|
|
{
|
|
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
|
|
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
|
|
|
_indexAccess.indexExpression()->accept(*this);
|
|
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType(256), true);
|
|
// stack layout: <value> <index>
|
|
// check out-of-bounds access
|
|
m_context << u256(fixedBytesType.numBytes());
|
|
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
|
|
// out-of-bounds access throws exception
|
|
m_context.appendConditionalJumpTo(m_context.errorTag());
|
|
|
|
m_context << Instruction::BYTE;
|
|
m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
|
|
}
|
|
else if (baseType.category() == Type::Category::TypeType)
|
|
{
|
|
solAssert(baseType.sizeOnStack() == 0, "");
|
|
solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, "");
|
|
// no-op - this seems to be a lone array type (`structType[];`)
|
|
}
|
|
else
|
|
solAssert(false, "Index access only allowed for mappings or arrays.");
|
|
|
|
return false;
|
|
}
|
|
|
|
void ExpressionCompiler::endVisit(Identifier const& _identifier)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _identifier);
|
|
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
|
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
|
{
|
|
switch (magicVar->type()->category())
|
|
{
|
|
case Type::Category::Contract:
|
|
// "this" or "super"
|
|
if (!dynamic_cast<ContractType const&>(*magicVar->type()).isSuper())
|
|
m_context << Instruction::ADDRESS;
|
|
break;
|
|
case Type::Category::Integer:
|
|
// "now"
|
|
m_context << Instruction::TIMESTAMP;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
|
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
|
|
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
|
|
appendVariable(*variable, static_cast<Expression const&>(_identifier));
|
|
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
|
{
|
|
if (contract->isLibrary())
|
|
m_context.appendLibraryAddress(contract->fullyQualifiedName());
|
|
}
|
|
else if (dynamic_cast<EventDefinition const*>(declaration))
|
|
{
|
|
// no-op
|
|
}
|
|
else if (dynamic_cast<EnumDefinition const*>(declaration))
|
|
{
|
|
// no-op
|
|
}
|
|
else if (dynamic_cast<StructDefinition const*>(declaration))
|
|
{
|
|
// no-op
|
|
}
|
|
else
|
|
{
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context."));
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::endVisit(Literal const& _literal)
|
|
{
|
|
CompilerContext::LocationSetter locationSetter(m_context, _literal);
|
|
TypePointer type = _literal.annotation().type;
|
|
|
|
switch (type->category())
|
|
{
|
|
case Type::Category::RationalNumber:
|
|
case Type::Category::Bool:
|
|
m_context << type->literalValue(&_literal);
|
|
break;
|
|
case Type::Category::StringLiteral:
|
|
break; // will be done during conversion
|
|
default:
|
|
solUnimplemented("Only integer, boolean and string literals implemented for now.");
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation)
|
|
{
|
|
Token::Value const c_op = _binaryOperation.getOperator();
|
|
solAssert(c_op == Token::Or || c_op == Token::And, "");
|
|
|
|
_binaryOperation.leftExpression().accept(*this);
|
|
m_context << Instruction::DUP1;
|
|
if (c_op == Token::And)
|
|
m_context << Instruction::ISZERO;
|
|
eth::AssemblyItem endLabel = m_context.appendConditionalJump();
|
|
m_context << Instruction::POP;
|
|
_binaryOperation.rightExpression().accept(*this);
|
|
m_context << endLabel;
|
|
}
|
|
|
|
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
|
|
{
|
|
if (_operator == Token::Equal || _operator == Token::NotEqual)
|
|
{
|
|
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
|
|
{
|
|
if (funType->location() == FunctionType::Location::Internal)
|
|
{
|
|
// We have to remove the upper bits (construction time value) because they might
|
|
// be "unknown" in one of the operands and not in the other.
|
|
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
|
m_context << Instruction::SWAP1;
|
|
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
|
}
|
|
}
|
|
m_context << Instruction::EQ;
|
|
if (_operator == Token::NotEqual)
|
|
m_context << Instruction::ISZERO;
|
|
}
|
|
else
|
|
{
|
|
bool isSigned = false;
|
|
if (auto type = dynamic_cast<IntegerType const*>(&_type))
|
|
isSigned = type->isSigned();
|
|
|
|
switch (_operator)
|
|
{
|
|
case Token::GreaterThanOrEqual:
|
|
m_context <<
|
|
(isSigned ? Instruction::SLT : Instruction::LT) <<
|
|
Instruction::ISZERO;
|
|
break;
|
|
case Token::LessThanOrEqual:
|
|
m_context <<
|
|
(isSigned ? Instruction::SGT : Instruction::GT) <<
|
|
Instruction::ISZERO;
|
|
break;
|
|
case Token::GreaterThan:
|
|
m_context << (isSigned ? Instruction::SGT : Instruction::GT);
|
|
break;
|
|
case Token::LessThan:
|
|
m_context << (isSigned ? Instruction::SLT : Instruction::LT);
|
|
break;
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator."));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
|
|
{
|
|
if (Token::isArithmeticOp(_operator))
|
|
appendArithmeticOperatorCode(_operator, _type);
|
|
else if (Token::isBitOp(_operator))
|
|
appendBitOperatorCode(_operator);
|
|
else
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator."));
|
|
}
|
|
|
|
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
|
|
{
|
|
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
|
bool const c_isSigned = type.isSigned();
|
|
|
|
if (_type.category() == Type::Category::FixedPoint)
|
|
solUnimplemented("Not yet implemented - FixedPointType.");
|
|
|
|
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.appendConditionalJumpTo(m_context.errorTag());
|
|
|
|
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:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator."));
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
|
|
{
|
|
switch (_operator)
|
|
{
|
|
case Token::BitOr:
|
|
m_context << Instruction::OR;
|
|
break;
|
|
case Token::BitAnd:
|
|
m_context << Instruction::AND;
|
|
break;
|
|
case Token::BitXor:
|
|
m_context << Instruction::XOR;
|
|
break;
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown bit operator."));
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type const& _valueType, Type const& _shiftAmountType)
|
|
{
|
|
// stack: shift_amount value_to_shift
|
|
|
|
bool c_valueSigned = false;
|
|
if (auto valueType = dynamic_cast<IntegerType const*>(&_valueType))
|
|
c_valueSigned = valueType->isSigned();
|
|
else
|
|
solAssert(dynamic_cast<FixedBytesType const*>(&_valueType), "Only integer and fixed bytes type supported for shifts.");
|
|
|
|
// The amount can be a RationalNumberType too.
|
|
bool c_amountSigned = false;
|
|
if (auto amountType = dynamic_cast<RationalNumberType const*>(&_shiftAmountType))
|
|
{
|
|
// This should be handled by the type checker.
|
|
solAssert(amountType->integerType(), "");
|
|
solAssert(!amountType->integerType()->isSigned(), "");
|
|
}
|
|
else if (auto amountType = dynamic_cast<IntegerType const*>(&_shiftAmountType))
|
|
c_amountSigned = amountType->isSigned();
|
|
else
|
|
solAssert(false, "Invalid shift amount type.");
|
|
|
|
// shift by negative amount throws exception
|
|
if (c_amountSigned)
|
|
{
|
|
m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
|
|
m_context.appendConditionalJumpTo(m_context.errorTag());
|
|
}
|
|
|
|
switch (_operator)
|
|
{
|
|
case Token::SHL:
|
|
m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL;
|
|
break;
|
|
case Token::SAR:
|
|
m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
|
|
break;
|
|
case Token::SHR:
|
|
default:
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator."));
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendExternalFunctionCall(
|
|
FunctionType const& _functionType,
|
|
vector<ASTPointer<Expression const>> const& _arguments
|
|
)
|
|
{
|
|
solAssert(
|
|
_functionType.takesArbitraryParameters() ||
|
|
_arguments.size() == _functionType.parameterTypes().size(), ""
|
|
);
|
|
|
|
// Assumed stack content here:
|
|
// <stack top>
|
|
// value [if _functionType.valueSet()]
|
|
// gas [if _functionType.gasSet()]
|
|
// self object [if bound - moved to top right away]
|
|
// function identifier [unless bare]
|
|
// contract address
|
|
|
|
unsigned selfSize = _functionType.bound() ? _functionType.selfType()->sizeOnStack() : 0;
|
|
unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0);
|
|
unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + selfSize + (_functionType.isBareCall() ? 0 : 1));
|
|
unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize);
|
|
unsigned valueStackPos = m_context.currentToBaseStackOffset(1);
|
|
|
|
// move self object to top
|
|
if (_functionType.bound())
|
|
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
|
|
|
|
using FunctionKind = FunctionType::Location;
|
|
FunctionKind funKind = _functionType.location();
|
|
bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
|
|
bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode;
|
|
bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall;
|
|
|
|
unsigned retSize = 0;
|
|
if (returnSuccessCondition)
|
|
retSize = 0; // return value actually is success condition
|
|
else
|
|
for (auto const& retType: _functionType.returnParameterTypes())
|
|
{
|
|
solAssert(!retType->isDynamicallySized(), "Unable to return dynamic type from external call.");
|
|
retSize += retType->calldataEncodedSize();
|
|
}
|
|
|
|
// Evaluate arguments.
|
|
TypePointers argumentTypes;
|
|
TypePointers parameterTypes = _functionType.parameterTypes();
|
|
bool manualFunctionId = false;
|
|
if (
|
|
(funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) &&
|
|
!_arguments.empty()
|
|
)
|
|
{
|
|
solAssert(_arguments.front()->annotation().type->mobileType(), "");
|
|
manualFunctionId =
|
|
_arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) ==
|
|
CompilerUtils::dataStartOffset;
|
|
}
|
|
if (manualFunctionId)
|
|
{
|
|
// If we have a Bare* and the first type has exactly 4 bytes, use it as
|
|
// function identifier.
|
|
_arguments.front()->accept(*this);
|
|
utils().convertType(
|
|
*_arguments.front()->annotation().type,
|
|
IntegerType(8 * CompilerUtils::dataStartOffset),
|
|
true
|
|
);
|
|
for (unsigned i = 0; i < gasValueSize; ++i)
|
|
m_context << swapInstruction(gasValueSize - i);
|
|
gasStackPos++;
|
|
valueStackPos++;
|
|
}
|
|
if (_functionType.bound())
|
|
{
|
|
argumentTypes.push_back(_functionType.selfType());
|
|
parameterTypes.insert(parameterTypes.begin(), _functionType.selfType());
|
|
}
|
|
for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i)
|
|
{
|
|
_arguments[i]->accept(*this);
|
|
argumentTypes.push_back(_arguments[i]->annotation().type);
|
|
}
|
|
|
|
if (funKind == FunctionKind::ECRecover)
|
|
{
|
|
// Clears 32 bytes of currently free memory and advances free memory pointer.
|
|
// Output area will be "start of input area" - 32.
|
|
// The reason is that a failing ECRecover cannot be detected, it will just return
|
|
// zero bytes (which we cannot detect).
|
|
solAssert(0 < retSize && retSize <= 32, "");
|
|
utils().fetchFreeMemoryPointer();
|
|
m_context << Instruction::DUP1 << u256(0) << Instruction::MSTORE;
|
|
m_context << u256(32) << Instruction::ADD;
|
|
utils().storeFreeMemoryPointer();
|
|
}
|
|
|
|
// Touch the end of the output area so that we do not pay for memory resize during the call
|
|
// (which we would have to subtract from the gas left)
|
|
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
|
// would remove that, so we use MSTORE here.
|
|
if (!_functionType.gasSet() && retSize > 0)
|
|
{
|
|
m_context << u256(0);
|
|
utils().fetchFreeMemoryPointer();
|
|
// This touches too much, but that way we save some rounding arithmetics
|
|
m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE;
|
|
}
|
|
|
|
// Copy function identifier to memory.
|
|
utils().fetchFreeMemoryPointer();
|
|
if (!_functionType.isBareCall() || manualFunctionId)
|
|
{
|
|
m_context << dupInstruction(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes));
|
|
utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false);
|
|
}
|
|
// If the function takes arbitrary parameters, copy dynamic length data in place.
|
|
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
|
// pointer on the stack).
|
|
utils().encodeToMemory(
|
|
argumentTypes,
|
|
parameterTypes,
|
|
_functionType.padArguments(),
|
|
_functionType.takesArbitraryParameters(),
|
|
isCallCode || isDelegateCall
|
|
);
|
|
|
|
// Stack now:
|
|
// <stack top>
|
|
// input_memory_end
|
|
// value [if _functionType.valueSet()]
|
|
// gas [if _functionType.gasSet()]
|
|
// function identifier [unless bare]
|
|
// contract address
|
|
|
|
// Output data will replace input data, unless we have ECRecover (then, output
|
|
// area will be 32 bytes just before input area).
|
|
// put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input>
|
|
m_context << u256(retSize);
|
|
utils().fetchFreeMemoryPointer(); // This is the start of input
|
|
if (funKind == FunctionKind::ECRecover)
|
|
{
|
|
// In this case, output is 32 bytes before input and has already been cleared.
|
|
m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1;
|
|
// Here: <input end> <output size> <outpos> <input pos>
|
|
m_context << Instruction::DUP1 << Instruction::DUP5 << Instruction::SUB;
|
|
m_context << Instruction::SWAP1;
|
|
}
|
|
else
|
|
{
|
|
m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::SUB;
|
|
m_context << Instruction::DUP2;
|
|
}
|
|
|
|
// CALL arguments: outSize, outOff, inSize, inOff (already present up to here)
|
|
// [value,] addr, gas (stack top)
|
|
if (isDelegateCall)
|
|
solAssert(!_functionType.valueSet(), "Value set for delegatecall");
|
|
else if (_functionType.valueSet())
|
|
m_context << dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
|
|
else
|
|
m_context << u256(0);
|
|
m_context << dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos));
|
|
|
|
bool existenceChecked = false;
|
|
// Check the the target contract exists (has code) for non-low-level calls.
|
|
if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall)
|
|
{
|
|
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
|
|
m_context.appendConditionalJumpTo(m_context.errorTag());
|
|
existenceChecked = true;
|
|
}
|
|
|
|
if (_functionType.gasSet())
|
|
m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
|
|
else
|
|
{
|
|
// send all gas except the amount needed to execute "SUB" and "CALL"
|
|
// @todo this retains too much gas for now, needs to be fine-tuned.
|
|
u256 gasNeededByCaller = eth::GasCosts::callGas + 10;
|
|
if (_functionType.valueSet())
|
|
gasNeededByCaller += eth::GasCosts::callValueTransferGas;
|
|
if (!isCallCode && !isDelegateCall && !existenceChecked)
|
|
gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know
|
|
m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB;
|
|
}
|
|
if (isDelegateCall)
|
|
m_context << Instruction::DELEGATECALL;
|
|
else if (isCallCode)
|
|
m_context << Instruction::CALLCODE;
|
|
else
|
|
m_context << Instruction::CALL;
|
|
|
|
unsigned remainsSize =
|
|
2 + // contract address, input_memory_end
|
|
_functionType.valueSet() +
|
|
_functionType.gasSet() +
|
|
(!_functionType.isBareCall() || manualFunctionId);
|
|
|
|
if (returnSuccessCondition)
|
|
m_context << swapInstruction(remainsSize);
|
|
else
|
|
{
|
|
//Propagate error condition (if CALL pushes 0 on stack).
|
|
m_context << Instruction::ISZERO;
|
|
m_context.appendConditionalJumpTo(m_context.errorTag());
|
|
}
|
|
|
|
utils().popStackSlots(remainsSize);
|
|
|
|
if (returnSuccessCondition)
|
|
{
|
|
// already there
|
|
}
|
|
else if (funKind == FunctionKind::RIPEMD160)
|
|
{
|
|
// fix: built-in contract returns right-aligned data
|
|
utils().fetchFreeMemoryPointer();
|
|
utils().loadFromMemoryDynamic(IntegerType(160), false, true, false);
|
|
utils().convertType(IntegerType(160), FixedBytesType(20));
|
|
}
|
|
else if (funKind == FunctionKind::ECRecover)
|
|
{
|
|
// Output is 32 bytes before input / free mem pointer.
|
|
// Failing ecrecover cannot be detected, so we clear output before the call.
|
|
m_context << u256(32);
|
|
utils().fetchFreeMemoryPointer();
|
|
m_context << Instruction::SUB << Instruction::MLOAD;
|
|
}
|
|
else if (!_functionType.returnParameterTypes().empty())
|
|
{
|
|
utils().fetchFreeMemoryPointer();
|
|
bool memoryNeeded = false;
|
|
for (auto const& retType: _functionType.returnParameterTypes())
|
|
{
|
|
utils().loadFromMemoryDynamic(*retType, false, true, true);
|
|
if (dynamic_cast<ReferenceType const*>(retType.get()))
|
|
memoryNeeded = true;
|
|
}
|
|
if (memoryNeeded)
|
|
utils().storeFreeMemoryPointer();
|
|
else
|
|
m_context << Instruction::POP;
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)
|
|
{
|
|
solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types.");
|
|
_expression.accept(*this);
|
|
utils().convertType(*_expression.annotation().type, _expectedType, true);
|
|
utils().storeInMemoryDynamic(_expectedType);
|
|
}
|
|
|
|
void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Expression const& _expression)
|
|
{
|
|
if (!_variable.isConstant())
|
|
setLValueFromDeclaration(_variable, _expression);
|
|
else
|
|
{
|
|
_variable.value()->accept(*this);
|
|
utils().convertType(*_variable.value()->annotation().type, *_variable.annotation().type);
|
|
}
|
|
}
|
|
|
|
void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression)
|
|
{
|
|
if (m_context.isLocalVariable(&_declaration))
|
|
setLValue<StackVariable>(_expression, dynamic_cast<VariableDeclaration const&>(_declaration));
|
|
else if (m_context.isStateVariable(&_declaration))
|
|
setLValue<StorageItem>(_expression, dynamic_cast<VariableDeclaration const&>(_declaration));
|
|
else
|
|
BOOST_THROW_EXCEPTION(InternalCompilerError()
|
|
<< errinfo_sourceLocation(_expression.location())
|
|
<< errinfo_comment("Identifier type not supported or identifier not found."));
|
|
}
|
|
|
|
void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
|
|
{
|
|
setLValue<StorageItem>(_expression, *_expression.annotation().type);
|
|
}
|
|
|
|
bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op)
|
|
{
|
|
if (Token::isCompareOp(_op) || Token::isShiftOp(_op))
|
|
return true;
|
|
else if (_type == Type::Category::Integer && (_op == Token::Div || _op == Token::Mod))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
CompilerUtils ExpressionCompiler::utils()
|
|
{
|
|
return CompilerUtils(m_context);
|
|
}
|
|
|
|
}
|
|
}
|