Browse Source

Merge remote-tracking branch 'upstream/develop' into addTests

cl-refactor
CJentzsch 10 years ago
parent
commit
b782425a1f
  1. 15
      libsolidity/AST.cpp
  2. 10
      libsolidity/AST.h
  3. 6
      libsolidity/AST_accept.h
  4. 5
      libsolidity/CompilerContext.cpp
  5. 3
      libsolidity/CompilerContext.h
  6. 204
      libsolidity/ExpressionCompiler.cpp
  7. 18
      libsolidity/ExpressionCompiler.h
  8. 24
      libsolidity/Parser.cpp
  9. 51
      libsolidity/Types.cpp
  10. 29
      libsolidity/Types.h
  11. 2
      libsolidity/grammar.txt
  12. 108
      test/SolidityEndToEndTest.cpp

15
libsolidity/AST.cpp

@ -331,20 +331,13 @@ bool FunctionCall::isTypeConversion() const
void NewExpression::checkTypeRequirements() void NewExpression::checkTypeRequirements()
{ {
m_contractName->checkTypeRequirements(); m_contractName->checkTypeRequirements();
for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements();
m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration()); m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration());
if (!m_contract) if (!m_contract)
BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
shared_ptr<ContractType const> type = make_shared<ContractType>(*m_contract); shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract);
m_type = type; TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes();
TypePointers const& parameterTypes = type->getConstructorType()->getParameterTypes(); m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType},
if (parameterTypes.size() != m_arguments.size()) FunctionType::Location::CREATION);
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for constructor call."));
for (size_t i = 0; i < m_arguments.size(); ++i)
if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i]))
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructor call."));
} }
void MemberAccess::checkTypeRequirements() void MemberAccess::checkTypeRequirements()

10
libsolidity/AST.h

@ -792,26 +792,22 @@ private:
}; };
/** /**
* Expression that creates a new contract, e.g. "new SomeContract(1, 2)". * Expression that creates a new contract, e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
*/ */
class NewExpression: public Expression class NewExpression: public Expression
{ {
public: public:
NewExpression(Location const& _location, ASTPointer<Identifier> const& _contractName, NewExpression(Location const& _location, ASTPointer<Identifier> const& _contractName):
std::vector<ASTPointer<Expression>> const& _arguments): Expression(_location), m_contractName(_contractName) {}
Expression(_location), m_contractName(_contractName), m_arguments(_arguments) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements() override;
std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; }
/// Returns the referenced contract. Can only be called after type checking. /// Returns the referenced contract. Can only be called after type checking.
ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; } ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; }
private: private:
ASTPointer<Identifier> m_contractName; ASTPointer<Identifier> m_contractName;
std::vector<ASTPointer<Expression>> m_arguments;
ContractDefinition const* m_contract = nullptr; ContractDefinition const* m_contract = nullptr;
}; };

6
libsolidity/AST_accept.h

@ -452,20 +452,14 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
void NewExpression::accept(ASTVisitor& _visitor) void NewExpression::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{
m_contractName->accept(_visitor); m_contractName->accept(_visitor);
listAccept(m_arguments, _visitor);
}
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
void NewExpression::accept(ASTConstVisitor& _visitor) const void NewExpression::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{
m_contractName->accept(_visitor); m_contractName->accept(_visitor);
listAccept(m_arguments, _visitor);
}
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }

5
libsolidity/CompilerContext.cpp

