Skip to content
Open
165 changes: 147 additions & 18 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3239,33 +3239,160 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)

annotation.requiredLookup = requiredLookup;

if (auto const* structType = dynamic_cast<StructType const*>(exprType))
// Sanity check. Variable declaration can only be a member of contract type, contract, module or struct.
if (dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
{
if (exprType->category() == Type::Category::TypeType)
{
solAssert(
reinterpret_cast<TypeType const*>(exprType)->actualType()->category() == Type::Category::Contract,
"Variable member is available only for module, contract or struct"
);
} else
{
solAssert(
exprType->category() == Type::Category::Module ||
exprType->category() == Type::Category::Struct ||
exprType->category() == Type::Category::Contract,
"Variable member is only available for module, contract or struct");
}
}

if (dynamic_cast<ContractType const*>(exprType))
{
// When contract is constant its members are also constant.
if (dynamic_cast<FunctionDefinition const*>(annotation.referencedDeclaration))
{
annotation.isPure = *_memberAccess.expression().annotation().isPure;
solAssert(
annotation.type->category() == FunctionType::Category::Function,
"Impossible declaration type for function definition access"
);
auto const* funcType = reinterpret_cast<FunctionType const*>(annotation.type);
// In case when internal library function is attached to contract, function call kind is internal.
solAssert(
funcType->kind() == FunctionType::Kind::Internal ||
funcType->kind() == FunctionType::Kind::External,
"Impossible function call kind for contract type member."
);
}
else if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = *_memberAccess.expression().annotation().isPure && varDecl->isConstant();
else
solAssert(false,"Impossible declaration type for contact member.");

annotation.isLValue = false;
}
else if (auto const* structType = dynamic_cast<StructType const*>(exprType))
{
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);

if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
{
annotation.isPure = varDecl->isConstant();
solAssert(!varDecl->isConstant(), "Struct member variables cannot be declared as constant.");
}
}
else if (exprType->category() == Type::Category::Array)
annotation.isLValue = false;
else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
{
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
if (typeType->actualType()->category() == Type::Category::Contract)
{
// ContractType has only user defined members, so annotation.referencedDeclaration is not `NULL`.
// See `ContractType::nativeMembers` for details.
solAssert(annotation.referencedDeclaration);
annotation.isLValue = annotation.referencedDeclaration->isLValue();
if (
auto const* functionType = dynamic_cast<FunctionType const*>(annotation.type);
functionType &&
functionType->kind() == FunctionType::Kind::Declaration
)
annotation.isPure = *_memberAccess.expression().annotation().isPure;
// Expressions like `C.foo;`, `C.Ev;` are pure and they must generate `Statement has no effect.` warning.
// TODO: However, in case a function this does not allow to assign the expression to a constant variable,
// TODO: because of different kind. Left-hand side of the variable declaration never has `Declaration` kind.
if (auto const* functionTypeMember = dynamic_cast<FunctionType const*>(annotation.type))
{
if (
functionTypeMember->isPure() ||
functionTypeMember->kind() == FunctionType::Kind::Event
)
annotation.isPure = true;
else if (functionTypeMember->kind() == FunctionType::Kind::Internal)
{
if (auto const* vDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = vDecl->isConstant();
else if (dynamic_cast<FunctionDefinition const*>(annotation.referencedDeclaration))
annotation.isPure = true;

solAssert(
dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration) ||
dynamic_cast<FunctionDefinition const*>(annotation.referencedDeclaration),
"Impossible declaration type for internal function call kind"
);
}
}
else if (auto const* typeTypeMember = dynamic_cast<TypeType const*>(annotation.type))
{
solAssert(
typeTypeMember->actualType()->category() == Type::Category::Struct ||
typeTypeMember->actualType()->category() == Type::Category::Enum ||
// Note: We add Contract intentionally, to cover possible contract nesting case.
typeTypeMember->actualType()->category() == Type::Category::Contract ||
typeTypeMember->actualType()->category() == Type::Category::UserDefinedValueType,
"Impossible `TypeType` category as contract member."
);
annotation.isPure = true;
}
// In case `Base.value` or `Lib.value` and when `value` is constant, the expression is pure.
else if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = varDecl->isConstant();
}
else
annotation.isLValue = false;
}
else if (exprType->category() == Type::Category::Module)
{
annotation.isPure = *_memberAccess.expression().annotation().isPure;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're only handling FunctionType, TypeType and things associated with a variable declaration. I think originally this covered more cases:

  • Category::Module (i.e. module accessed through another module, M1.M2). Original code would make it pure because the member expression is an Identifier and those are pure when they refer to modules:
    else if (dynamic_cast<ModuleType const*>(annotation.type))
    annotation.isPure = true;

    After your changes I think we'll reach the !annotation.isPure.set() at the end of the function instead and set it to false.
  • MagicVariableDeclaration. I think this one cannot occur on a module, but I'd assert against that.
  • There are probably no other possibilities (AFAIR modules can contain functions, constants, events, errors and type definitions) but I'd assert against that too.

if (auto const* functionTypeMember = dynamic_cast<FunctionType const*>(annotation.type))
{
if (
functionTypeMember->isPure() ||
functionTypeMember->kind() == FunctionType::Kind::Event
)
annotation.isPure = true;
else if (functionTypeMember->kind() == FunctionType::Kind::Internal)
{
if (auto const* vDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = vDecl->isConstant();
else if (dynamic_cast<FunctionDefinition const*>(annotation.referencedDeclaration))
annotation.isPure = true;

solAssert(
dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration) ||
dynamic_cast<FunctionDefinition const*>(annotation.referencedDeclaration),
"Impossible declaration type for internal function call kind"
);
}
}
else if (auto const* typeTypeMember = dynamic_cast<TypeType const*>(annotation.type))
{
solAssert(
typeTypeMember->actualType()->category() == Type::Category::Struct ||
typeTypeMember->actualType()->category() == Type::Category::Enum ||
typeTypeMember->actualType()->category() == Type::Category::Contract ||
typeTypeMember->actualType()->category() == Type::Category::UserDefinedValueType,
"Impossible `TypeType` category as module member."
);
annotation.isPure = true;
}
else if (annotation.type->category() == ModuleType::Category::Module)
annotation.isPure = true;
else if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = varDecl->isConstant();
else
solAssert(false, "Impossible module member type.");

annotation.isLValue = false;
}
else
// TODO: Is it ok? I.e. it used to set false for ContractType member
annotation.isLValue = false;

