Skip to content

Warn if a non dynamic class declaration is overridden in an extension #6346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 17, 2017

Conversation

KingOfBrian
Copy link
Contributor

This will generate a warning if a non-dynamic class declaration is overridden in an extension. When this happens, swift does not update the vtable entry for the class, it just adds the dynamic method. This causes a silent failure when invoking methods via the base class, since the overridden method is not in the vtable.

I updated some Sema unit tests to include this error, since the scenario was tested, and expected to work.

This makes progress on SR-584. I'm investigating the full fix of correctly updating the VTable, if the extension is in the declaring module, but I believe that is in the SILGen code which I haven't touched yet. Any pointers of where to look would be great. I'm going to try to trace into how a dynamic override is able to correctly update the vtable, and see where that goes.

I still think that this fix as it stands would be an improvement, as this can still warn the developer at compile time that the override will not work.

@KingOfBrian
Copy link
Contributor Author

KingOfBrian commented Dec 17, 2016

There are some test failures in CursorInfoTest that I was hoping were a build issue, but still happen. Any thoughts on how to run them without re-running the whole test suite? I scanned through the tests and I'm not sure how I could of broken them, they aren't even operating on classes 🤔

@slavapestov
Copy link
Contributor

@KingOfBrian Run check-swift-validation -v, which prints each command that is run. The final invocation runs lit.py with some parameters. Copy and paste those parameters and add --filter=SourceKit/ to run just the sourcekit tests. When you change the code, run ninja sourcekitd-test to just rebuild sourcekitd without the standard library, etc.

@KingOfBrian
Copy link
Contributor Author

KingOfBrian commented Dec 18, 2016

Thanks @slavapestov -- I couldn't find check-swift-validation, but --filter is a big help. I think it was a build issue though, I ended up removing build and doing a full new build and the tests are passing -- so this PR should be ready for review.

Copy link
Contributor

@gottesmm gottesmm left a comment

Choose a reason for hiding this comment

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

A few small coding style requests.

// warn, since it is not added to the class vtable.
// FIXME: Only warn if the extension is in another module, and if
// it is in the same module, update the vtable.
if (auto baseDecl = dyn_cast<ClassDecl>(base->getDeclContext())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please put the * after the auto. This follows LLVM style:

http://llvm.org/docs/CodingStandards.html#beware-unnecessary-copies-with-auto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, thanks for the link

// If the overridden method is declared in a Swift Class Declaration,
// dispatch will use table dispatch. If the override is in an extension
// warn, since it is not added to the class vtable.
// FIXME: Only warn if the extension is in another module, and if
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you put a new comment in between the FIXME and the rest of the comment.

So this:

// Foo
// Bar
// FIXME:

Becomes

// Foo
// Bar
//
// FIXME:

This optimizes the FIXME for the reader who is quickly scanning the file by making it more obvious.

if (auto *baseDecl = dyn_cast<ClassDecl>(base->getDeclContext())) {
if (baseDecl->hasKnownSwiftImplementation() &&
!base->isDynamic() &&
override->getDeclContext()->isExtensionContext()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make this conditional on !TC.Context.isSwiftVersion3(), or turn it into a warning?

To test both the Swift 3 and 4 behavior, have test/decl/inherit/override.swift pass -swift-version 4 in its RUN line, and add a new test/Compatibility/override.swift which tests the Swift 3 behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh thanks, I did not consider compatibility. I should be able to get a fix up soon.

@slavapestov slavapestov self-assigned this Jan 9, 2017
@jrose-apple
Copy link
Contributor

The other fix we'd like to make in this area is to just take extensions in the same module into account when building the vtable. I guess this doesn't make things worse, though; it just means we stop lying about what's working and what's not.

@slavapestov
Copy link
Contributor

@jrose-apple Just to clarify, implementing the new vtable behavior is not a requirement for this patch going in, is it? I think even after we implement the new SILGen behavior, we'll still want the warning for the case where you're extending a type from another module.

@jrose-apple
Copy link
Contributor

Yes, I agree. It's not a warning but an error anyway. (I suppose we could warn in Swift 3 mode and error in Swift 4 mode.)

@KingOfBrian
Copy link
Contributor Author

Thanks again for the feedback. I went with your suggestion @jrose-apple of generating a warning in swift 3 in the hope that it helps someone. I couldn't figure out how to generate a diagnostic as a warning or an error without adding another entry in DiagnosticsSema.def. Any suggestions to clean that up would be great.

@slavapestov
Copy link
Contributor

Two duplicate diagnostics are fine in this case.

Can you rebase though to kill that merge commit?

@KingOfBrian
Copy link
Contributor Author

Hey @jrose-apple - I made a naive stab at this and was curious what you thought. This is my first dip into SILGen, and my cpp is still pretty rusty, but I was able to get extensions to override. I still don't understand how this could work for extensions in multiple files though. If this is a tree that you think is worth me barking up, I could make a PR for comments, or bring discussion to swift-dev?

KingOfBrian@10690fb

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

The new version looks great. I only have two minor nitpicks.

I had a look at the SILGen vtable change. It looks like a good start. I saw you completely removed the diagnostic, though -- I think we still want it to handle the case where an extension attempts to override a method from another source file or another module.

@@ -1703,6 +1703,12 @@ ERROR(override_ownership_mismatch,none,
"cannot override %select{strong|weak|unowned|unowned(unsafe)}0 property "
"with %select{strong|weak|unowned|unowned(unsafe)}1 property",
(/*Ownership*/unsigned, /*Ownership*/unsigned))
ERROR(override_class_declaration_in_extension,none,
"cannot override a non-dynamic class declaration from an extension.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: remove the .

"cannot override a non-dynamic class declaration from an extension.",
())
WARNING(override_class_declaration_in_extension_warning,none,
"cannot override a non-dynamic class declaration from an extension.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

@jrose-apple
Copy link
Contributor

I agree with @slavapestov: this version looks good to go (with the diagnostic nitpicks). We can do the SILGen part later (and probably not for Swift 3.1).

@jrose-apple
Copy link
Contributor

@swift-ci Please smoke test

@KingOfBrian
Copy link
Contributor Author

I can fix up the diagnostic errors on that commit and make another PR. I've been trying to understand when a module refers to a compilation context and when it refers to the Swift module. This made the warning checks a bit more interesting, so I just disabled them while seeing if I could get the SIL to change correctly.

Allowing VTable expansion inside one file doesn't seem like much of an improvement. It would be great if the VTable could be expanded inside a swift module, but I think that would force compilation to use whole module optimizations. I'm not sure if forcing WMO to build with swift is in the cards or not though. And without forcing WMO, we'd need to restrict VTable assembly to account for the file when building with WMO so the VTable is the same with and without WMO.

Anyway, I'm just thinking these things through, I imagine you guys have a plan here, but it's been fun to tag along.

@jrose-apple jrose-apple merged commit 29b5934 into swiftlang:master Jan 17, 2017
@jrose-apple
Copy link
Contributor

The plan is to go crawl through all extensions in other files when in single-file mode. That's pretty much it. :-)

@KingOfBrian
Copy link
Contributor Author

Interesting, thanks @jrose-apple!

@KingOfBrian KingOfBrian deleted the bugfix/SR-584 branch January 20, 2017 15:04
@rudkx
Copy link
Contributor

rudkx commented Feb 22, 2017

@KingOfBrian I filed an issue related to this diagnostic. It looks like it is emitted multiple times in some cases: https://bugs.swift.org/browse/SR-4024

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.

5 participants