Magic variables.

This commit is contained in:
Christian 2014-11-21 19:14:56 +01:00
parent c50cd646ce
commit 583a315d3d
14 changed files with 150 additions and 74 deletions

17
AST.cpp
View File

@ -372,8 +372,6 @@ void VariableDefinition::checkTypeRequirements()
m_variable->setType(m_value->getType());
}
}
if (m_variable->getType() && !m_variable->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(m_variable->createTypeError("Type is required to live outside storage."));
}
void Assignment::checkTypeRequirements()
@ -466,8 +464,6 @@ void FunctionCall::checkTypeRequirements()
}
else
{
m_expression->requireLValue();
//@todo would be nice to create a struct type from the arguments
// and then ask if that is implicitly convertible to the struct represented by the
// function parameters
@ -495,25 +491,23 @@ bool FunctionCall::isTypeConversion() const
void MemberAccess::checkTypeRequirements()
{
m_expression->checkTypeRequirements();
m_expression->requireLValue();
Type const& type = *m_expression->getType();
m_type = type.getMemberType(*m_memberName);
if (!m_type)
BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found in " + type.toString()));
m_isLvalue = true;
m_isLvalue = (type.getCategory() == Type::Category::STRUCT && m_type->getCategory() != Type::Category::MAPPING);
}
void IndexAccess::checkTypeRequirements()
{
m_base->checkTypeRequirements();
m_base->requireLValue();
if (m_base->getType()->getCategory() != Type::Category::MAPPING)
BOOST_THROW_EXCEPTION(m_base->createTypeError("Indexed expression has to be a mapping (is " +
m_base->getType()->toString() + ")"));
MappingType const& type = dynamic_cast<MappingType const&>(*m_base->getType());
m_index->expectType(*type.getKeyType());
m_type = type.getValueType();
m_isLvalue = true;
m_isLvalue = m_type->getCategory() != Type::Category::MAPPING;
}
void Identifier::checkTypeRequirements()
@ -545,7 +539,6 @@ void Identifier::checkTypeRequirements()
// Calling a function (e.g. function(12), otherContract.function(34)) does not do a type
// conversion.
m_type = make_shared<FunctionType>(*functionDef);
m_isLvalue = true;
return;
}
ContractDefinition* contractDef = dynamic_cast<ContractDefinition*>(m_referencedDeclaration);
@ -554,6 +547,12 @@ void Identifier::checkTypeRequirements()
m_type = make_shared<TypeType>(make_shared<ContractType>(*contractDef));
return;
}
MagicVariableDeclaration* magicVariable = dynamic_cast<MagicVariableDeclaration*>(m_referencedDeclaration);
if (magicVariable)
{
m_type = magicVariable->getType();
return;
}
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type."));
}

22
AST.h
View File

@ -230,6 +230,28 @@ private:
std::shared_ptr<Type const> m_type; ///< derived type, initially empty
};
/**
* Pseudo AST node that is used as declaration for "this", "msg", "tx" and "block" when the
* identifier is encountered. Will never have a valid location in the source code.
*/
class MagicVariableDeclaration: public Declaration
{
public:
enum class VariableKind { THIS, MSG, TX, BLOCK };
MagicVariableDeclaration(VariableKind _kind, ASTString const& _name,
std::shared_ptr<Type const> const& _type):
Declaration(Location(), std::make_shared<ASTString>(_name)), m_kind(_kind), m_type(_type) {}
virtual void accept(ASTVisitor&) override { BOOST_THROW_EXCEPTION(InternalCompilerError()
<< errinfo_comment("MagicVariableDeclaration used inside real AST.")); }
std::shared_ptr<Type const> const& getType() const { return m_type; }
VariableKind getKind() const { return m_kind; }
private:
VariableKind m_kind;
std::shared_ptr<Type const> m_type;
};
/// Types
/// @{

View File

@ -40,6 +40,7 @@ class StructDefinition;
class ParameterList;
class FunctionDefinition;
class VariableDeclaration;
class MagicVariableDeclaration;
class TypeName;
class ElementaryTypeName;
class UserDefinedTypeName;

View File