// TODO some members might be pure, but for example `address(0x123).balance` is not pure
Expand Down Expand Up @@ -3322,14 +3449,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.isPure = true;
}

if (
auto const* varDecl = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration);
!annotation.isPure.set() &&
varDecl &&
varDecl->isConstant()
)
annotation.isPure = true;

if (auto magicType = dynamic_cast<MagicType const*>(exprType))
{
if (magicType->kind() == MagicType::Kind::ABI)
Expand Down Expand Up @@ -3409,6 +3528,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (!annotation.isPure.set())
annotation.isPure = false;

// Sanity check. For function type pure flag should be equal to
if (
auto const* funcType = dynamic_cast<FunctionType const*>(annotation.type);
funcType &&
funcType->kind() != FunctionType::Kind::External &&
funcType->kind() != FunctionType::Kind::Internal &&
funcType->kind() != FunctionType::Kind::Event
)
solAssert(funcType->isPure() == *annotation.isPure);

return false;
}

Expand Down Expand Up @@ -3901,7 +4030,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)

FunctionType const* functionType = dynamic_cast<FunctionType const*>(
functionDefinition.libraryFunction() ?
functionDefinition.typeViaContractName() :
functionDefinition.typeViaContractName(Declaration::ContractNameAccessKind::Library) :
functionDefinition.type()
);

Expand Down
50 changes: 40 additions & 10 deletions libsolidity/ast/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,17 +491,47 @@ Type const* FunctionDefinition::type() const
return TypeProvider::function(*this, FunctionType::Kind::Internal);
}

Type const* FunctionDefinition::typeViaContractName() const
Type const* FunctionDefinition::typeViaContractName(ContractNameAccessKind _accessKind) const
{
if (libraryFunction())
switch (_accessKind)
{
if (isPublic())
return FunctionType(*this).asExternallyCallableFunction(true);
else
return TypeProvider::function(*this, FunctionType::Kind::Internal);
case ContractNameAccessKind::Local:
{
solAssert(!libraryFunction(), "Library member cannot be accessed from local/deriving scope");
solAssert(visibility() > Visibility::Private, "Private non-library member is not visible via contract type name");

if (!Declaration::isVisibleInContract() || !isImplemented())
// If is external or has no implementation, it cannot be called using contract type name. In case of accessing
// via contract type name, only declaration is available, to be used in non calling context. I.e. to access
// function selector `C.foo.selector` where foo has external visibility.
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
else
// If call is in local (or deriving) scope, function is visible in contract (non-external) and it has an
// implementation, internal call is used.
return type();
}
case ContractNameAccessKind::Foreign:
{
solAssert(!libraryFunction(), "Non-library members cannot be accessed via library name.");
solAssert(isVisibleViaContractInstantce(), "Externally invisible member accessed via contract name.");
// Foreign contract member function being accessed via contract type name, cannot be called.
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
}
case ContractNameAccessKind::Library:
{
// Private library members can be accessed in context of `using` statement.
solAssert(libraryFunction(), "Non-library members cannot be accessed via library name.");
// In case of library contract, member call kind depends on its visibility.
if (isPublic())
// When Lib.foo is public or external, an external call (delegate call) is used.
return FunctionType(*this).asExternallyCallableFunction(true);
else
// For private or internal visibility, internal call is used.
return type();
}
default:
solAssert(false, "Unimplemented contract member access kind.");
}
else
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
}

