Skip to content
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

8026369: javac potentially ambiguous overload warning needs an improved scheme #12645

Closed
wants to merge 7 commits into from

Conversation

archiecobbs
Copy link
Contributor

@archiecobbs archiecobbs commented Feb 19, 2023

This bug relates to the "potentially ambiguous overload" warning which is enabled by -Xlint:overloads.

The warning detects certain ambiguities that can cause problems for lambdas. For example, consider the interface Spliterator.OfInt, which declares these two methods:

void forEachRemaining(Consumer<? super Integer> action);
void forEachRemaining(IntConsumer action);

Both methods have the same name, same number of parameters, and take a lambda with the same "shape" in the same argument position. This causes an ambiguity in any code that wants to do this:

    spliterator.forEachRemaining(x -> { ... });

That code won't compile; instead, you'll get this error:

Ambiguity.java:4: error: reference to forEachRemaining is ambiguous
        spliterator.forEachRemaining(x -> { });
                   ^
  both method forEachRemaining(IntConsumer) in OfInt and method forEachRemaining(Consumer<? super Integer>) in OfInt match

The problem reported by the bug is that the warning fails to detect ambiguities which are created purely by inheritance, for example:

interface ConsumerOfInteger {
    void foo(Consumer<Integer> c);
}

interface IntegerConsumer {
    void foo(IntConsumer c);
}

// We should get a warning here...
interface Test extends ConsumerOfInteger, IntegerConsumer {
}

The cause of the bug is that ambiguities are detected on a per-method basis, by checking whether a method is part of an ambiguity pair when we visit that method. So if the methods in an ambiguity pair are inherited from two distinct supertypes, we'll miss the ambiguity.

To fix the problem, we need to look for ambiguities on a per-class level, checking all pairs of methods. However, it's not that simple - we only want to "blame" a class when that class itself, and not some supertype, is responsible for creating the ambiguity. For example, any interface extending Spliterator.OfInt will automatically inherit the two ambiguities mentioned above, but these are not the interface's fault so to speak so no warning should be generated. Making things more complicated is the fact that methods can be overridden and declared in generic classes so they only conflict in some subtypes, etc.

So we generate the warning when there are two methods m1 and m2 in a class C such that:

  • m1 and m2 consitiute a "potentially ambiguous overload" (using the same definition as before)
  • There is no direct supertype T of C such that m1 and m2, or some methods they override, both exist in T and constitute a "potentially ambiguous overload" as members of T
  • We haven't already generated a warning for either m1 or m2 in class C

If either method is declared in C, we locate the warning there, but when both methods are inherited, there's no method declaration to point at so the warning is instead located at the class declaration.

I noticed a couple of other minor bugs; these are also being fixed here:

(1) For inherited methods, the method signatures were being reported as they are declared, rather than in the context of the class being visited. As a result, when a methods is inherited from a generic supertype, the ambiguity is less clear. Here's an example:

interface Upper<T> {
    void foo(T c);
}

interface Lower extends Upper<IntConsumer> {
    void foo(Consumer<Integer> c);
}

Currently, the error is reported as:

warning: [overloads] foo(Consumer<Integer>) in Lower is potentially ambiguous with foo(T) in Upper

Reporting the method signatures in the context of the class being visited makes the ambiguity clearer:

warning: [overloads] foo(Consumer<Integer>) in Lower is potentially ambiguous with foo(IntConsumer) in Upper

(2) When a method is identified as part of an ambiguous pair, we were setting a POTENTIALLY_AMBIGUOUS flag on it. This caused it to be forever excluded from future warnings. For methods that are declared in the class we're visiting, this makes sense, but it doesn't make sense for inherited methods, because it disqualifies them from participating in the analysis of any other class that also inherits them.

As a result, for a class like the one below, the compiler was only generating one warning instead of three:

public interface SuperIface {
    void foo(Consumer<Integer> c);
}

public interface I1 extends SuperIface { 
    void foo(IntConsumer c);        // warning was generated here
}   

public interface I2 extends SuperIface { 
    void foo(IntConsumer c);        // no warning was generated here
}   

public interface I3 extends SuperIface { 
    void foo(IntConsumer c);        // no warning was generated here
}   

With this patch the POTENTIALLY_AMBIGUOUS flag is no longer needed. I wasn't sure whether to renumber all the subsequent flags, or just leave an empty placeholder, so I chose the latter.

Finally, this fix uncovers new warnings in java.base and java.desktop, so these are now suppressed in the patch.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8303170 to be approved

