Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3333,9 +3333,44 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
{
case Type::Category::Struct:
{
auto const* owningObjectStructType = dynamic_cast<StructType const*>(owningObjectType);
solAssert(owningObjectStructType);
_memberAccess.annotation().isLValue = !owningObjectStructType->dataStoredIn(DataLocation::CallData);
_memberAccess.annotation().isLValue = !static_cast<StructType const*>(owningObjectType)->dataStoredIn(DataLocation::CallData);

if (
auto const* accessedVariableDeclaration =
dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)
)
{
_memberAccess.annotation().isPure =
*_memberAccess.expression().annotation().isPure ||
accessedVariableDeclaration->isConstant(); // It is always false. See below.

solUnimplementedAssert(
!accessedVariableDeclaration->isConstant(),
"Constant struct members are not yet implemented."
);
}
else if (dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
{
solAssert(_memberAccess.annotation().type->category() == Type::Category::Function);
auto const* accessedMemberFunctionType = static_cast<FunctionType const*>(_memberAccess.annotation().type);
// It is not possible to define a function inside a struct definition, but a library function can be
// attached to a struct with the `using` keyword. In this case the function invoke kind can be only
// `internal` or `delegate`.
solAssert(
accessedMemberFunctionType->kind() == FunctionType::Kind::Internal ||
accessedMemberFunctionType->kind() == FunctionType::Kind::DelegateCall,
"Impossible function call kind for struct type member."
);

// When struct is constant its members are also constant.
_memberAccess.annotation().isPure = *_memberAccess.expression().annotation().isPure;
}
else
solAssert(
false,
"Struct must have all members defined and they must be variables declarations or functions definitions"
);

break;
}
case Type::Category::Function:
Expand Down Expand Up @@ -4052,12 +4087,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
FunctionDefinition const& functionDefinition =
dynamic_cast<FunctionDefinition const&>(*path->annotation().referencedDeclaration);

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

FunctionType const* functionType = dynamic_cast<FunctionType const*>(functionDefinition.typeWhenAttached());
solAssert(functionType);

if (functionDefinition.parameters().empty())
Expand Down
62 changes: 44 additions & 18 deletions libsolidity/ast/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,17 +491,51 @@ 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(), "Library members can only be accessed via library name.");
solAssert(isVisibleViaContractInstance(), "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 /* _inLibrary */);
else
// For private or internal visibility, internal call is used.
return type();
}
}
else
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
}

Type const* FunctionDefinition::typeWhenAttached() const
{
solAssert(isFree() || libraryFunction());
return libraryFunction() ? typeViaContractName(ContractNameAccessKind::Library) : type();
}

std::string FunctionDefinition::externalSignature() const
Expand Down Expand Up @@ -950,11 +984,7 @@ FunctionType const* UnaryOperation::userDefinedFunctionType() const
return nullptr;

FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction;
return dynamic_cast<FunctionType const*>(
userDefinedFunction->libraryFunction() ?
userDefinedFunction->typeViaContractName() :
userDefinedFunction->type()
);
return dynamic_cast<FunctionType const*>(userDefinedFunction->typeWhenAttached());
}

FunctionType const* BinaryOperation::userDefinedFunctionType() const
Expand All @@ -963,11 +993,7 @@ FunctionType const* BinaryOperation::userDefinedFunctionType() const
return nullptr;

FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction;
return dynamic_cast<FunctionType const*>(
userDefinedFunction->libraryFunction() ?
userDefinedFunction->typeViaContractName() :
userDefinedFunction->type()
);
return dynamic_cast<FunctionType const*>(userDefinedFunction->typeWhenAttached());
}

BinaryOperationAnnotation& BinaryOperation::annotation() const
Expand Down
17 changes: 13 additions & 4 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,17 @@ 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.
enum class ContractNameAccessKind {
Local, ///< Via contract name from local contract scope or deriving contract.
Foreign, ///< Via contract name from foreign (unrelated) contract.
Library, ///< Via library name.
};

/// @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 @@ -1040,7 +1048,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
bool isVisibleViaContractInstance() const override
{
solAssert(!isFree(), "");
return isOrdinary() && visibility() >= Visibility::Public;
return isPartOfExternalInterface();
}
bool isPartOfExternalInterface() const override { return isOrdinary() && isPublic(); }