@ -32,17 +32,13 @@ using namespace std;
namespace dev {
namespace solidity {
bytes Compiler::compile(ContractDefinition& _contract, bool _optimize)
{
Compiler compiler;
compiler.compileContract(_contract);
return compiler.m_context.getAssembledBytecode(_optimize);
}
void Compiler::compileContract(ContractDefinition& _contract)
void Compiler::compileContract(ContractDefinition& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals)
{
m_context = CompilerContext(); // clear it just in case
for (MagicVariableDeclaration const* variable: _magicGlobals)
m_context.addMagicGlobal(*variable);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
if (function->getName() != _contract.getName()) // don't add the constructor here
m_context.addFunction(*function);
@ -328,7 +324,8 @@ bool Compiler::visit(ExpressionStatement& _expressionStatement)
{
Expression& expression = _expressionStatement.getExpression();
ExpressionCompiler::compileExpression(m_context, expression);
if (expression.getType()->getCategory() != Type::Category::VOID)
Type::Category category = expression.getType()->getCategory();
if (category != Type::Category::VOID && category != Type::Category::MAGIC)
m_context << eth::Instruction::POP;
return false;
}

View File

@ -32,13 +32,10 @@ class Compiler: private ASTVisitor
public:
Compiler(): m_returnTag(m_context.newTag()) {}
void compileContract(ContractDefinition& _contract);
void compileContract(ContractDefinition& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals);
bytes getAssembledBytecode(bool _optimize = false) { return m_context.getAssembledBytecode(_optimize); }
void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); }
/// Compile the given contract and return the EVM bytecode.
static bytes compile(ContractDefinition& _contract, bool _optimize);
private:
/// Creates a new compiler context / assembly, packs the current code into the data part and
/// adds the constructor code.

View File

@ -30,6 +30,11 @@ using namespace std;
namespace dev {
namespace solidity {
void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration)
{
m_magicGlobals.insert(&_declaration);
}
void CompilerContext::addStateVariable(VariableDeclaration const& _declaration)
{
m_stateVariables[&_declaration] = m_stateVariablesSize;

View File

@ -40,6 +40,7 @@ class CompilerContext
public:
CompilerContext(): m_stateVariablesSize(0) {}
void addMagicGlobal(MagicVariableDeclaration const& _declaration);
void addStateVariable(VariableDeclaration const& _declaration);
void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); }
void initializeLocalVariables(unsigned _numVariables);
@ -48,6 +49,7 @@ public:
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); }
bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration); }
bool isFunctionDefinition(Declaration const* _declaration) const { return m_functionEntryLabels.count(_declaration); }
bool isLocalVariable(Declaration const* _declaration) const;
bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration); }
@ -90,6 +92,8 @@ public:
private:
eth::Assembly m_asm;
/// Magic global variables like msg, tx or this, distinguished by type.
std::set<Declaration const*> m_magicGlobals;
/// Size of the state variables, offset of next variable to be added.
u256 m_stateVariablesSize;
/// Storage offsets of state variables

View File

@ -64,7 +64,7 @@ bytes const& CompilerStack::compile(bool _optimize)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
m_bytecode.clear();
m_compiler = make_shared<Compiler>();
m_compiler->compileContract(*m_contractASTNode);
m_compiler->compileContract(*m_contractASTNode, m_globalContext->getMagicVariables());
return m_bytecode = m_compiler->getAssembledBytecode(_optimize);
}

View File

