Skip to content

8348427: DeferredLintHandler API should use JCTree instead of DiagnosticPosition#23281

Closed
archiecobbs wants to merge 7 commits intoopenjdk:masterfrom
archiecobbs:JDK-8348427
Closed

8348427: DeferredLintHandler API should use JCTree instead of DiagnosticPosition#23281
archiecobbs wants to merge 7 commits intoopenjdk:masterfrom
archiecobbs:JDK-8348427

Conversation

@archiecobbs
Copy link
Contributor

@archiecobbs archiecobbs commented Jan 23, 2025

The purpose of DeferredLintHandler is to allow @SuppressWarnings to be applied to warnings that are generated before @SuppressWarnings annotations themselves have been processed. The way this currently works is that warning callbacks are kept in a HashMap keyed by the innermost containing module, package, class, method, or variable declarations (in the form of a JCModuleDecl, JCPackageDecl, JCClassDecl, JCMethodDecl, or JCVariableDecl). Later, when the compiler executes the attribution phase and the lint categories suppressed at each declaration are known, the corresponding warning callbacks are provided with an appropriately configured Lint instance and "flushed".

However, the DeferredLintHandler API uses DiagnosticPosition instead of JCTree for registering and flushing deferred warnings. This opens the door for bugs where warnings are registered to an object which is not a declaration, and therefore ignored.

