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

Make sure that our multi-release jar actually lets people use module-scope @NullMarked #245

Closed
cpovirk opened this issue Apr 21, 2022 · 15 comments · Fixed by #246
Closed
Labels
dev development tasks, build issues...

Comments

@cpovirk
Copy link
Collaborator

cpovirk commented Apr 21, 2022

I had found that it works for plain javac invocations, and I believe eamonnmcmanus was the one who confirmed for us that it works with NetBeans. But we had seen a report that it doesn't work with IntelliJ, and we haven't tried Eclipse.

And now... I tried to use module-scope @NullMarked in a Gradle project that uses --release 11, and:

error: annotation type not applicable to this kind of declaration

That's happening even though I see a jspecify-0.2.0.jar (with what looks like the right contents) on the --module-path when I pass -d to Gradle.

[edit: Apparently kotlinc doesn't look under META-INF/versions, either. But that's mostly academic: Users won't need to write JSpecify annotations in Kotlin sources (except in rare cases until KT-47417 is implemented), and even if they do, they won't be putting an annotation on a module.]

[edit: I wonder what Turbine does....]

@cpovirk cpovirk added this to the 1.0 milestone Apr 21, 2022
@cpovirk
Copy link
Collaborator Author

cpovirk commented Apr 22, 2022

On the Gradle front, I found a bug that sound potentially related: gradle/gradle#19587. I also wasn't surprised to find that the Gradle people recommend that library authors instead use their mechanism for separate jars, the thing that I continually feel guilty for not enabling for Guava. Separate jars is certainly a possible fallback option, though Guava users are familiar with some of the resulting pain, and Gradle's work to eliminate that pain won't help users of other build systems.

Also, to be clear: @KengoTODA had added some tests to make sure that the right version of @NullMarked is selected at runtime. The problem I was seeing was at compile time.

And that problem was:

$ ./gradlew compileJava
...
/usr/local/google/home/cpovirk/clients/caffeine-black/caffeine/caffeine/src/main/java/module-info.java:1: error: annotation type not applicable to this kind of declaration
@org.jspecify.nullness.NullMarked
^
1 error
...

...when I tried to make this change to Caffeine.

@Sineaggi
Copy link

Sineaggi commented May 7, 2022

At least in a small repo that doesn't have much code, adding the @NullMarked annotation to the module works for me. Intellij doesn't like it, but that doesn't prevent me from compile and verifying via gradle run

System.out.println(Arrays.toString(Main.class.getModule().getAnnotations()));

that

[@org.jspecify.nullness.NullMarked()]

is printed as output.

@Sineaggi
Copy link

Sineaggi commented May 7, 2022

Also I grabbed the source (and generated sources) from caffeine, pulled them into a new project and was able to compile with the NullMarked annotation. Not sure why the upstream caffeine project can't.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 9, 2022

Thank you very much for checking that!

The Caffeine finding is still a little concerning: Whatever is going on with the Gradle configuration, clearly that particular compiler invocation is targeting Java 9 or higher, since it's compiling a module-info.

But it's great to know that a more vanilla configuration works: The problem isn't quite so bad, and we have an idea of where we could start looking for the problem.

@Sineaggi
Copy link

Sineaggi commented May 9, 2022

Perhaps there's a bug in javac? Or in how gradle invokes javac? I was able to get your branch to build by removing the
java.toolchain.languageVersion setting, which in turn seems to have replaced --release 11 with -target 11 -source 11.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 9, 2022

Ah, that's very interesting! I had been thinking of "targeting Java 9 or higher" as a single yes/no question. But in fact, --release 11 means not only -target 11 -source 11 (which is enough to permit compiling a module-info) but also something like -bootclasspath java11 -mrjarversiontoread 11. (Not that -mrjarversiontoread is a real flag, and not that "bootclasspath" is really what we mean nowadays for system modules(???).) That suggests a problem somewhere outside javac, whether in Gradle or in some Gradle extension or in this particular build script.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 9, 2022

(Sorry, my post is a bit cryptic -- trying to post quickly at the end of my day.)

@Sineaggi
Copy link

Sineaggi commented May 9, 2022

Fwiw, in my personal mini caffiene testing project, it's about as minimal as it gets, and I can reproduce the issue just by uncommenting the options.release setting in https://github.com/Sineaggi/caffeine-jspecify-testing/blob/main/build.gradle.kts

@Sineaggi
Copy link

Sineaggi commented May 9, 2022

whether in Gradle or in some Gradle extension or in this particular build script

Which might suggest it's not specific to caffiene's build script, but potentially in gradle itself.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 10, 2022