@ -162,7 +162,7 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
{
if (_functionCall.isTypeConversion())
{
//@todo we only have integers and bools for now which cannot be explicitly converted
//@todo struct construction
if (asserts(_functionCall.getArguments().size() == 1))
BOOST_THROW_EXCEPTION(InternalCompilerError());
Expression& firstArgument = *_functionCall.getArguments().front();
@ -179,6 +179,8 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
}
else
{
//@todo: check for "external call" (to be stored in type)
// Calling convention: Caller pushes return address and arguments
// Callee removes them and pushes return values
FunctionDefinition const& function = dynamic_cast<FunctionType const&>(*_functionCall.getExpression().getType()).getFunction();
@ -193,9 +195,6 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
appendTypeConversion(*arguments[i]->getType(), *function.getParameters()[i]->getType());
}
_functionCall.getExpression().accept(*this);
if (asserts(m_currentLValue.isInCode()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Code reference expected."));
m_currentLValue.reset();
m_context.appendJump();
m_context << returnLabel;
@ -213,19 +212,36 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall)
void ExpressionCompiler::endVisit(MemberAccess& _memberAccess)
{
if (asserts(m_currentLValue.isInStorage()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to a non-storage value."));
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
m_context << type.getStorageOffsetOfMember(_memberAccess.getMemberName()) << eth::Instruction::ADD;
m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess);
switch (_memberAccess.getExpression().getType()->getCategory())
{
case Type::Category::INTEGER:
if (asserts(_memberAccess.getMemberName() == "balance"))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer."));
m_context << eth::Instruction::BALANCE;
break;
case Type::Category::CONTRACT:
// call function
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Contract variables not yet implemented."));
break;
case Type::Category::MAGIC:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Magic variables not yet implemented."));
break;
case Type::Category::STRUCT:
{
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
m_context << type.getStorageOffsetOfMember(_memberAccess.getMemberName()) << eth::Instruction::ADD;
m_currentLValue = LValue(m_context, LValue::STORAGE);
m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess);
break;
}
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type."));
}
}
bool ExpressionCompiler::visit(IndexAccess& _indexAccess)
{
m_currentLValue.reset();
_indexAccess.getBaseExpression().accept(*this);
if (asserts(m_currentLValue.isInStorage()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index access to a non-storage value."));
_indexAccess.getIndexExpression().accept(*this);
appendTypeConversion(*_indexAccess.getIndexExpression().getType(),
*dynamic_cast<MappingType const&>(*_indexAccess.getBaseExpression().getType()).getKeyType(),
@ -242,8 +258,25 @@ bool ExpressionCompiler::visit(IndexAccess& _indexAccess)
void ExpressionCompiler::endVisit(Identifier& _identifier)
{
m_currentLValue.fromDeclaration(_identifier, *_identifier.getReferencedDeclaration());
m_currentLValue.retrieveValueIfLValueNotRequested(_identifier);
Declaration* declaration = _identifier.getReferencedDeclaration();
if (MagicVariableDeclaration* magicVar = dynamic_cast<MagicVariableDeclaration*>(declaration))
{
if (magicVar->getKind() == MagicVariableDeclaration::VariableKind::THIS)
m_context << eth::Instruction::ADDRESS;
return;
}
if (FunctionDefinition* functionDef = dynamic_cast<FunctionDefinition*>(declaration))
{
m_context << m_context.getFunctionEntryLabel(*functionDef).pushTag();
return;
}
if (VariableDeclaration* varDef = dynamic_cast<VariableDeclaration*>(declaration))
{
m_currentLValue.fromIdentifier(_identifier, *_identifier.getReferencedDeclaration());
m_currentLValue.retrieveValueIfLValueNotRequested(_identifier);
return;
}
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context."));
}
void ExpressionCompiler::endVisit(Literal& _literal)
@ -410,9 +443,6 @@ void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bo
{
switch (m_type)
{
case CODE:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Tried to retrieve value of a function."));
break;
case STACK:
{
unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset));
@ -423,11 +453,15 @@ void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bo
break;
}
case STORAGE:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
if (!_remove)
*m_context << eth::Instruction::DUP1;
*m_context << eth::Instruction::SLOAD;
break;
case MEMORY:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Location type not yet implemented."));
break;
@ -455,15 +489,15 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool
break;
}
case LValue::STORAGE:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
if (!_move)
*m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
*m_context << eth::Instruction::SSTORE;
break;
case LValue::CODE:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Location type does not support assignment."));
break;
case LValue::MEMORY:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Location type not yet implemented."));
break;
@ -483,7 +517,7 @@ void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression co
}
}
void ExpressionCompiler::LValue::fromDeclaration(Expression const& _expression, Declaration const& _declaration)
void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration)
{
if (m_context->isLocalVariable(&_declaration))
{
@ -495,13 +529,8 @@ void ExpressionCompiler::LValue::fromDeclaration(Expression const& _expression,
m_type = STORAGE;
*m_context << m_context->getStorageLocationOfVariable(_declaration);
}
else if (m_context->isFunctionDefinition(&_declaration))
{
m_type = CODE;
*m_context << m_context->getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(_declaration)).pushTag();
}
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation())
<< errinfo_comment("Identifier type not supported or identifier not found."));
}

View File

@ -83,31 +83,30 @@ private:
/**
* Helper class to store and retrieve lvalues to and from various locations.
* All types except STACK store a reference in a slot on the stack, STACK just stores the
* base stack offset of the variable in @a m_baseStackOffset.
* All types except STACK store a reference in a slot on the stack, STACK just
* stores the base stack offset of the variable in @a m_baseStackOffset.
*/
class LValue
{
public:
enum LValueType { NONE, CODE, STACK, MEMORY, STORAGE };
enum LValueType { NONE, STACK, MEMORY, STORAGE };
explicit LValue(CompilerContext& _compilerContext): m_context(&_compilerContext) { reset(); }
LValue(CompilerContext& _compilerContext, LValueType _type, unsigned _baseStackOffset = 0):
m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset) {}
/// Set type according to the declaration and retrieve the reference.
/// @a _expression is the current expression, used for error reporting.
void fromDeclaration(Expression const& _expression, Declaration const& _declaration);
/// @a _expression is the current expression
void fromIdentifier(Identifier const& _identifier, Declaration const& _declaration);
void reset() { m_type = NONE; m_baseStackOffset = 0; }
bool isValid() const { return m_type != NONE; }
bool isInCode() const { return m_type == CODE; }
bool isInOnStack() const { return m_type == STACK; }
bool isInMemory() const { return m_type == MEMORY; }
bool isInStorage() const { return m_type == STORAGE; }
/// @returns true if this lvalue reference type occupies a slot on the stack.
bool storesReferenceOnStack() const { return m_type == STORAGE || m_type == MEMORY || m_type == CODE; }
bool storesReferenceOnStack() const { return m_type == STORAGE || m_type == MEMORY; }
/// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true,
/// also removes the reference from the stack (note that is does not reset the type to @a NONE).