@ -95,6 +95,11 @@ unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const
return _baseOffset + m_asm.deposit(); return _baseOffset + m_asm.deposit();
} }
unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const
{
return -baseToCurrentStackOffset(-_offset);
}
u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
{ {
auto it = m_stateVariables.find(&_declaration); auto it = m_stateVariables.find(&_declaration);

3
libsolidity/CompilerContext.h

@ -62,6 +62,9 @@ public:
/// If supplied by a value returned by @ref getBaseStackOffsetOfVariable(variable), returns /// If supplied by a value returned by @ref getBaseStackOffsetOfVariable(variable), returns
/// the distance of that variable from the current top of the stack. /// the distance of that variable from the current top of the stack.
unsigned baseToCurrentStackOffset(unsigned _baseOffset) const; unsigned baseToCurrentStackOffset(unsigned _baseOffset) const;
/// Converts an offset relative to the current stack height to a value that can be used later
/// with baseToCurrentStackOffset to point to the same stack element.
unsigned currentToBaseStackOffset(unsigned _offset) const;
u256 getStorageLocationOfVariable(Declaration const& _declaration) const; u256 getStorageLocationOfVariable(Declaration const& _declaration) const;
/// Appends a JUMPI instruction to a new tag and @returns the tag /// Appends a JUMPI instruction to a new tag and @returns the tag

204
libsolidity/ExpressionCompiler.cpp

@ -232,27 +232,71 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
case Location::EXTERNAL: case Location::EXTERNAL:
case Location::BARE: case Location::BARE:
_functionCall.getExpression().accept(*this);
appendExternalFunctionCall(function, arguments, function.getLocation() == Location::BARE);
break;
case Location::CREATION:
{ {
FunctionCallOptions options; _functionCall.getExpression().accept(*this);
options.bare = function.getLocation() == Location::BARE; solAssert(!function.gasSet(), "Gas limit set for contract creation.");
options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; solAssert(function.getReturnParameterTypes().size() == 1, "");
appendExternalFunctionCall(function, arguments, options); ContractDefinition const& contract = dynamic_cast<ContractType const&>(
*function.getReturnParameterTypes().front()).getContractDefinition();
// copy the contract's code into memory
bytes const& bytecode = m_context.getCompiledContract(contract);
m_context << u256(bytecode.size());
//@todo could be done by actually appending the Assembly, but then we probably need to compile
// multiple times. Will revisit once external fuctions are inlined.
m_context.appendData(bytecode);
//@todo copy to memory position 0, shift as soon as we use memory
m_context << u256(0) << eth::Instruction::CODECOPY;
unsigned length = bytecode.size();
length += appendArgumentCopyToMemory(function.getParameterTypes(), arguments, length);
// size, offset, endowment
m_context << u256(length) << u256(0);
if (function.valueSet())
m_context << eth::dupInstruction(3);
else
m_context << u256(0);
m_context << eth::Instruction::CREATE;
if (function.valueSet())
m_context << eth::swapInstruction(1) << eth::Instruction::POP;
break; break;
} }
case Location::SEND: case Location::SET_GAS:
{ {
FunctionCallOptions options; // stack layout: contract_address function_id [gas] [value]
options.bare = true; _functionCall.getExpression().accept(*this);
options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; arguments.front()->accept(*this);
options.obtainValue = [&]() appendTypeConversion(*arguments.front()->getType(), IntegerType(256), true);
{ // Note that function is not the original function, but the ".gas" function.
arguments.front()->accept(*this); // Its values of gasSet and valueSet is equal to the original function's though.
appendTypeConversion(*arguments.front()->getType(), unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0);
*function.getParameterTypes().front(), true); if (stackDepth > 0)
}; m_context << eth::swapInstruction(stackDepth);
appendExternalFunctionCall(FunctionType(TypePointers{}, TypePointers{}, Location::EXTERNAL), {}, options); if (function.gasSet())
m_context << eth::Instruction::POP;
break; break;
} }
case Location::SET_VALUE:
// stack layout: contract_address function_id [gas] [value]
_functionCall.getExpression().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 << eth::Instruction::POP;
arguments.front()->accept(*this);
break;
case Location::SEND:
_functionCall.getExpression().accept(*this);
m_context << u256(0); // 0 gas, we do not want to execute code
arguments.front()->accept(*this);
appendTypeConversion(*arguments.front()->getType(),
*function.getParameterTypes().front(), true);
appendExternalFunctionCall(FunctionType(TypePointers{}, TypePointers{},
Location::EXTERNAL, true, true), {}, true);
break;
case Location::SUICIDE: case Location::SUICIDE:
arguments.front()->accept(*this); arguments.front()->accept(*this);
appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true);
@ -289,11 +333,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
static const map<Location, u256> contractAddresses{{Location::ECRECOVER, 1}, static const map<Location, u256> contractAddresses{{Location::ECRECOVER, 1},
{Location::SHA256, 2}, {Location::SHA256, 2},
{Location::RIPEMD160, 3}}; {Location::RIPEMD160, 3}};
u256 contractAddress = contractAddresses.find(function.getLocation())->second; m_context << contractAddresses.find(function.getLocation())->second;
FunctionCallOptions options; appendExternalFunctionCall(function, arguments, true);
options.bare = true;
options.obtainAddress = [&]() { m_context << contractAddress; };
appendExternalFunctionCall(function, arguments, options);
break; break;
} }
default: default:
@ -305,38 +346,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
bool ExpressionCompiler::visit(NewExpression const& _newExpression) bool ExpressionCompiler::visit(NewExpression const& _newExpression)
{ {
ContractType const* type = dynamic_cast<ContractType const*>(_newExpression.getType().get()); // code is created for the function call (CREATION) only
solAssert(type, "");
TypePointers const& types = type->getConstructorType()->getParameterTypes();
vector<ASTPointer<Expression const>> arguments = _newExpression.getArguments();
solAssert(arguments.size() == types.size(), "");
// copy the contracts code into memory
bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract());
m_context << u256(bytecode.size());
//@todo could be done by actually appending the Assembly, but then we probably need to compile
// multiple times. Will revisit once external fuctions are inlined.
m_context.appendData(bytecode);
//@todo copy to memory position 0, shift as soon as we use memory
m_context << u256(0) << eth::Instruction::CODECOPY;
unsigned dataOffset = bytecode.size();
for (unsigned i = 0; i < arguments.size(); ++i)
{
arguments[i]->accept(*this);
appendTypeConversion(*arguments[i]->getType(), *types[i], true);
unsigned const c_numBytes = types[i]->getCalldataEncodedSize();
if (c_numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(arguments[i]->getLocation())
<< errinfo_comment("Type " + types[i]->toString() + " not yet supported."));
bool const c_leftAligned = types[i]->getCategory() == Type::Category::STRING;
bool const c_padToWords = true;
dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, c_numBytes,
c_leftAligned, c_padToWords);
}
// size, offset, endowment
m_context << u256(dataOffset) << u256(0) << u256(0) << eth::Instruction::CREATE;
return false; return false;
} }
@ -370,6 +380,10 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer."));
break; break;
case Type::Category::FUNCTION:
solAssert(!!_memberAccess.getExpression().getType()->getMemberType(member),
"Invalid member access to function.");
break;
case Type::Category::MAGIC: case Type::Category::MAGIC:
// we can ignore the kind of magic and only look at the name of the member // we can ignore the kind of magic and only look at the name of the member
if (member == "coinbase") if (member == "coinbase")
@ -646,46 +660,60 @@ void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack)
void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functionType, void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functionType,
vector<ASTPointer<Expression const>> const& _arguments, vector<ASTPointer<Expression const>> const& _arguments,
FunctionCallOptions const& _options) bool bare)
{ {
solAssert(_arguments.size() == _functionType.getParameterTypes().size(), ""); solAssert(_arguments.size() == _functionType.getParameterTypes().size(), "");
_options.obtainAddress(); // Assumed stack content here:
if (!_options.bare) // <stack top>
CompilerUtils(m_context).storeInMemory(0, CompilerUtils::dataStartOffset); // value [if _functionType.valueSet()]
// gas [if _functionType.gasSet()]
// function identifier [unless bare]
// contract address
unsigned dataOffset = _options.bare ? 0 : CompilerUtils::dataStartOffset; // reserve 4 bytes for the function's hash identifier unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0);
for (unsigned i = 0; i < _arguments.size(); ++i)
unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (bare ? 0 : 1));
unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize);
unsigned valueStackPos = m_context.currentToBaseStackOffset(1);
if (!bare)
{ {
_arguments[i]->accept(*this); // copy function identifier
Type const& type = *_functionType.getParameterTypes()[i]; m_context << eth::dupInstruction(gasValueSize + 1);
appendTypeConversion(*_arguments[i]->getType(), type, true); CompilerUtils(m_context).storeInMemory(0, CompilerUtils::dataStartOffset);
unsigned const c_numBytes = type.getCalldataEncodedSize();
if (c_numBytes == 0 || c_numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(_arguments[i]->getLocation())
<< errinfo_comment("Type " + type.toString() + " not yet supported."));
bool const c_leftAligned = type.getCategory() == Type::Category::STRING;
bool const c_padToWords = true;
dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, c_numBytes,
c_leftAligned, c_padToWords);
} }
// reserve space for the function identifier
unsigned dataOffset = bare ? 0 : CompilerUtils::dataStartOffset;
dataOffset += appendArgumentCopyToMemory(_functionType.getParameterTypes(), _arguments, dataOffset);
//@todo only return the first return value for now //@todo only return the first return value for now
Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr :
_functionType.getReturnParameterTypes().front().get(); _functionType.getReturnParameterTypes().front().get();
unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0; unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0;
// CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top) // CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top)
m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0); m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0);
if (_options.obtainValue) if (_functionType.valueSet())
_options.obtainValue(); m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
else else
m_context << u256(0); m_context << u256(0);
m_context << eth::dupInstruction(6); //copy contract address m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos));
m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB if (_functionType.gasSet())
<< eth::Instruction::CALL m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
<< eth::Instruction::POP // @todo do not ignore failure indicator else
<< eth::Instruction::POP; // pop contract address // send all gas except for the 21 needed to execute "SUB" and "CALL"
m_context << u256(21) << eth::Instruction::GAS << eth::Instruction::SUB;
m_context << eth::Instruction::CALL
<< eth::Instruction::POP; // @todo do not ignore failure indicator
if (_functionType.valueSet())
m_context << eth::Instruction::POP;
if (_functionType.gasSet())
m_context << eth::Instruction::POP;
if (!bare)
m_context << eth::Instruction::POP;
m_context << eth::Instruction::POP; // pop contract address
if (retSize > 0) if (retSize > 0)
{ {
@ -694,6 +722,28 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
} }
} }
unsigned ExpressionCompiler::appendArgumentCopyToMemory(TypePointers const& _types,
vector<ASTPointer<Expression const>> const& _arguments,
unsigned _memoryOffset)
{
unsigned length = 0;
for (unsigned i = 0; i < _arguments.size(); ++i)
{
_arguments[i]->accept(*this);
appendTypeConversion(*_arguments[i]->getType(), *_types[i], true);
unsigned const c_numBytes = _types[i]->getCalldataEncodedSize();
if (c_numBytes == 0 || c_numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(_arguments[i]->getLocation())
<< errinfo_comment("Type " + _types[i]->toString() + " not yet supported."));
bool const c_leftAligned = _types[i]->getCategory() == Type::Category::STRING;
bool const c_padToWords = true;
length += CompilerUtils(m_context).storeInMemory(_memoryOffset + length, c_numBytes,
c_leftAligned, c_padToWords);
}
return length;
}
ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType,
unsigned _baseStackOffset): unsigned _baseStackOffset):
m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset), m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset),