In fact, this occurs once in the code (here) where a JCExpression is being passed, although in this case the bug appears to be harmless (because annotation values can't contain any type of declaration).

The API should be tighted up, and furthermore an assertion should be added to verify that the JCTree being passed is actually a declaration supporting @SuppressWarnings.

In addition, there is a design flaw in the API: it's not possible to obtain the current immediate mode Lint object, so if an immediate mode Lint object is pushed/popped more than once, the second Lint object will overwrite the first.

To fix this, the API should be adjusted so the stack of current declarations and/or immediate mode Lint objects is managed by the DeferredLintHandler itself, by providing a push() and pop() methods in the API.


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

Issue

  • JDK-8348427: DeferredLintHandler API should use JCTree instead of DiagnosticPosition (Enhancement - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23281

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jan 23, 2025

👋 Welcome back acobbs! 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
Copy link

openjdk bot commented Jan 23, 2025

@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:

8348427: DeferredLintHandler API should use JCTree instead of DiagnosticPosition

Reviewed-by: mcimadamore

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 22 new commits pushed to the master branch:

  • 1079147: 8348570: CTW: Expose the code hidden by uncommon traps
  • 1a74ee6: 8349092: File.getFreeSpace violates specification if quotas are in effect (win)
  • 0181030: 8349006: File.getCanonicalPath should remove "(on UNIX platforms)" from its specification
  • ed8945a: 8347377: Add validation checks for ICC_Profile header fields
  • 1ab1c1d: 8349058: 'internal proprietary API' warnings make javac warnings unusable
  • eb84702: 8349513: Remove unused BUILD_JDK_JTREG_LIBRARIES_JDK_LIBS_libTracePinnedThreads
  • f12d2de: 8345212: Since checker should better handle non numeric values
  • 5ec1aae: 8347842: ThreadPoolExecutor specification discusses RuntimePermission
  • 3fbae32: 8349465: [UBSAN] test_os_reserve_between.cpp reported applying non-zero offset to null pointer
  • e0487c7: 8346777: Add missing const declarations and rename variables
  • ... and 12 more: https://git.openjdk.org/jdk/compare/89e5e7ab73472b7d02aac5b8b0c7e9f26db6ec32...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.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Jan 23, 2025

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

  • compiler

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

@openjdk openjdk bot added the compiler compiler-dev@openjdk.org label Jan 23, 2025
@archiecobbs archiecobbs marked this pull request as ready for review January 23, 2025 21:43
@openjdk openjdk bot added the rfr Pull request is ready for review label Jan 23, 2025
@mlbridge
Copy link

mlbridge bot commented Jan 23, 2025

Webrevs

loggers.append(logger);
}
public void push(JCTree decl) {
Assert.check(decl.getTag() == Tag.MODULEDEF
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

@mcimadamore
Copy link
Contributor

Question: looking more broadly at the deferred lint handler API, I see that in most cases (all but one) report is called with a lambda that ignores its lint object. This seems odd - consider this:

private void warnOnExplicitStrictfp(DiagnosticPosition pos) {
        DiagnosticPosition prevLintPos = deferredLintHandler.setPos(pos);
        try {
            deferredLintHandler.report(_ -> lint.logIfEnabled(log, pos, LintWarnings.Strictfp)); // <---------------
        } finally {
            deferredLintHandler.setPos(prevLintPos);
        }
    }

Is the above code correct? E.g. should the call on lint.logIfEnabled occur on the lint that is the parameter to the lambda expression?

}
loggers.append(logger);
}
public void push(JCTree decl) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if enterDecl/leaveDecl would be more evocative than push/pop. (but don't have any suggestion for the weird immediate mode).

@mcimadamore
Copy link
Contributor

I have some doubts about the general deferred lint handler API - but my doubts are completely unrelated to your PR which, I think, does a nice job of making the API a little more tight, and easier to use for clients. Good job!

@mcimadamore
Copy link
Contributor

(My doubts re DeferredLintHandler are whether it's a good abstraction or not. It seems to provide, on paper, a good service: allow for lint warnings to be correctly reported in earlier phases of the compiler. However, I note that:

  • this class seems to be serving too many masters -- the "immediate" mode seems to be a sign of that
  • using this class correctly is rather finicky, and depends on which part of javac you are on. For instance, Flow doesn't use it, because it is after Attr... in other words, reporting of lint warnings is not centralized - and each components does what it needs to get the job done -- understandable, but leads to messy code (this also leads to Check::setLint)
  • the synergy between Lint and DeferredLintHandler goes quite deep. For instance, flush has to be called in a place where Lint has been augmented with the contents of the relevant SuppressWarnings. All these dependencies make it quite hard to verify that the code does what it needs to do.

My general feeling here is that we'd be better off with a separate compilation step that runs after all the various front-end steps (e.g. after Flow). E.g. up to Flow, we keep accumulating lint warnings grouped by declaration. Then, in a single pass we take care of updating Lint with the correct @SuppressWarning found in a given declaration, and flush all the pending lint warnings for all that declaration. This would also mean that Attr and Check would be hypothetically be freed from Lint duties. Maybe this is too extreme, and we can't get there in one step - but I'd like to know what you think about the general direction (since we have already added more compilation steps in this area).

@archiecobbs
Copy link
Contributor Author

archiecobbs commented Jan 24, 2025

Is the above code correct? E.g. should the call on lint.logIfEnabled occur on the lint that is the parameter to the lambda expression?

I wondered the same thing, and it seems plainly counter to the whole point of warning deferral. I haven't tried to figure it out yet - I assume it must work because when the lambda executes, the correct Lint object happens to still be in place.

But you got me curious... my theory is easy to test with this patch:

diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
index fe40bf0dad1..3a23c5df6d6 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
@@ -244,7 +244,7 @@ MethodSymbol setMethod(MethodSymbol newMethod) {
      *  @param pos        Position to be used for error reporting.
      *  @param sym        The deprecated symbol.
      */
-    void warnDeprecated(DiagnosticPosition pos, Symbol sym) {
+    void warnDeprecated(Lint lint, DiagnosticPosition pos, Symbol sym) {
         if (sym.isDeprecatedForRemoval()) {
             if (!lint.isSuppressed(LintCategory.REMOVAL)) {
                 if (sym.kind == MDL) {
@@ -284,7 +284,7 @@ public void warnDeclaredUsingPreview(DiagnosticPosition pos, Symbol sym) {
      *  @param pos        Position to be used for error reporting.
      *  @param msg        A Warning describing the problem.
      */
-    public void warnRestrictedAPI(DiagnosticPosition pos, Symbol sym) {
+    public void warnRestrictedAPI(Lint lint, DiagnosticPosition pos, Symbol sym) {
         lint.logIfEnabled(log, pos, LintWarnings.RestrictedMethod(sym.enclClass(), sym));
     }
 
@@ -648,8 +648,8 @@ public void checkRedundantCast(Env<AttrContext> env, final JCTypeCast tree) {
                 && types.isSameType(tree.expr.type, tree.clazz.type)
                 && !(ignoreAnnotatedCasts && TreeInfo.containsTypeAnnotation(tree.clazz))
                 && !is292targetTypeCast(tree)) {
-            deferredLintHandler.report(_l -> {
-                lint.logIfEnabled(log, tree.pos(), LintWarnings.RedundantCast(tree.clazz.type));
+            deferredLintHandler.report(lint2 -> {
+                lint2.logIfEnabled(log, tree.pos(), LintWarnings.RedundantCast(tree.clazz.type));
             });
         }
     }
@@ -1324,7 +1324,7 @@ && checkDisjoint(pos, flags,
     private void warnOnExplicitStrictfp(JCTree tree) {
         deferredLintHandler.push(tree);
         try {
-            deferredLintHandler.report(_ -> lint.logIfEnabled(log, tree.pos(), LintWarnings.Strictfp));
+            deferredLintHandler.report(lint2 -> lint2.logIfEnabled(log, tree.pos(), LintWarnings.Strictfp));
         } finally {
             deferredLintHandler.pop();
         }
@@ -3785,7 +3785,7 @@ void checkDeprecated(Supplier<DiagnosticPosition> pos, final Symbol other, final
                 || s.isDeprecated() && !other.isDeprecated())
                 && (s.outermostClass() != other.outermostClass() || s.outermostClass() == null)
                 && s.kind != Kind.PCK) {
-            deferredLintHandler.report(_l -> warnDeprecated(pos.get(), s));
+            deferredLintHandler.report(lint2 -> warnDeprecated(lint2, pos.get(), s));
         }
     }
 
@@ -3850,7 +3850,7 @@ void checkPreview(DiagnosticPosition pos, Symbol other, Type site, Symbol s) {
 
     void checkRestricted(DiagnosticPosition pos, Symbol s) {
         if (s.kind == MTH && (s.flags() & RESTRICTED) != 0) {
-            deferredLintHandler.report(_l -> warnRestrictedAPI(pos, s));
+            deferredLintHandler.report(lint2 -> warnRestrictedAPI(lint2, pos, s));
         }
     }
 
@@ -4122,7 +4122,7 @@ void checkDivZero(final DiagnosticPosition pos, Symbol operator, Type operand) {
             int opc = ((OperatorSymbol)operator).opcode;
             if (opc == ByteCodes.idiv || opc == ByteCodes.imod
                 || opc == ByteCodes.ldiv || opc == ByteCodes.lmod) {
-                deferredLintHandler.report(_ -> lint.logIfEnabled(log, pos, LintWarnings.DivZero));
+                deferredLintHandler.report(lint2 -> lint2.logIfEnabled(log, pos, LintWarnings.DivZero));
             }
         }
     }
@@ -4135,8 +4135,8 @@ void checkDivZero(final DiagnosticPosition pos, Symbol operator, Type operand) {
      */
     void checkLossOfPrecision(final DiagnosticPosition pos, Type found, Type req) {
         if (found.isNumeric() && req.isNumeric() && !types.isAssignable(found, req)) {
-            deferredLintHandler.report(_ ->
-                lint.logIfEnabled(log, pos, LintWarnings.PossibleLossOfPrecision(found, req)));
+            deferredLintHandler.report(lint2 ->
+                lint2.logIfEnabled(log, pos, LintWarnings.PossibleLossOfPrecision(found, req)));
         }
     }
 
@@ -4335,8 +4335,8 @@ void checkDefaultConstructor(ClassSymbol c, DiagnosticPosition pos) {
                             // Warning may be suppressed by
                             // annotations; check again for being
                             // enabled in the deferred context.
-                            deferredLintHandler.report(_ ->
-                                lint.logIfEnabled(log, pos, LintWarnings.MissingExplicitCtor(c, pkg, modle)));
+                            deferredLintHandler.report(lint2 ->
+                                lint2.logIfEnabled(log, pos, LintWarnings.MissingExplicitCtor(c, pkg, modle)));
                         } else {
                             return;
                         }
@@ -4670,26 +4670,26 @@ private void checkVisible(DiagnosticPosition pos, Symbol what, PackageSymbol inP
 
     void checkModuleExists(final DiagnosticPosition pos, ModuleSymbol msym) {
         if (msym.kind != MDL) {
-            deferredLintHandler.report(_ ->
-                lint.logIfEnabled(log, pos, LintWarnings.ModuleNotFound(msym)));
+            deferredLintHandler.report(lint2 ->
+                lint2.logIfEnabled(log, pos, LintWarnings.ModuleNotFound(msym)));
         }
     }
 
     void checkPackageExistsForOpens(final DiagnosticPosition pos, PackageSymbol packge) {
         if (packge.members().isEmpty() &&
             ((packge.flags() & Flags.HAS_RESOURCE) == 0)) {
-            deferredLintHandler.report(_ ->
-                lint.logIfEnabled(log, pos, LintWarnings.PackageEmptyOrNotFound(packge)));
+            deferredLintHandler.report(lint2 ->
+                lint2.logIfEnabled(log, pos, LintWarnings.PackageEmptyOrNotFound(packge)));
         }
     }
 
     void checkModuleRequires(final DiagnosticPosition pos, final RequiresDirective rd) {
         if ((rd.module.flags() & Flags.AUTOMATIC_MODULE) != 0) {
-            deferredLintHandler.report(_ -> {
-                if (rd.isTransitive() && lint.isEnabled(LintCategory.REQUIRES_TRANSITIVE_AUTOMATIC)) {
+            deferredLintHandler.report(lint2 -> {
+                if (rd.isTransitive() && lint2.isEnabled(LintCategory.REQUIRES_TRANSITIVE_AUTOMATIC)) {
                     log.warning(pos, LintWarnings.RequiresTransitiveAutomatic);
                 } else {
-                    lint.logIfEnabled(log, pos, LintWarnings.RequiresAutomatic);
+                    lint2.logIfEnabled(log, pos, LintWarnings.RequiresAutomatic);
                 }
             });
         }

Turns out it's not quite that simple; the above patch breaks the build:

Building target 'test' in configuration 'macosx-aarch64-server-release'
Compiling up to 360 files for BUILD_jdk.compiler.interim
Updating support/src.zip
Compiling up to 368 files for jdk.compiler
/Users/archie/proj/jdk/src/jdk.compiler/share/classes/com/sun/tools/javac/file/FSInfo.java:142: warning: [deprecation] URL(URL,String) in URL has been deprecated
        URL retVal = new URL(base, input);
                     ^
error: warnings found and -Werror specified
/Users/archie/proj/jdk/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java:269: warning: [restricted] Controller.enableNativeAccess(Module) is a restricted method.
            controller.enableNativeAccess(module);
                      ^
  (Restricted methods are unsafe and, if used incorrectly, might crash the Java runtime or corrupt memory)
1 error
2 warnings
make[3]: *** [/Users/archie/proj/jdk/build/macosx-aarch64-server-release/jdk/modules/jdk.compiler/_the.jdk.compiler_batch] Error 1
make[2]: *** [jdk.compiler-java] Error 2

So I don't know what's going on there exactly and have so far avoided that particular dark corner.

I wonder if enterDecl/leaveDecl would be more evocative than push/pop. (but don't have any suggestion for the weird immediate mode).

Yes that naming would be better - if there were no immediate mode. But since immediate mode exists, the stack contains more than just declarations. Also, push() hopefully makes it more obvious that you are responsible putting in place a corresponding pop().

My general feeling here is that we'd be better off with a separate compilation step that runs after all the various front-end steps (e.g. after Flow).

I have that same feeling :) We have the new warn() compiler phase, so that would be the logical place to put the grand flush() operation.

I think this is do-able. Semantically things would get simpler, which means while the patch would be large, it would consist mostly of decomplexification. Rough sketch (this would come after #23237):

  • Move the deferral logic of DeferredLintManager into Lint; the DeferredLintManager singleton goes away.
  • Lint has a single flush() method which is invoked once per source file during the compiler warn() step
    • First, lexical deferrals are remapped to innermost containing JCTree
    • Next, all warnings are emitted (in lexical order) by scanning the JCCompilationUnit and flushing each declaration
  • Lint warnings are created via Lint.logIfEnabled() (or similar) at any time during compilation
    • If you can locate the warning with a JCTree, you supply that
    • Otherwise, you locate the warning with an integer pos source file offset
    • In either case, an assertion verifies that flush() has not yet been invoked

What do you think?

One thing I'm unclear of is whether flush() can assume that all of the accumulated warnings correspond to the same file. Might not be true with CompilePolicy.BY_TODO?

@mcimadamore
Copy link
Contributor

I think this is do-able. Semantically things would get simpler, which means while the patch would be large, it would consist mostly of decomplexification. Rough sketch (this would come after #23237):

* Move the deferral logic of `DeferredLintManager` into `Lint`; the `DeferredLintManager` singleton goes away.

* Lint has a single `flush()` method which is invoked _once_ per source file during the compiler `warn()` step
  
  * First, lexical deferrals are remapped to innermost containing `JCTree`
  * Next, all warnings are emitted (in lexical order) by scanning the `JCCompilationUnit` and flushing each declaration

* Lint warnings are created via `Lint.logIfEnabled()` (or similar) at any time during compilation
  
  * If you can locate the warning with a `JCTree`, you supply that
  * Otherwise, you locate the warning with an integer `pos` source file offset
  * In either case, an assertion verifies that `flush()` has not yet been invoked

What do you think?

This seems like a great plan. We don't have to get there in one step (and this PR can proceed separately). I just want to make sure that we end up somewhere around there :-)

(as your quick experiment with Check suggests, some of this stuff is not trivial at all -- even the use of the immediate mode seems a bit odd to be honest).

One thing I'm unclear of is whether flush() can assume that all of the accumulated warnings correspond to the same file. Might not be true with CompilePolicy.BY_TODO?

I believe that in flow we still see all the classes in the same source as belonging to the same toplevel unit. It's after desugar that things start to get messier. So, I believe that the warning visitor can assume that it's called once per compilation unit, and call flush on all the stuff that belongs to that unit.

@archiecobbs
Copy link
Contributor Author

This seems like a great plan. We don't have to get there in one step (and this PR can proceed separately). I just want to make sure that we end up somewhere around there :-)

Sounds good. I created JDK-8348611 to capture the idea.

I believe that in flow we still see all the classes in the same source as belonging to the same toplevel unit. It's after desugar that things start to get messier. So, I believe that the warning visitor can assume that it's called once per compilation unit, and call flush on all the stuff that belongs to that unit.

Thanks - that makes life simpler.


import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these imports all used?

Copy link
Contributor

@mcimadamore mcimadamore left a comment

Choose a reason for hiding this comment

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

Question: what happens if we get rid of the "immediate" and we always defer? Does this only affect the order in which diagnostics are reported, or is there something deeper?

@archiecobbs
Copy link
Contributor Author

Question: what happens if we get rid of the "immediate" and we always defer? Does this only affect the order in which diagnostics are reported, or is there something deeper?

I learn a little bit more about this. It looks like the only time immediate mode is used explicitly (i.e., other due to DeferredLintHandler's default state) is when resolving imports (here). In that case, of course, there can be no enclosing @SuppressWarnings so that makes sense.

It looks like the other apparent use (here) never occurs because annotateLater() is never invoked with a null deferPos (I've cleaned this up and added an assertion).

This seems like a great plan. We don't have to get there in one step (and this PR can proceed separately). I just want to make sure that we end up somewhere around there :-)

I did some more exploration of this idea. The main trip-up at this point is the speculative compilation stuff, because in that case warnings that occur as a side effect should be discarded because they're not "real". This is done via Log.DeferredDiagnosticHandler. So my initial strategy of "Just defer all warnings and actually execute them later" is too simplistic (although I do have a prototype which "almost works" I'm happy to share). If/when you have time I'd like to discuss how to proceed with you. That discussion probably belongs somewhere else e.g. on compiler-dev or a draft PR.

@mcimadamore
Copy link
Contributor

mcimadamore commented Feb 6, 2025

learn a little bit more about this. It looks like the only time immediate mode is used explicitly (i.e., other due to DeferredLintHandler's default state) is when resolving imports (here). In that case, of course, there can be no enclosing @SuppressWarnings so that makes sense.

Can we use a similar "flush" strategy ? E.g. make the warnings be part of an import declaration, and then process them as part of a flush? While it's good that you knocked one of the two usages off -- it's a bit sad to have this machinery "just" for one case.

Separately, I wonder if warnings issued from import statements should be suppressible as well (perhaps piggy backing on the toplevel class @SuppressWarnings uhmmm). It seems bad that there's some warnings that can't be suppressed programmatically.

Copy link
Contributor

@mcimadamore mcimadamore left a comment

Choose a reason for hiding this comment

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

Regardless of the separate discussion re. "pushImmediate", this PR contains a lot of good changes, and I'm ok with it being integrated as is. We can see if we can get rid of the "immediate" state in a separate PR.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Feb 6, 2025
@mcimadamore
Copy link
Contributor

learn a little bit more about this. It looks like the only time immediate mode is used explicitly (i.e., other due to DeferredLintHandler's default state) is when resolving imports (here). In that case, of course, there can be no enclosing @SuppressWarnings so that makes sense.

Can we use a similar "flush" strategy ? E.g. make the warnings be part of an import declaration, and then process them as part of a flush? While it's good that you knocked one of the two usages off -- it's a bit sad to have this machinery "just" for one case.

Separately, I wonder if warnings issued from import statements should be suppressible as well (perhaps piggy backing on the toplevel class @SuppressWarnings uhmmm). It seems bad that there's some warnings that can't be suppressed programmatically.

I think it would be useful to understand which warnings can be generated during resolveImports. @jddarcy pointed me at:

https://openjdk.org/jeps/211

See related change:

https://hg.openjdk.org/jdk9/jdk9/langtools/rev/82384454947c

Where we explicitly disable deprecation warnings on import since 9. Is there any other?

@mcimadamore
Copy link
Contributor

learn a little bit more about this. It looks like the only time immediate mode is used explicitly (i.e., other due to DeferredLintHandler's default state) is when resolving imports (here). In that case, of course, there can be no enclosing @SuppressWarnings so that makes sense.

Can we use a similar "flush" strategy ? E.g. make the warnings be part of an import declaration, and then process them as part of a flush? While it's good that you knocked one of the two usages off -- it's a bit sad to have this machinery "just" for one case.
Separately, I wonder if warnings issued from import statements should be suppressible as well (perhaps piggy backing on the toplevel class @SuppressWarnings uhmmm). It seems bad that there's some warnings that can't be suppressed programmatically.

I think it would be useful to understand which warnings can be generated during resolveImports. @jddarcy pointed me at:

https://openjdk.org/jeps/211

See related change:

https://hg.openjdk.org/jdk9/jdk9/langtools/rev/82384454947c

Where we explicitly disable deprecation warnings on import since 9. Is there any other?

If this was indeed the only possible case, there is a possible silver lining, because either warnings in imports are enabled (e.g. --source 8) in which case we can just report them immediately, as they can't be suppressed, or they are disabled (e.g. --source >= 9) in which case no warning should even be generated, period.

But I'm sure reality is more complex than that :-)

@archiecobbs
Copy link
Contributor Author

Hi @mcimadamore,

Thanks for the review & comments!

Can we use a similar "flush" strategy ? E.g. make the warnings be part of an import declaration, and then process them as part of a flush? While it's good that you knocked one of the two usages off -- it's a bit sad to have this machinery "just" for one case.

Agreed - it seems to be a result of mixing the ordering of attributing things and flushing the DeferredLintHandler. An example is this program:

import java.security.PolicySpi;
public class A {

    @SuppressWarnings("deprecation")
    private PolicySpi foo;

    @SuppressWarnings("deprecation")
    public void m() throws Exception {
        A.class.newInstance();
    }
}

If you comment out the one remaining deferredLintHandler.pushImmediate() invocation (in TypeEnter.java), then watch watch happens when compiling the above program, you'll see that checkDeprecated() is invoked twice for PolicySpi, and the first time (other = unnamed package) the current Lint instance is correct but the deferred instance is incorrect, and the second time (other = A) the reverse is true; when it's invoked for newInstance() (only once), the current instance is correct but the deferred instance is incorrect. So - all still very confusing to me.

"When to flush" is indeed a key issue for other reasons too. For example, it appears that JCTree nodes tend to randomly disappear after attribution (well, it only seems random to me so far), so that deferring warning analysis for too long can set you up for a NullPointerException chasing exercise. A simple example is in Gen.genClass() where all of a class' declarations are discarded after the classfile is generate (whether that matters depends on the compiler execution mode), and there are others which I'm still chasing down.

I think it would be useful to understand which warnings can be generated during resolveImports. @jddarcy pointed me at: https://openjdk.org/jeps/211

I ran across this as well and accommodated it my prototype by effectively pretending there is a @SuppressWarnings("deprecation", "removal", "preview") on every import statement if source ≥ 9. This is possible because the @SuppressWarning analysis is a completely separate step.

Separately, I wonder if warnings issued from import statements should be suppressible as well (perhaps piggy backing on the toplevel class @SuppressWarnings uhmmm). It seems bad that there's some warnings that can't be suppressed programmatically.

It would be nice if the compiler allowed @SuppressWarnings in more places. If/when it ever does, the proposed refactoring would hopefully make it easier to implement.

@archiecobbs
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Feb 12, 2025

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

  • c5ac3c4: 8249831: Test sun/security/mscapi/nonUniqueAliases/NonUniqueAliases.java is marked with @ignore
  • 336d0d8: 8349926: [BACKOUT] Support static JDK in libfontmanager/freetypeScaler.c
  • 4b463ee: 8342103: C2 compiler support for Float16 type and associated scalar operations
  • 332d87c: 8349859: Support static JDK in libfontmanager/freetypeScaler.c
  • 73e1780: 8349836: G1: Improve group prediction log message
  • ed17c55: 8349145: Make Class.getProtectionDomain() non-native
  • e700460: 8349813: Test behavior of limiting() on RS operators throwing exceptions
  • 08f4c1c: 8349781: make test TEST=gtest fails on WSL
  • bb41df4: 8349723: Problemlist jdp tests for macosx-x64
  • adda12b: 8349874: Missing comma in copyright from JDK-8349689
  • ... and 65 more: https://git.openjdk.org/jdk/compare/89e5e7ab73472b7d02aac5b8b0c7e9f26db6ec32...master

Your commit was automatically rebased without conflicts.

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

openjdk bot commented Feb 12, 2025

@archiecobbs Pushed as commit ba28119.

💡 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 integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

2 participants