View File

@ -45,25 +45,37 @@ GlobalContext::GlobalContext()
void GlobalContext::setCurrentContract(ContractDefinition const& _contract)
{
m_this = createVariable("this", make_shared<ContractType>(_contract));
m_currentContract = &_contract;
}
vector<Declaration*> GlobalContext::getDeclarations() const
{
vector<Declaration*> declarations;
declarations.reserve(m_objects.size() + 1);
for (ASTPointer<Declaration> const& declaration: m_objects)
declarations.push_back(declaration.get());
declarations.push_back(m_this.get());
declarations.reserve(m_magicVariables.size() + 1);
for (ASTPointer<Declaration> const& variable: m_magicVariables)
declarations.push_back(variable.get());
declarations.push_back(getCurrentThis());
return declarations;
}
ASTPointer<VariableDeclaration> GlobalContext::createVariable(const string& _name, shared_ptr<const Type> const& _type)
MagicVariableDeclaration*GlobalContext::getCurrentThis() const
{
ASTPointer<VariableDeclaration> variable = make_shared<VariableDeclaration>(Location(), ASTPointer<TypeName>(),
make_shared<ASTString>(_name));
variable->setType(_type);
return variable;
if (!m_thisPointer[m_currentContract])
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>(
MagicVariableDeclaration::VariableKind::THIS,
"this", make_shared<ContractType>(*m_currentContract));
return m_thisPointer[m_currentContract].get();
}
vector<MagicVariableDeclaration const*> GlobalContext::getMagicVariables() const
{
vector<MagicVariableDeclaration const*> declarations;
declarations.reserve(m_magicVariables.size() + 1);
for (ASTPointer<MagicVariableDeclaration const> const& variable: m_magicVariables)
declarations.push_back(variable.get());
declarations.push_back(getCurrentThis());
return declarations;
}
}

View File

@ -24,6 +24,7 @@
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <boost/noncopyable.hpp>
#include <libsolidity/ASTForward.h>
@ -47,14 +48,14 @@ public:
GlobalContext();
void setCurrentContract(ContractDefinition const& _contract);
std::vector<MagicVariableDeclaration const*> getMagicVariables() const;
std::vector<Declaration*> getDeclarations() const;
private:
/// Creates a virtual variable declaration with the given name and type.
static ASTPointer<VariableDeclaration> createVariable(std::string const& _name, std::shared_ptr<Type const> const& _type);
std::vector<ASTPointer<Declaration>> m_objects;
ASTPointer<Declaration> m_this;
MagicVariableDeclaration* getCurrentThis() const;
std::vector<std::shared_ptr<MagicVariableDeclaration>> m_magicVariables;
ContractDefinition const* m_currentContract;
std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration>> mutable m_thisPointer;
};
}

View File

@ -189,6 +189,8 @@ u256 IntegerType::literalValue(Literal const& _literal) const
return u256(value);
}
const MemberList IntegerType::AddressMemberList = MemberList({{"balance", std::make_shared<IntegerType const>(256)}});
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
// conversion to integer is fine, but not to address

10
Types.h
View File

@ -73,7 +73,7 @@ class Type: private boost::noncopyable
public:
enum class Category
{
INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE
INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE, MAGIC
};
///@{
@ -110,6 +110,9 @@ public:
virtual bool canBeStored() const { return true; }
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
virtual bool canLiveOutsideStorage() const { return true; }
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
/// Returns the list of all members of this type. Default implementation: no members.
virtual MemberList const& getMembers() const { return EmptyMemberList; }
@ -154,6 +157,9 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual unsigned getCalldataEncodedSize() const override { return m_bits / 8; }
virtual bool isValueType() const override { return true; }
virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; }
virtual std::string toString() const override;
virtual u256 literalValue(Literal const& _literal) const override;
@ -166,6 +172,7 @@ public:
private:
int m_bits;
Modifier m_modifier;
static const MemberList AddressMemberList;
};
/**
@ -186,6 +193,7 @@ public:
}
virtual unsigned getCalldataEncodedSize() const { return 1; }
virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "bool"; }
virtual u256 literalValue(Literal const& _literal) const override;