18
libsolidity/ExpressionCompiler.h

@ -87,21 +87,13 @@ private:
//// Appends code that cleans higher-order bits for integer types. //// Appends code that cleans higher-order bits for integer types.
void appendHighBitsCleanup(IntegerType const& _typeOnStack); void appendHighBitsCleanup(IntegerType const& _typeOnStack);
/// Additional options used in appendExternalFunctionCall.
struct FunctionCallOptions
{
FunctionCallOptions() {}
/// Invoked to copy the address to the stack
std::function<void()> obtainAddress;
/// Invoked to copy the ethe value to the stack (if not specified, value is 0).
std::function<void()> obtainValue;
/// If true, do not prepend function index to call data
bool bare = false;
};
/// Appends code to call a function of the given type with the given arguments. /// Appends code to call a function of the given type with the given arguments.
void appendExternalFunctionCall(FunctionType const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments, void appendExternalFunctionCall(FunctionType const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments,
FunctionCallOptions const& _options = FunctionCallOptions()); bool bare = false);
/// Appends code that copies the given arguments to memory (with optional offset).
/// @returns the number of bytes copied to memory
unsigned appendArgumentCopyToMemory(TypePointers const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments,
unsigned _memoryOffset = 0);
/** /**
* Helper class to store and retrieve lvalues to and from various locations. * Helper class to store and retrieve lvalues to and from various locations.

24
libsolidity/Parser.cpp

@ -466,17 +466,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression()
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
Token::Value token = m_scanner->getCurrentToken(); Token::Value token = m_scanner->getCurrentToken();
if (token == Token::NEW) if (Token::isUnaryOp(token) || Token::isCountOp(token))
{
expectToken(Token::NEW);
ASTPointer<Identifier> contractName = ASTNodeFactory(*this).createNode<Identifier>(expectIdentifierToken());
expectToken(Token::LPAREN);
vector<ASTPointer<Expression>> arguments(parseFunctionCallArguments());
expectToken(Token::RPAREN);
nodeFactory.markEndPosition();
return nodeFactory.createNode<NewExpression>(contractName, arguments);
}
else if (Token::isUnaryOp(token) || Token::isCountOp(token))
{ {
// prefix expression // prefix expression
m_scanner->next(); m_scanner->next();
@ -500,7 +490,17 @@ ASTPointer<Expression> Parser::parseUnaryExpression()
ASTPointer<Expression> Parser::parseLeftHandSideExpression() ASTPointer<Expression> Parser::parseLeftHandSideExpression()
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<Expression> expression = parsePrimaryExpression(); ASTPointer<Expression> expression;
if (m_scanner->getCurrentToken() == Token::NEW)
{
expectToken(Token::NEW);
ASTPointer<Identifier> contractName = ASTNodeFactory(*this).createNode<Identifier>(expectIdentifierToken());
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<NewExpression>(contractName);
}
else
expression = parsePrimaryExpression();
while (true) while (true)
{ {
switch (m_scanner->getCurrentToken()) switch (m_scanner->getCurrentToken())

51
libsolidity/Types.cpp

@ -546,7 +546,8 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested."));
} }
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal) FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
m_location(_isInternal ? Location::INTERNAL : Location::EXTERNAL)
{ {
TypePointers params; TypePointers params;
TypePointers retParams; TypePointers retParams;
@ -558,7 +559,6 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
retParams.push_back(var->getType()); retParams.push_back(var->getType());
swap(params, m_parameterTypes); swap(params, m_parameterTypes);
swap(retParams, m_returnParameterTypes); swap(retParams, m_returnParameterTypes);
m_location = _isInternal ? Location::INTERNAL : Location::EXTERNAL;
} }
bool FunctionType::operator==(Type const& _other) const bool FunctionType::operator==(Type const& _other) const
@ -580,6 +580,9 @@ bool FunctionType::operator==(Type const& _other) const
if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(),
other.m_returnParameterTypes.cbegin(), typeCompare)) other.m_returnParameterTypes.cbegin(), typeCompare))
return false; return false;
//@todo this is ugly, but cannot be prevented right now
if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
return false;
return true; return true;
} }
@ -595,17 +598,45 @@ string FunctionType::toString() const
} }
unsigned FunctionType::getSizeOnStack() const unsigned FunctionType::getSizeOnStack() const
{
unsigned size = 0;
if (m_location == Location::EXTERNAL)
size = 2;
else if (m_location == Location::INTERNAL || m_location == Location::BARE)
size = 1;
if (m_gasSet)
size++;
if (m_valueSet)
size++;
return size;
}
MemberList const& FunctionType::getMembers() const
{ {
switch (m_location) switch (m_location)
{ {
case Location::INTERNAL:
return 1;
case Location::EXTERNAL: case Location::EXTERNAL:
return 2; case Location::CREATION:
case Location::ECRECOVER:
case Location::SHA256:
case Location::RIPEMD160:
case Location::BARE: case Location::BARE:
return 1; if (!m_members)
{
map<string, TypePointer> members{
{"gas", make_shared<FunctionType>(parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(true, false)},
Location::SET_GAS, m_gasSet, m_valueSet)},
{"value", make_shared<FunctionType>(parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(false, true)},
Location::SET_VALUE, m_gasSet, m_valueSet)}};
if (m_location == Location::CREATION)
members.erase("gas");
m_members.reset(new MemberList(members));
}
return *m_members;
default: default:
return 0; return EmptyMemberList;
} }
} }
@ -628,6 +659,12 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
return pointers; return pointers;
} }
TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const
{
return make_shared<FunctionType>(m_parameterTypes, m_returnParameterTypes, m_location,
m_gasSet || _setGas, m_valueSet || _setValue);
}
bool MappingType::operator==(Type const& _other) const bool MappingType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())

29
libsolidity/Types.h

@ -291,6 +291,8 @@ public:
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
ContractDefinition const& getContractDefinition() const { return m_contract; }
/// Returns the function type of the constructor. Note that the location part of the function type /// Returns the function type of the constructor. Note that the location part of the function type
/// is not used, as this type cannot be the type of a variable or expression. /// is not used, as this type cannot be the type of a variable or expression.
std::shared_ptr<FunctionType const> const& getConstructorType() const; std::shared_ptr<FunctionType const> const& getConstructorType() const;
@ -345,10 +347,15 @@ class FunctionType: public Type
{ {
public: public:
/// The meaning of the value(s) on the stack referencing the function: /// The meaning of the value(s) on the stack referencing the function:
/// INTERNAL: jump tag, EXTERNAL: contract address + function index, /// INTERNAL: jump tag, EXTERNAL: contract address + function identifier,
/// BARE: contract address (non-abi contract call) /// BARE: contract address (non-abi contract call)
/// OTHERS: special virtual function, nothing on the stack /// OTHERS: special virtual function, nothing on the stack
enum class Location { INTERNAL, EXTERNAL, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160, LOG0, LOG1, LOG2, LOG3, LOG4, BARE }; enum class Location { INTERNAL, EXTERNAL, CREATION, SEND,
SHA3, SUICIDE,
ECRECOVER, SHA256, RIPEMD160,
LOG0, LOG1, LOG2, LOG3, LOG4,
SET_GAS, SET_VALUE,
BARE };
virtual Category getCategory() const override { return Category::FUNCTION; } virtual Category getCategory() const override { return Category::FUNCTION; }
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true);
@ -357,9 +364,10 @@ public:
FunctionType(parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), FunctionType(parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes),
_location) {} _location) {}
FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes, FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes,
Location _location = Location::INTERNAL): Location _location = Location::INTERNAL,
bool _gasSet = false, bool _valueSet = false):
m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes), m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes),
m_location(_location) {} m_location(_location), m_gasSet(_gasSet), m_valueSet(_valueSet) {}
TypePointers const& getParameterTypes() const { return m_parameterTypes; } TypePointers const& getParameterTypes() const { return m_parameterTypes; }
TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; }
@ -370,16 +378,27 @@ public:
virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); }
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override; virtual unsigned getSizeOnStack() const override;
virtual MemberList const& getMembers() const override;
Location const& getLocation() const { return m_location; } Location const& getLocation() const { return m_location; }
std::string getCanonicalSignature() const; std::string getCanonicalSignature() const;
bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
/// @returns a copy of this type, where gas or value are set manually. This will never set one
/// of the parameters to fals.
TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const;
private: private:
static TypePointers parseElementaryTypeVector(strings const& _types); static TypePointers parseElementaryTypeVector(strings const& _types);
TypePointers m_parameterTypes; TypePointers m_parameterTypes;
TypePointers m_returnParameterTypes; TypePointers m_returnParameterTypes;
Location m_location; Location const m_location;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
mutable std::unique_ptr<MemberList> m_members;
}; };
/** /**

2
libsolidity/grammar.txt

@ -33,7 +33,7 @@ Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewE
// The expression syntax is actually much more complicated // The expression syntax is actually much more complicated
Assignment = Expression (AssignmentOp Expression) Assignment = Expression (AssignmentOp Expression)
FunctionCall = Expression '(' Expression ( ',' Expression )* ')' FunctionCall = Expression '(' Expression ( ',' Expression )* ')'
NewExpression = 'new' Identifier '(' ( Expression ( ',' Expression )* ) ')' NewExpression = 'new' Identifier
MemberAccess = Expression '.' Identifier MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expresison ']' IndexAccess = Expression '[' Expresison ']'
PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')'

108
test/SolidityEndToEndTest.cpp

@ -1295,7 +1295,113 @@ BOOST_AUTO_TEST_CASE(contracts_as_addresses)
} }
)"; )";
compileAndRun(sourceCode, 20); compileAndRun(sourceCode, 20);
BOOST_REQUIRE(callContractFunction("getBalance()") == toBigEndian(u256(20 - 5)) + toBigEndian(u256(5))); BOOST_REQUIRE(callContractFunction("getBalance()") == encodeArgs(u256(20 - 5), u256(5)));
}
BOOST_AUTO_TEST_CASE(gas_and_value_basic)
{
char const* sourceCode = R"(
contract helper {
bool flag;
function getBalance() returns (uint256 myBalance) {
return this.balance;
}
function setFlag() { flag = true; }
function getFlag() returns (bool fl) { return flag; }
}
contract test {
helper h;
function test() { h = new helper(); }
function sendAmount(uint amount) returns (uint256 bal) {
return h.getBalance.value(amount)();
}
function outOfGas() returns (bool flagBefore, bool flagAfter, uint myBal) {
flagBefore = h.getFlag();
h.setFlag.gas(2)(); // should fail due to OOG, return value can be garbage
flagAfter = h.getFlag();
myBal = this.balance;
}
}
)";
compileAndRun(sourceCode, 20);
BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(5));
// call to helper should not succeed but amount should be transferred anyway
BOOST_REQUIRE(callContractFunction("outOfGas()", 5) == encodeArgs(false, false, 20 - 5));
}
BOOST_AUTO_TEST_CASE(value_complex)
{
char const* sourceCode = R"(
contract helper {
function getBalance() returns (uint256 myBalance) {
return this.balance;
}
}
contract test {
helper h;
function test() { h = new helper(); }
function sendAmount(uint amount) returns (uint256 bal) {
var x1 = h.getBalance.value(amount);
uint someStackElement = 20;
var x2 = x1.gas(1000);
return x2.value(amount + 3)();// overwrite value
}
}
)";
compileAndRun(sourceCode, 20);
BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(8));
}
BOOST_AUTO_TEST_CASE(value_insane)
{
char const* sourceCode = R"(
contract helper {
function getBalance() returns (uint256 myBalance) {
return this.balance;
}
}
contract test {
helper h;
function test() { h = new helper(); }
function sendAmount(uint amount) returns (uint256 bal) {
var x1 = h.getBalance.value;
uint someStackElement = 20;
var x2 = x1(amount).gas;
var x3 = x2(1000).value;
return x3(amount + 3)();// overwrite value
}
}
)";
compileAndRun(sourceCode, 20);
BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(8));
}
BOOST_AUTO_TEST_CASE(value_for_constructor)
{
char const* sourceCode = R"(
contract Helper {
string3 name;
bool flag;
function Helper(string3 x, bool f) {
name = x;
flag = f;
}
function getName() returns (string3 ret) { return name; }
function getFlag() returns (bool ret) { return flag; }
}
contract Main {
Helper h;
function Main() {
h = new Helper.value(10)("abc", true);
}
function getFlag() returns (bool ret) { return h.getFlag(); }
function getName() returns (string3 ret) { return h.getName(); }
function getBalances() returns (uint me, uint them) { me = this.balance; them = h.balance;}
})";
compileAndRun(sourceCode, 22, "Main");
BOOST_REQUIRE(callContractFunction("getFlag()") == encodeArgs(true));
BOOST_REQUIRE(callContractFunction("getName()") == encodeArgs("abc"));
BOOST_REQUIRE(callContractFunction("getBalances()") == encodeArgs(12, 10));
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

Loading…
Cancel
Save