Expand All @@ -1053,7 +1061,8 @@ 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;
Type const* typeWhenAttached() const;

/// @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
45 changes: 27 additions & 18 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,7 @@ std::set<FunctionDefinition const*, ASTNode::CompareByID> Type::operatorDefiniti
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(
*identifierPath->annotation().referencedDeclaration
);
auto const* functionType = dynamic_cast<FunctionType const*>(
functionDefinition.libraryFunction() ? functionDefinition.typeViaContractName() : functionDefinition.type()
);
auto const* functionType = dynamic_cast<FunctionType const*>(functionDefinition.typeWhenAttached());
solAssert(functionType && !functionType->parameterTypes().empty());

size_t parameterCount = functionDefinition.parameterList().parameters().size();
Expand All @@ -425,8 +423,7 @@ MemberList::MemberMap Type::attachedFunctions(Type const& _type, ASTNode const&
{
if (!_name)
_name = _function.name();
Type const* functionType =
_function.libraryFunction() ? _function.typeViaContractName() : _function.type();
Type const* functionType = _function.typeWhenAttached();
solAssert(functionType, "");
FunctionType const* withBoundFirstArgument =
dynamic_cast<FunctionType const&>(*functionType).withBoundFirstArgument();
Expand Down Expand Up @@ -3978,21 +3975,33 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
if (declaration->name().empty())
continue;

if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
if (contract.isLibrary() && declaration->isVisibleAsLibraryMember())
{
if (
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(declaration);
functionDefinition && !functionDefinition->isImplemented()
)
members.emplace_back(declaration, declaration->typeViaContractName());
else
members.emplace_back(declaration, declaration->type());
// In case the contract is library, add only visible library members. visibility >= Internal.
members.emplace_back(
declaration,
declaration->typeViaContractName(Declaration::ContractNameAccessKind::Library)
);
}
else if (!contract.isLibrary() && inDerivingScope && declaration->visibility() > Visibility::Private)
{
// In case of regular contract (not library) and member is in the same deriving scope, add all
// members which are not private. Private members cannot be accessed via contract type name
// i.e C.fooPrivate.
members.emplace_back(
declaration,
declaration->typeViaContractName(Declaration::ContractNameAccessKind::Local)
);
}
else if (!contract.isLibrary() && !inDerivingScope && declaration->isVisibleViaContractInstance())
{
// In case of regular contract (not library) being accessed from foreign contract (not in deriving
// scope), add only externally visible members.
members.emplace_back(
declaration,
declaration->typeViaContractName(Declaration::ContractNameAccessKind::Foreign)
);
}
else if (
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
declaration->isVisibleViaContractInstance()
)
members.emplace_back(declaration, declaration->typeViaContractName());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
struct S {
uint256 v;
}

library L {
function double(S memory s) pure public returns(uint256) {
return 2 * s.v;
}
}

using L for S;

contract C
{
S private s;
function test() pure private {
s.double;
}
}
// ----
// TypeError 2527: (226-227): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
// TypeError 2527: (226-234): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
struct S {
uint256 v;
}

library L {
function doubleInternal(S memory s) pure internal returns(uint256) {
return 2 * s.v;
}

function doubleExternal(S memory s) pure external returns(uint256) {
return 2 * s.v;
}

function doublePublic(S memory s) pure public returns(uint256) {
return 2 * s.v;
}
}

using L for S;

contract C
{
uint256 constant s = S(1).v;

function test() pure private {
S(1);
S(1).v;
S(1).doubleInternal;
S(1).doubleExternal;
S(1).doublePublic;
}
}
// ----
// Warning 6133: (457-461): Statement has no effect.
// Warning 6133: (471-477): Statement has no effect.
// Warning 6133: (487-506): Statement has no effect.
// Warning 6133: (516-535): Statement has no effect.
// Warning 6133: (545-562): Statement has no effect.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
struct S {
uint256 v;
}

contract C
{
S private s;
uint256 v = s.v;
uint256 constant vConst = s.v;
}
// ----
// TypeError 8349: (110-113): Initial value for constant variable has to be compile-time constant.