Skip to content

Conversation

@Marenz
Copy link
Contributor

@Marenz Marenz commented Dec 12, 2019

fixes #7314

I need to use the resolveDirectBaseContracts function from the override checker. I am currently not sure what the best way to do that is. I could pull it out into a different or extra file.. I could move my check into the override checker.. or something else?

@Marenz
Copy link
Contributor Author

Marenz commented Dec 12, 2019

I see this is in the 0.6.0 project.. but it doesn't need to be, nothing here is breaking. So if this is blocking feel free to move it to 0.6.1

{
if (
!funcDef->isImplemented() &&
contains(resolveDirectBaseContracts(*m_scope), funcDef->annotation().contract) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it important whether it is a direct base or an indirect base?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's a good point. I had a wrong assumption here.

}
// ----
// TypeError: (110-117): Member "f" not found or not visible after argument-dependent lookup in contract super b.
// TypeError: (110-119): Call to unimplemented function "f".
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this should be an error in general. At this point, we cannot tell whether there is a super function or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Really? I mean, the check is very specifically checking for this situation? Can you think of a scenario where this message is wrong?

Copy link
Contributor

Choose a reason for hiding this comment

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

super performs virtual function lookup. This means the lookup is performed differently depending on the final derived contract that is being compiled. In the following example, super.f() should compile for D.

contract A { function f() public pure virtual; }
contract B is A { function f() public pure override virtual { super.f(); } }
contract C is A { function f() public pure override virtual { } }
contract D is B, C { function f() public pure virtual override(B, C) { super.f(); } }

I think we need to discuss what to do if B is to be compiled on its own. I think @ekpyron is of the opinion to remove super altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what does the super in contract B refer to then? (that's where it would throw an error with this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose I can either exclude super from the check, or I set a tag in the annotation and only trigger errors when the final contract was processed... though that could get messy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure about your example? I think this doesn't even work with the current develop version

Copy link
Contributor

Choose a reason for hiding this comment

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

Please only fix the non-super case. I said that super.f() should compile. It is a bug that it currently does not compile, but it is a different bug that #7314

}
// ----
// TypeError: (118-125): Member "f" not found or not visible after argument-dependent lookup in contract super b.
// TypeError: (118-127): Call to unimplemented function "f".
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

@chriseth
Copy link
Contributor

Please do not change the behaviour of super, I think the two should be considered independent of each other.

@Marenz
Copy link
Contributor Author

Marenz commented Dec 12, 2019

I was doing that in response to

The way we solved it with super in #5130 is sub-optimal because it just removes non-implemented functions from the member list. Instead, a specific error should be thrown.

@Marenz
Copy link
Contributor Author

Marenz commented Dec 12, 2019

updated to exclude super

{
if (
!funcDef->isImplemented() &&
contains(m_scope->annotation().linearizedBaseContracts, funcDef->annotation().contract) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is how we should do it. It does not catch things like

function() internal pure x = a.t;
x();

@Marenz
Copy link
Contributor Author

Marenz commented Dec 16, 2019

Updated. The check happens now as soon as we access the member.
I am not too happy with the deep if nesting. I could make it more linear by putting it in an extra function. Let me know if you prefer that.

@Marenz Marenz requested a review from chriseth December 16, 2019 16:48
Copy link
Contributor

@bshastry bshastry left a comment

Choose a reason for hiding this comment

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

Some minor comments.

Changelog.md Outdated


Bugfixes:
* Fix compiler error when calling the base function from the derived
Copy link
Contributor

Choose a reason for hiding this comment

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

White space instead of tab?

struct SetScopeFunc
{
SetScopeFunc(TypeChecker& _checker, FunctionDefinition const* _func)
:m_checker( _checker)
Copy link
Contributor

@bshastry bshastry Dec 16, 2019

Choose a reason for hiding this comment

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

Suggested change
:m_checker( _checker)
: m_checker(_checker)

function f() public override { a.f(); }
}
// ----
// TypeError: (110-113): Referencing unimplemented function "f".
Copy link
Contributor

Choose a reason for hiding this comment

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

Referencing unimplemented function "a.f" would be a nicer error message though.

@bshastry
Copy link
Contributor

This (valid?) test case throws a different ICE? Perhaps unrelated but documenting it here in any case.

abstract contract a {
    function f() virtual public;
}
contract b is a {
    function f() public override {}
    function g() public { a.f(); }
}

throws this

https://github.com/ethereum/solidity/blob/7711631153a43b1290a2b98db73fb972b295dfd7/libsolidity/codegen/ContractCompiler.cpp#L1253

if (
!funcDef->isImplemented() &&
contains(m_scope->annotation().linearizedBaseContracts, funcDef->annotation().contract) &&
m_scopeFunction->name() == funcDef->name() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the assumption that the names are the same valid? Please see the test input that I added

Copy link
Contributor

@bshastry bshastry left a comment

Choose a reason for hiding this comment

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

One more related comment.

)
m_errorReporter.typeError(
_memberAccess.location(),
"Referencing unimplemented function \"" + funcDef->name() + "\"."
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"Referencing unimplemented function \"" + funcDef->name() + "\"."
"Referencing unimplemented function \"" + funcDef->name() + "\" in contract \"" + tt->actualType()->canonicalName() + "\"."

If you accept this change, test expectations need to be updated.

@Marenz
Copy link
Contributor Author

Marenz commented Dec 16, 2019

Good finds! Not sure about that extra test case you found. We certainly shouldn't get an ICE, but I want to hear what @chriseth has to say before changing my code

{
if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true;
else if (tt->actualType()->category() == Type::Category::Contract)
Copy link
Contributor

Choose a reason for hiding this comment

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

This again does not catch function() x = A.f. I think that information has to be added to the type. We should discuss if we want that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, sorry, that's wrong - there is a test about exactly that :)


ContractDefinition const* m_scope = nullptr;

/// Pointer to the function we're currently in
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it important that we are currently inside that function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this stems from a misunderstanding from my side. The issue description says

This is a rather specific case - you have to call the non-implemented base function and override the function at the same time.

I thought this means it only happens when you call it from within the function that overrides which is what I am detecting here.

@bshastry newly found test case shows that the problem is more general than this.

@chriseth
Copy link
Contributor

I think we have to discuss that at a more basic level. Do we want A.f.signature to work? Do we want A.f itself to generate an error? Or do we want A.f to result in a function type that is not callable?

@Marenz Marenz self-assigned this Jan 2, 2020
@ekpyron
Copy link
Collaborator

ekpyron commented Jan 6, 2020

I would say A.f.signature should work and thereby merely accessing A.f alone is fine, but A.f is only callable or convertible to an actual function pointer, if A.f is implemented, so both A.f(); or some_function_pointer = A.f; are errors.

@ekpyron
Copy link
Collaborator

ekpyron commented Jan 6, 2020

Part of my reasoning would be that A.f.signature should even work in general, i.e. in a contract that does not even inherit from A. So stuff like this should work:

interface I {
  function f() external;
}
contract C {
  function g() public returns (bytes4) { return I.f.selector; }
}

And for that we'd need to have a specific type for I.f anyways... which is a special kind of function type that is not callable and not convertible to function pointers... and that's the very same type I'd use for unimplemented base functions as well.

@ekpyron
Copy link
Collaborator

ekpyron commented Jan 6, 2020

Ah, yes: for reference: access to selectors via contract names in general is tracked as #3506 and staged for 0.6.2 as well.

@ekpyron
Copy link
Collaborator

ekpyron commented Jan 9, 2020

@Marenz We now merged #8105 , which introduces a new kind of FunctionType::Kind::Declaration which can be used for a nicer solution to this - there will still be some subtleties involved, though - if you reference unimplemented public or external base functions by the name of the base, they will just get a function type of kind FunctionType::Kind::Declaration. Probably internal ones as well, but they won't get the selector native member. For implemented public base functions we have to check, if it's nicer to keep them as internal function type kinds, or whether we let the declaration kind be callable in this case (I suspect the former will be less work).

So: do you want to continue with this based on that, or should I take over this PR as well?

@ekpyron ekpyron self-assigned this Jan 10, 2020
@ekpyron
Copy link
Collaborator

ekpyron commented Jan 10, 2020

As discussed with @Marenz in chat: I'm taking this over now.

@ekpyron
Copy link
Collaborator

ekpyron commented Jan 15, 2020

Closing this in favour of #7987

@ekpyron ekpyron closed this Jan 15, 2020
@axic axic deleted the fix-bug-7314 branch April 5, 2020 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Calling non-implemented base contracts when overriding causes internal error

5 participants