std::string FunctionDefinition::externalSignature() const
Expand Down Expand Up @@ -952,7 +982,7 @@ FunctionType const* UnaryOperation::userDefinedFunctionType() const
FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction;
return dynamic_cast<FunctionType const*>(
userDefinedFunction->libraryFunction() ?
userDefinedFunction->typeViaContractName() :
userDefinedFunction->typeViaContractName(Declaration::ContractNameAccessKind::Library) :
userDefinedFunction->type()
);
}
Expand All @@ -965,7 +995,7 @@ FunctionType const* BinaryOperation::userDefinedFunctionType() const
FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction;
return dynamic_cast<FunctionType const*>(
userDefinedFunction->libraryFunction() ?
userDefinedFunction->typeViaContractName() :
userDefinedFunction->typeViaContractName(Declaration::ContractNameAccessKind::Library) :
userDefinedFunction->type()
);
}
Expand Down
27 changes: 17 additions & 10 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ class Declaration: public ASTNode, public Scopable
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
virtual bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
virtual bool isVisibleViaContractTypeAccess() const { return false; }
virtual bool isVisibleViaContractInstantce() const { return false; }

virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
Expand All @@ -297,9 +297,16 @@ class Declaration: public ASTNode, public Scopable
/// This can only be called once types of variable declarations have already been resolved.
virtual Type const* type() const = 0;

/// @returns the type for members of the containing contract type that refer to this declaration.
/// Defines type of member access via contract type name.
/// @Local means access via contract name from local contract scope or deriving contract.
/// @Foreign means access via contract name from foreign (unrelated) contract.
/// @Library means access via library name.
enum class ContractNameAccessKind { Local, Foreign, Library };

/// @returns the type for members of the containing contract type that refer to this declaration. Depends on access
/// context defined by `ContractNameAccessKind`.
/// This can only be called once types of variable declarations have already been resolved.
virtual Type const* typeViaContractName() const { return type(); }
virtual Type const* typeViaContractName(ContractNameAccessKind) const { return type(); }

/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function.
Expand Down Expand Up @@ -780,7 +787,7 @@ class StructDefinition: public Declaration, public StructurallyDocumented, publi
Type const* type() const override;

bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
bool isVisibleViaContractInstantce() const override { return true; }

StructDeclarationAnnotation& annotation() const override;

Expand Down Expand Up @@ -808,7 +815,7 @@ class EnumDefinition: public Declaration, public StructurallyDocumented, public
void accept(ASTConstVisitor& _visitor) const override;

bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
bool isVisibleViaContractInstantce() const override { return true; }

std::vector<ASTPointer<EnumValue>> const& members() const { return m_members; }

Expand Down Expand Up @@ -870,7 +877,7 @@ class UserDefinedValueTypeDefinition: public Declaration
TypeDeclarationAnnotation& annotation() const override;

TypeName const* underlyingType() const { return m_underlyingType.get(); }
bool isVisibleViaContractTypeAccess() const override { return true; }
bool isVisibleViaContractInstantce() const override { return true; }

private:
/// The name of the underlying type
Expand Down Expand Up @@ -1037,7 +1044,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
{
return isOrdinary() && Declaration::isVisibleInContract();
}
bool isVisibleViaContractTypeAccess() const override
bool isVisibleViaContractInstantce() const override
{
solAssert(!isFree(), "");
return isOrdinary() && visibility() >= Visibility::Public;
Expand All @@ -1053,7 +1060,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
std::string externalIdentifierHex() const;

Type const* type() const override;
Type const* typeViaContractName() const override;
Type const* typeViaContractName(ContractNameAccessKind _accessKind) const override;

/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function.
Expand Down Expand Up @@ -1317,7 +1324,7 @@ class EventDefinition: public CallableDeclaration, public StructurallyDocumented
FunctionTypePointer functionType(bool /*_internal*/) const override;

bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
bool isVisibleViaContractInstantce() const override { return true; }

EventDefinitionAnnotation& annotation() const override;

Expand Down Expand Up @@ -1361,7 +1368,7 @@ class ErrorDefinition: public CallableDeclaration, public StructurallyDocumented
FunctionTypePointer functionType(bool _internal) const override;

bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
bool isVisibleViaContractInstantce() const override { return true; }

ErrorDefinitionAnnotation& annotation() const override;

Expand Down
Loading