Issues

  • JDK-8026369: javac potentially ambiguous overload warning needs an improved scheme
  • JDK-8303170: Generate "potentially ambiguous overload" for two inherited methods (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/12645/head:pull/12645
$ git checkout pull/12645

Update a local copy of the PR:
$ git checkout pull/12645
$ git pull https://git.openjdk.org/jdk pull/12645/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 12645

View PR using the GUI difftool:
$ git pr show -t 12645

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/12645.diff

@bridgekeeper
Copy link

bridgekeeper bot commented Feb 19, 2023

👋 Welcome back archiecobbs! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk openjdk bot added the rfr Pull request is ready for review label Feb 19, 2023
@openjdk
Copy link

openjdk bot commented Feb 19, 2023

@archiecobbs The following labels will be automatically applied to this pull request:

  • client
  • compiler
  • core-libs
  • i18n

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added client client-libs-dev@openjdk.org core-libs core-libs-dev@openjdk.org compiler compiler-dev@openjdk.org i18n i18n-dev@openjdk.org labels Feb 19, 2023
@mlbridge
Copy link

mlbridge bot commented Feb 19, 2023

Webrevs

@SWinxy
Copy link
Contributor

SWinxy commented Feb 20, 2023

In the AWTEventMulticaster class(es), which interfaces are causing the ambiguity?

@archiecobbs
Copy link
Contributor Author

In the AWTEventMulticaster class(es), which interfaces are causing the ambiguity?

Ah - my apologies. Those got added during development but were not needed once all the bugs were worked out.

I've removed them and that will also eliminate client and i18n from review. Thanks!

/label remove client,i18n

@openjdk openjdk bot removed client client-libs-dev@openjdk.org i18n i18n-dev@openjdk.org labels Feb 21, 2023
@openjdk
Copy link

openjdk bot commented Feb 21, 2023

@archiecobbs
The client label was successfully removed.

The i18n label was successfully removed.

*/
public static final long POTENTIALLY_AMBIGUOUS = 1L<<48;
public static final long UNUSED_1 = 1L<<48;
Copy link
Contributor

Choose a reason for hiding this comment

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

what about just adding the comment saying that this spot is free without declaring an unused flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works for me - as long as it's OK that this will cause the Flags enum constant ordinals to renumber after that point.

Fixed in 0131b9b.

@vicente-romero-oracle
Copy link
Contributor

I think a CSR is needed as we will be generating more warnings and projects compiling with -Werror could see compilation errors

@archiecobbs
Copy link
Contributor Author

I think a CSR is needed as we will be generating more warnings and projects compiling with -Werror could see compilation errors

Will do - thanks.

@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Feb 24, 2023
* ambiguities for both forEachRemaining() and tryAdvance() (in both cases the
* overloads are IntConsumer and Consumer&lt;? super Integer&gt;). So we only want
* to "blame" a class when that class is itself responsible for creating the
* ambiguity. So we declare that site is "responsible" for the ambigutity between
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: ambigutity -> ambiguity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks - fixed.

* As an optimization, we first check if either method is declared in site and
* does not override any other methods (in which case site is responsible).
*/
void checkPotentiallyAmbiguousOverloads(JCClassDecl tree, Type site) {
Copy link
Contributor

Choose a reason for hiding this comment

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

general comments: overall looks correct but I think the code should be split a bit using helper methods, that will help with readability, I think. Side: I'm a bit worried that overuse of streams in this code could imply some performance hit. Of course if the corresponding lint warning is not enabled we will skip it but a lot of projects compile with -Xlint:all nowadays.

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 the code should be split a bit using helper methods

OK - will do.

I'm a bit worried that overuse of streams in this code could imply some performance hit

I asked basically the same question (in a separate thread) two days ago and nobody replied with a definitive answer (not surprising).

However, note also that in that same thread Christoph reported no timing difference between Stream vs. for() loop (here), although there were more allocations. FWIW.

Sorry to go off on a tangent here, but I'm sure this issue will come up again and it would be nice to have some kind of (informal) policy to go on...

I generally try to follow the "measure first, optimize second" rule to avoid preemptive "optimizations" that come at the expense of code clarity for unproven meaningful benefit.

So I can de-Stream the code but are we sure it's worth it? Are we going to have a no Stream policy in the compiler code? Why did we develop Stream's if they can't be used in a mainstream tool like javac? Where does the madness end? :)

There is also the larger philosophical question as well, which is that if a Stream is appreciably slower than the semantically-equivalent for() loop, then isn't that a Hotspot problem, not a developer problem?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not saying that we should de-stream the code, actually we can do that later on, in a separate issue, iff there is a performance related complain, but it is true that in the past, I have seen some performance issues and the final culprit have been streams. But you are right it could be that it is not worthy to affect the readability of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK thanks, I'll leave it for now - but it would be nice to (someday) do some comprehensive testing so we have a better intuitive understanding of the performance impact of using a Stream in any particular situation.

I wonder if there is some IDE tool that could automatically Streamify and/or de-Streamify loops. If so, we could apply it to the entire compiler and compare...

Copy link
Contributor

Choose a reason for hiding this comment

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

yes that would be nice to have, although streams have different effects depending on how hot the code path is, so in general they are OK. Inference code is one of the places where I have seen that using streams is not a good idea for example

List<java.util.List<MethodSymbol>> methodsByName = StreamSupport.stream(
types.membersClosure(site, false).getSymbols(new ClashFilter(site), RECURSIVE).spliterator(), false)
.map(MethodSymbol.class::cast)
.filter(m -> m.owner.type.tsym != syms.objectType.tsym)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this filter could be folded with the one above, new ClashFilter(site)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed in 7d8a54c.

// Gather all supertype methods overridden by m, directly or indirectly
java.util.List<MethodSymbol> overriddenMethods = list.stream()
.filter(m2 -> m2 != m)
.filter(m2 -> overrides.test(m, m2))
Copy link
Contributor

Choose a reason for hiding this comment

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

can these two filters be folded?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed in 7d8a54c.

.stream()
.sorted(Comparator.comparing(e -> e.getKey().toString()))
.map(Map.Entry::getValue)
.peek(Collections::reverse) // seems to help warning ordering
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure about reversing the order here, it seems to me that if the order is reversed then the warning is shown in the first occurrence of a method instead of in the second which is the offending one. So for example for this code:

    interface I1 {
        void foo(Consumer<Integer> c);
        void foo(IntConsumer c);
    }

the warning is shown for the first method when I think it should be shown for the second which is the one introducing the ambiguity

EDIT, after your last commit, this comment now applies to: methodGroups.forEach(Collections::reverse);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reversing was originally done in order to maintain consistency with the existing regression test outputs. But you are right - it results in visiting the methods backwards.

Fixed in e25cece.

* As an optimization, we first check if either method is declared in C and does not override
* any other methods; in this case the class is definitely responsible.
*/
BiPredicate<MethodSymbol, MethodSymbol> buildResponsiblePredicate(Type site,
Copy link
Contributor

Choose a reason for hiding this comment

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

this method not only builds the predicate as its name indicates, it also removes methods from the methodGroups, not saying that we should split this method but I think that this removing activity should be mentioned in the comment above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, my bad - previous refactoring was incomplete.

Should be fixed in e25cece.

Thanks again for the careful review!

Copy link
Contributor

Choose a reason for hiding this comment

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

sure, thanks, nice refactoring

Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

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

looks good, thanks for fixing this!

@openjdk openjdk bot removed the csr Pull request needs approved CSR before integration label Feb 27, 2023
@openjdk
Copy link

openjdk bot commented Feb 27, 2023

@archiecobbs This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8026369: javac potentially ambiguous overload warning needs an improved scheme

Reviewed-by: vromero

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 122 new commits pushed to the master branch:

  • f7f1036: 8303068: Memory leak in DwarfFile::LineNumberProgram::run_line_number_program
  • 784f7b1: 8293667: Align jlink's --compress option with jmod's --compress option
  • 54603aa: 8303208: JFR: 'jfr print' displays incorrect timestamps
  • 4c169d2: 8303253: Remove unnecessary calls to super() in java.time value based classes
  • b527edd: 8292914: Lambda proxies have unstable names
  • 42330d2: 8029370: (fc) FileChannel javadoc not clear for cases where position == size
  • a253b46: 8301119: Support for GB18030-2022
  • 55e6bb6: 8302685: Some javac unit tests aren't reliably closing open files
  • f5a1276: 8262895: [macos_aarch64] runtime/CompressedOops/CompressedClassPointers.java fails with 'Narrow klass base: 0x0000000000000000' missing from stdout/stderr
  • 2fe4e5f: 8303169: Remove Windows specific workaround from libdt
  • ... and 112 more: https://git.openjdk.org/jdk/compare/7abe26935ab4356de54acee93390a0d8be1ea289...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@vicente-romero-oracle) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Feb 27, 2023
@archiecobbs
Copy link
Contributor Author

/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Feb 27, 2023
@openjdk
Copy link

openjdk bot commented Feb 27, 2023

@archiecobbs
Your change (at version e25cece) is now ready to be sponsored by a Committer.

@vicente-romero-oracle
Copy link
Contributor

/sponsor

@openjdk
Copy link

openjdk bot commented Feb 28, 2023

Going to push as commit 1e3c9fd.
Since your change was applied there have been 124 commits pushed to the master branch:

  • 14a014d: 8302124: HotSpot Style Guide should permit noreturn attribute
  • bca60f4: 8303249: JFR: Incorrect description of dumponexit
  • f7f1036: 8303068: Memory leak in DwarfFile::LineNumberProgram::run_line_number_program
  • 784f7b1: 8293667: Align jlink's --compress option with jmod's --compress option
  • 54603aa: 8303208: JFR: 'jfr print' displays incorrect timestamps
  • 4c169d2: 8303253: Remove unnecessary calls to super() in java.time value based classes
  • b527edd: 8292914: Lambda proxies have unstable names
  • 42330d2: 8029370: (fc) FileChannel javadoc not clear for cases where position == size
  • a253b46: 8301119: Support for GB18030-2022
  • 55e6bb6: 8302685: Some javac unit tests aren't reliably closing open files
  • ... and 114 more: https://git.openjdk.org/jdk/compare/7abe26935ab4356de54acee93390a0d8be1ea289...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Feb 28, 2023
@openjdk openjdk bot closed this Feb 28, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Feb 28, 2023
@openjdk
Copy link

openjdk bot commented Feb 28, 2023

@vicente-romero-oracle @archiecobbs Pushed as commit 1e3c9fd.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler compiler-dev@openjdk.org core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated
3 participants