Ouch! At minimum, it is quite surprising that release seemingly does not set the --release flag, at least in some cases—especially given the docs here (found via gradle/gradle#2510). Maybe I'm reading too much into your results, but it certainly looks that way at first glance.

To be fair:

So, to recap, we're now seeing trouble in IntelliJ and sometimes Gradle, and I'm not sure we're getting anything out of it.

We may well still need our multi-compilation setup so that we can build Java 8 bytecode for our main classes while still building a module-info (which requires targeting Java 9+). But our attempt to have separate versions of NullMarked may be best reverted.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 10, 2022

No, wait, we still get the warning, at least in the testing I'm doing right now:

warning: unknown enum constant ElementType.MODULE

We may still have to live with that, given the alternative....

cpovirk added a commit to cpovirk/jspecify that referenced this issue May 10, 2022
…o` out.

Fixes jspecify#245. But introduces a
compile warning for users targeting old versions of Java, and introduces
runtime problems if a user tries to read _the annotations on
`@NullMarked` itself_.

Fixes jspecify#172. I didn't really
need to do this in the same commit. I just happened to get things
working that way first, and we've talked about at least trying this
change, so here goes....

It's clear that there's no great option for `MODULE`. But given that
we've already encountered problems in the wild with our old approach,
let's try this new approach instead.
cpovirk added a commit to cpovirk/jspecify that referenced this issue May 10, 2022
…o` out.

Fixes jspecify#245. But introduces a
compile warning for users targeting old versions of Java, and introduces
[runtime problems](https://bugs.openjdk.java.net/browse/JDK-8247797) if
a user tries to read _the annotations on `@NullMarked` itself_.

Fixes jspecify#172. I didn't really
need to do this in the same commit. I just happened to get things
working that way first, and we've talked about at least trying this
change, so here goes....

It's clear that there's no great option for `MODULE`. But given that
we've already encountered problems in the wild with our old approach,
let's try this new approach instead.
@cpovirk
Copy link
Collaborator Author

cpovirk commented May 10, 2022

I put together #246 to include MODULE unconditionally. That can lead to other issues, but my theory is that the result will be less bad on net than the current situation. Or at least I'd like for our next release to provide people the opportunity to test that theory.

If Java 8 users do have a problem with the artifact we release after #246, they may at least have the option of defining an annotation that @Implies the normal @NullMarked annotation but omits MODULE from its @Target. (Perhaps we'd even supply a standard such annotation ourselves.) But I should say something about @Implies and JDK-8247797 somewhere.... [edit: #218 (comment)]

@ExE-Boss
Copy link

I looked into javac code, and the current behaviour for unknown ElementType values is to treat the annotation as applicable in all possible locations1.

Similarly, an unknown enum constant in an annotation array value causes the whole array to be invalid23 when viewed using reflection.

Footnotes

  1. https://github.com/openjdk/jdk/blob/6586e5ae37e09a6d47f07758e710e1327e1c3be9/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java#L3438-L3439

  2. https://github.com/openjdk/jdk/blob/6586e5ae37e09a6d47f07758e710e1327e1c3be9/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java#L721-L727

  3. https://github.com/openjdk/jdk/blob/6586e5ae37e09a6d47f07758e710e1327e1c3be9/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java#L478-L483

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 12, 2022

Thanks.

javac's ElementType behavior apparently has some history behind it (JDK-8261610). We probably don't want to omit @Target from our annotation, but it's among our options, all of which have downsides :(

And the reflection source code suggests that the problem you likely already saw in https://github.com/jspecify/jspecify/pull/246/files#diff-6a22a6ede57062df5613483ebcc3fd278316078440bc35c7bf253ef49e89ee12R58 is still around even today.

cpovirk added a commit that referenced this issue May 13, 2022
…o` out.

(#246)

Fixes #245. But introduces a
compile warning for users targeting old versions of Java, and introduces
[runtime problems](https://bugs.openjdk.java.net/browse/JDK-8247797) if
a user tries to read _the annotations on `@NullMarked` itself_.

Fixes #172. I didn't really
need to do this in the same commit. I just happened to get things
working that way first, and we've talked about at least trying this
change, so here goes....

It's clear that there's no great option for `MODULE`. But given that
we've already encountered problems in the wild with our old approach,
let's try this new approach instead.
@kevinb9n kevinb9n added the dev development tasks, build issues... label Nov 30, 2022
@cpovirk
Copy link
Collaborator Author

cpovirk commented Mar 18, 2024

I was just tempted to summarize our findings here as "the idea seems to be that a compiler should be able to look at whichever version it wants." I probably still will do that, but I want to note a small caveat: We seem to be able to put module-info under the mrjar root (#484) and still rely on tools to see it. I do wonder if that will actually continue to be the case as we try more tools....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev development tasks, build issues...
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants