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

Upstreaming: Not inheriting annotations, somehow avoiding stub-file recursion #11

Open
cpovirk opened this issue Nov 23, 2020 · 7 comments
Assignees
Labels
enhancement New feature or request

Comments

@cpovirk
Copy link
Collaborator

cpovirk commented Nov 23, 2020

f3e9f21

Note that that links to jspecify/jspecify-reference-checker@2bab27e, which describes another problem solved by that commit. It doesn't seem that "not inheriting annotations" is necessarily a sufficient fix for that problem in general, but it works here. More to the point, if we someday restore inheriting annotations, we'll still need a way to avoid the recursion. (The linked commit does discuss some other options.)

cpovirk added a commit to cpovirk/jspecify-reference-checker that referenced this issue Mar 11, 2021
The infinite recursion is almost certainly caused by the hacky changes
I made a while back and described in
jspecify/checker-framework#11

The repeating part of the stack is:

```
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getAnnotatedType(AnnotatedTypeFactory.java:2875)
      	at org.checkerframework.framework.stub.AnnotationFileParser.processFakeOverride(AnnotationFileParser.java:1853)
      	at org.checkerframework.framework.stub.AnnotationFileParser.processTypeDecl(AnnotationFileParser.java:860)
      	at org.checkerframework.framework.stub.AnnotationFileParser.processCompilationUnit(AnnotationFileParser.java:685)
      	at org.checkerframework.framework.stub.AnnotationFileParser.processStubUnit(AnnotationFileParser.java:661)
      	at org.checkerframework.framework.stub.AnnotationFileParser.process(AnnotationFileParser.java:650)
      	at org.checkerframework.framework.stub.AnnotationFileParser.parseStubFile(AnnotationFileParser.java:604)
      	at org.checkerframework.framework.stub.AnnotationFileParser.parseJdkFileAsStub(AnnotationFileParser.java:580)
      	at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseJarEntry(AnnotationFileElementTypes.java:575)
      	at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseEnclosingClass(AnnotationFileElementTypes.java:502)
      	at org.checkerframework.framework.stub.AnnotationFileElementTypes.getDeclAnnotation(AnnotationFileElementTypes.java:374)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotations(AnnotatedTypeFactory.java:3703)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotation(AnnotatedTypeFactory.java:3631)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotation(AnnotatedTypeFactory.java:3553)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getSupportedAnnotationsInElementAnnotation(AnnotatedTypeFactory.java:4108)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getQualifierParameterHierarchies(AnnotatedTypeFactory.java:4066)
      	at org.checkerframework.framework.type.GenericAnnotatedTypeFactory.applyQualifierParameterDefaults(GenericAnnotatedTypeFactory.java:1889)
      	at org.checkerframework.framework.type.GenericAnnotatedTypeFactory.addComputedTypeAnnotations(GenericAnnotatedTypeFactory.java:1984)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getAnnotatedType(AnnotatedTypeFactory.java:1092)
      	at org.checkerframework.framework.type.AnnotatedTypeFactory.getAnnotatedType(AnnotatedTypeFactory.java:2875)
```
@cpovirk
Copy link
Collaborator Author

cpovirk commented Mar 11, 2021

And I forgot to link 719b6f0, which came right after f3e9f21. Note that, after a rename upstream, that code is now in AnnotationFileElementTypes.

cpovirk pushed a commit to jspecify/jdk that referenced this issue Mar 12, 2021
---

cpovirk adds:

This turns out to be very important but only in pseudorandom cases.

JavaStubifier sometimes fails to identify java.base/share/classes as a
source root, and thus it chooses not to stubify the files under this
directory. The reason that it sometimes fails to identify the directory
as a source root is the invalid module-info file.

But sometimes, even though the invalid module-info file is processed and
rejected, JavaStubifier still identifies the directory as a source root,
and thus it chooses to stubify the files under the directory.

The difference seems to be the order in which the filesystem returns the
contents of that directory. As it happens, my root filesystem (where my
/tmp is) has been consistently returning the contents in a bad order,
while my larger filesystem (where my home directory is) has been
consistently returning the contents in a good order.

(GitHub Actions, too, has been consistently seen the contents in a bad
order. See
jspecify/jspecify-reference-checker#9
for some false starts on my investigation of that. I may still end up
wanting to commit part of that change -- the part that turns
`applyQualifierParameterDefaults` into a no-op -- depending on what
happens after this fix.)

Presumably, if we see module-info before we see a file that we can
"usefully parse," we skip scanning more files under the directory. Or
something like that. I haven't dug into this deeply, but I assume the
relevant logic is the values returned from the `FileVisitor`
implementation in `ParserCollectionStrategy`, e.g.,
https://github.com/typetools/stubparser/blob/25e36946755b040d23c0af8f4b7bd2bc0fcf4042/javaparser-core/src/main/java/com/github/javaparser/utils/ParserCollectionStrategy.java#L84

(By "usefully parse," I'm not sure exactly what I mean. But it's
something like "Can we get enough information out of it to identify the
source root -- typically, a package name that matches the directory
hierarchy?" However, even under the "bad order" I was seeing, I would
have thought we would still have gotten that far, since I'm seeing a
normal-looking .java file processed before module-info. I'd need to dig
more to understand exactly what's happening.)

Anyway, the result is that we fail to call `addSourceRoot` on the way
out:
https://github.com/typetools/stubparser/blob/25e36946755b040d23c0af8f4b7bd2bc0fcf4042/javaparser-core/src/main/java/com/github/javaparser/utils/ParserCollectionStrategy.java#L92

(Previously I had a theory that the cause was my directory names: Under
/tmp, I was using directories with a period in the name. I thought that
maybe JavaParser was rejecting this as an invalid package name or trying
to treat it as a module name or something. I don't _think_ the names
were actually relevant, but I haven't tested further.)

To debug this, I found it useful to add a `Log.info` to
`CollectionStrategy.getRoot` and also to change logging to use
stdout/stderr. To do the latter, I modified the `SourceRoot` constructor
to call `Log.setAdapter(new Log.StandardOutStandardErrorAdapter());`.
(Probably I could have found a better place to do that, but it was good
enough this time.)

So why did any of _that_ matter? Because, if JavaStubifier doesn't run
on java.base/share/classes, we end up with sources that contain method
bodies, private members, etc. In principle, I think that ought to be
fine. However, at least one of those extras turns out to be trouble:
`AnnotationFileParser.sameType` does not expect `INTERSECTION`:
https://github.com/jspecify/checker-framework/blob/5ba2cd26b470a1dacb2cd6a80829d137e80b3a7a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java#L1838

(In an ideal world, I will minimize this and file a bug. But I probably
won't anytime soon. It's entirely possible that the problem arises only
because of other questionable things that our hacky checker is doing.)

Thus, I see exceptions when I run the checker -- though only on _some_
code, it seems :)

I was having trouble even seeing those exceptions, though: My checker
runs were using up all RAM that the container made available, so they
were getting killed. The problem was that
`AnnotationFileElementTypes.parseJarEntry` was wrapping the original
`BugInCF` instance with an instance that added the name of the stub file
it was processing. That, too, would be fine, but we parse stub files
recursively, which had already caused us problems in the past
(jspecify/checker-framework#11). As a result,
`parseJarEntry` was on the stack multiple times, and each of those
frames was making the exception message longer and longer until we
couldn't allocate any more:
https://github.com/jspecify/checker-framework/blob/5ba2cd26b470a1dacb2cd6a80829d137e80b3a7a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java#L584

I had some luck avoiding this by running the checker myself locally
instead of under GitHub Actions or our usual Google build farm. I'm not
sure if that just made more memory available or if it let me see some
streamed output that would otherwise have been lost or what.

(In hindsight, I may have found it useful to just change that `throw new
BugInCF` line to rethrow `e` directly. Then again, I had tried throwing
an `AssertionError` wrapper instead, and that would probably have
worked, too, if only the tests hadn't started _succeeding_ then because
of the "good order" / "bad order" issue!)

---

`AnnotationFileParser.sameType` does not expect `INTERSECTION`:

```
Exception while parsing annotated-jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java: Unhandled type java.lang.Object&java.lang.Comparable<? super T> of kind INTERSECTION
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseJarEntry(AnnotationFileElementTypes.java:584)
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseEnclosingClass(AnnotationFileElementTypes.java:502)
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.getDeclAnnotation(AnnotationFileElementTypes.java:374)
        at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotations(AnnotatedTypeFactory.java:3703)
        at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotation(AnnotatedTypeFactory.java:3631)
        at org.checkerframework.framework.type.AnnotatedTypeFactory.getDeclAnnotation(AnnotatedTypeFactory.java:3553)
        at com.google.jspecify.nullness.NullSpecAnnotatedTypeFactory.hasNullAwareOrEquivalent(NullSpecAnnotatedTypeFactory.java:1416)
        at com.google.jspecify.nullness.NullSpecAnnotatedTypeFactory.access$800(NullSpecAnnotatedTypeFactory.java:116)
        at com.google.jspecify.nullness.NullSpecAnnotatedTypeFactory$NullSpecQualifierDefaults.populateNewDefaults(NullSpecAnnotatedTypeFactory.java:777)
        at org.checkerframework.framework.util.defaults.QualifierDefaults.defaultsAt(QualifierDefaults.java:629)
        at org.checkerframework.framework.util.defaults.QualifierDefaults.applyDefaultsElement(QualifierDefaults.java:695)
        at org.checkerframework.framework.util.defaults.QualifierDefaults.annotate(QualifierDefaults.java:405)
        at org.checkerframework.framework.type.GenericAnnotatedTypeFactory.addComputedTypeAnnotations(GenericAnnotatedTypeFactory.java:1986)
        at org.checkerframework.framework.type.GenericAnnotatedTypeFactory.postDirectSuperTypes(GenericAnnotatedTypeFactory.java:854)
        at org.checkerframework.framework.type.SupertypeFinder.directSupertypes(SupertypeFinder.java:55)
        at org.checkerframework.framework.type.AnnotatedTypeMirror$AnnotatedDeclaredType.directSupertypes(AnnotatedTypeMirror.java:974)
        at org.checkerframework.framework.stub.AnnotationFileParser.annotateSupertypes(AnnotationFileParser.java:989)
        at org.checkerframework.framework.stub.AnnotationFileParser.processType(AnnotationFileParser.java:935)
        at org.checkerframework.framework.stub.AnnotationFileParser.processTypeDecl(AnnotationFileParser.java:813)
        at org.checkerframework.framework.stub.AnnotationFileParser.processCompilationUnit(AnnotationFileParser.java:685)
        at org.checkerframework.framework.stub.AnnotationFileParser.processStubUnit(AnnotationFileParser.java:661)
        at org.checkerframework.framework.stub.AnnotationFileParser.process(AnnotationFileParser.java:650)
        at org.checkerframework.framework.stub.AnnotationFileParser.parseStubFile(AnnotationFileParser.java:604)
        at org.checkerframework.framework.stub.AnnotationFileParser.parseJdkFileAsStub(AnnotationFileParser.java:580)
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseJarEntry(AnnotationFileElementTypes.java:575)
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.parseEnclosingClass(AnnotationFileElementTypes.java:502)
        at org.checkerframework.framework.stub.AnnotationFileElementTypes.getAnnotatedTypeMirror(AnnotationFileElementTypes.java:354)
        at org.checkerframework.framework.type.AnnotatedTypeFactory.fromElement(AnnotatedTypeFactory.java:1280)
        at org.checkerframework.framework.type.AnnotatedTypeFactory.fromElement(AnnotatedTypeFactory.java:2884)
        at org.checkerframework.common.basetype.BaseTypeVisitor.<init>(BaseTypeVisitor.java:242)
        at org.checkerframework.common.basetype.BaseTypeVisitor.<init>(BaseTypeVisitor.java:223)
        at com.google.jspecify.nullness.NullSpecVisitor.<init>(NullSpecVisitor.java:91)
        at com.google.jspecify.nullness.NullSpecChecker.createSourceVisitor(NullSpecChecker.java:49)
        at com.google.jspecify.nullness.NullSpecChecker.createSourceVisitor(NullSpecChecker.java:36)
        at org.checkerframework.framework.source.SourceChecker.initChecker(SourceChecker.java:878)
        at org.checkerframework.common.basetype.BaseTypeChecker.initChecker(BaseTypeChecker.java:112)
        at org.checkerframework.framework.source.SourceChecker.typeProcessingStart(SourceChecker.java:829)
        at org.checkerframework.javacutil.AbstractTypeProcessor$AttributionTaskListener.finished(AbstractTypeProcessor.java:167)
        at jdk.compiler/com.sun.tools.javac.api.ClientCodeWrapper$WrappedTaskListener.finished(ClientCodeWrapper.java:828)
        at jdk.compiler/com.sun.tools.javac.api.MultiTaskListener.finished(MultiTaskListener.java:132)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1394)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1351)
        at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:947)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:311)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:170)
        at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:57)
        at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:43)
```

---

"Bad order":

```
New source root at "/tmp/tmp.hMPBYGuMAr/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/unix/classes"
trying /tmp/tmp.hMPBYGuMAr/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes/jdk/internal/module/ModuleReferenceImpl.java
trying /tmp/tmp.hMPBYGuMAr/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes/module-info.java
Parsing was not successful.
There were (1) problems parsing file: [(line 341,col 20) Parse error. Found "exports", expected one of  "," ";"
Problem stacktrace :
  com.github.javaparser.GeneratedJavaParser.generateParseException(GeneratedJavaParser.java:13115)
  com.github.javaparser.GeneratedJavaParser.jj_consume_token(GeneratedJavaParser.java:12961)
  com.github.javaparser.GeneratedJavaParser.ModuleDirective(GeneratedJavaParser.java:7745)
  com.github.javaparser.GeneratedJavaParser.ModuleDeclaration(GeneratedJavaParser.java:7865)
  com.github.javaparser.GeneratedJavaParser.CompilationUnit(GeneratedJavaParser.java:377)
  com.github.javaparser.JavaParser.parse(JavaParser.java:124)
  com.github.javaparser.JavaParser.parse(JavaParser.java:307)
  com.github.javaparser.utils.CollectionStrategy.getRoot(CollectionStrategy.java:50)
  com.github.javaparser.utils.ParserCollectionStrategy$1.visitFile(ParserCollectionStrategy.java:73)
  com.github.javaparser.utils.ParserCollectionStrategy$1.visitFile(ParserCollectionStrategy.java:66)
  java.base/java.nio.file.Files.walkFileTree(Files.java:2726)
  java.base/java.nio.file.Files.walkFileTree(Files.java:2798)
  com.github.javaparser.utils.ParserCollectionStrategy.collect(ParserCollectionStrategy.java:66)
  org.checkerframework.framework.stub.JavaStubifier.process(JavaStubifier.java:74)
  org.checkerframework.framework.stub.JavaStubifier.main(JavaStubifier.java:57)]
trying /tmp/tmp.hMPBYGuMAr/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/windows/classes/sun/nio/ch/FileKey.java
```

---

"Good order":

```
New source root at "/usr/local/google/home/cpovirk/clients/nullness-checker-for-checker-framework-everything/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/unix/classes"
trying /usr/local/google/home/cpovirk/clients/nullness-checker-for-checker-framework-everything/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes/java/security/AlgorithmParametersSpi.java
trying /usr/local/google/home/cpovirk/clients/nullness-checker-for-checker-framework-everything/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes/module-info.java
Parsing was not successful.
There were (1) problems parsing file: [(line 341,col 20) Parse error. Found "exports", expected one of  "," ";"
Problem stacktrace :
  com.github.javaparser.GeneratedJavaParser.generateParseException(GeneratedJavaParser.java:13115)
  com.github.javaparser.GeneratedJavaParser.jj_consume_token(GeneratedJavaParser.java:12961)
  com.github.javaparser.GeneratedJavaParser.ModuleDirective(GeneratedJavaParser.java:7745)
  com.github.javaparser.GeneratedJavaParser.ModuleDeclaration(GeneratedJavaParser.java:7865)
  com.github.javaparser.GeneratedJavaParser.CompilationUnit(GeneratedJavaParser.java:377)
  com.github.javaparser.JavaParser.parse(JavaParser.java:124)
  com.github.javaparser.JavaParser.parse(JavaParser.java:307)
  com.github.javaparser.utils.CollectionStrategy.getRoot(CollectionStrategy.java:50)
  com.github.javaparser.utils.ParserCollectionStrategy$1.visitFile(ParserCollectionStrategy.java:73)
  com.github.javaparser.utils.ParserCollectionStrategy$1.visitFile(ParserCollectionStrategy.java:66)
  java.base/java.nio.file.Files.walkFileTree(Files.java:2726)
  java.base/java.nio.file.Files.walkFileTree(Files.java:2798)
  com.github.javaparser.utils.ParserCollectionStrategy.collect(ParserCollectionStrategy.java:66)
  org.checkerframework.framework.stub.JavaStubifier.process(JavaStubifier.java:74)
  org.checkerframework.framework.stub.JavaStubifier.main(JavaStubifier.java:57)]
trying /usr/local/google/home/cpovirk/clients/nullness-checker-for-checker-framework-everything/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/Instruction.java
New source root at "/usr/local/google/home/cpovirk/clients/nullness-checker-for-checker-framework-everything/checker-framework/framework/build/generated/resources/annotated-jdk/src/java.base/share/classes"
```
@cpovirk
Copy link
Collaborator Author

cpovirk commented Mar 12, 2021

Recursion (deep, though I don't think actual infinite) contributed to complicating my investigation of jspecify/jdk@fdf7954.

@cpovirk
Copy link
Collaborator Author

cpovirk commented Jan 17, 2023

I think netdpb may have noted at one point that there was some particular API that all checkers look up from their constructors, and that may be the cause of some infinite recursion. But I'm not sure what the API is, and I haven't looked into how easy it would be to avoid (though I'm hoping "not hard"), and I don't know if it's the main problem here (notably, I still don't know if the recursion is actually infinite or just keep) or if the problem would just resurface whenever we actually have to parse the stub files later.

We're hoping that we can sidestep this by reducing our need for reading stub files, but we'll see how that goes.

@cpovirk cpovirk closed this as completed Feb 1, 2023
@cpovirk cpovirk reopened this Feb 1, 2023
@cpovirk
Copy link
Collaborator Author

cpovirk commented Feb 1, 2023

It's also possible that typetools PR 5581 would be of help here. Again, though, we hope to move away from stub files enough that we no longer have to worry about this.

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 15, 2023

  • I was seeing more recursion (and later NPE) from stub-file parsing while looking at internal bug 202004399.
  • eisop redid some of the code in this area. Maybe it will help us.

@wmdietl
Copy link
Collaborator

wmdietl commented Feb 6, 2024

eisop/checker-framework#305 and eisop/checker-framework#269 fixed some stub file problems.
Do you have test cases that will allow us to reproduce this issue?

Regarding inheriting decl annos: I see no problem making the methods you disable in xxx protected to allow changing the behavior in a checker.
Do you remember whether there is a test for this issue?

@cpovirk
Copy link
Collaborator Author

cpovirk commented Feb 6, 2024

(dodging the questions about test cases until later in this post...)

The first bit of good news is that we turned off the use of stubs entirely (JDK and otherwise) for our integration of the JSpecify checker inside Google. So the use of stubs isn't necessary for that particular use case, which I think is where many of the problems were discovered.

It's also possible that some of the problems come up only in code that makes "significant use" of JDK classes. If so, the problems might not come up in the existing samples directory (especially the copy in the main branch) or the new conformance tests that are being filled in. Or, if they do come up, we might be able to make them go away without significant side effects by simply disabling the JDK stubs for our test runs.

Now, if an average person wants to try the reference checker, then that person is likely to want the JDK stubs. So we might not be able to just give up on stubs entirely. But... maybe we could for now? In the longer term, maybe we'll find a way to let external users put annotations into their JDK APIs more directly. And today, it's not as if we recommend using the reference checker for more than experimentation. Granted, it would be nice if experimentation with JDK APIs were to work as expected....

I guess that my suggestion would be to back out all these changes and see what happens with the existing tests. That's probably enough testing: If they work, we're probably fine. Or at least that's enough to put the ball back in my court to test things inside Google.

As for a test for inheriting annotations: I doubt there's a test specifically for that: I don't think we recognize any of the inherited annotations, especially because we treat everything as @Pure. So I think my real motivation for disabling them was the second problem listed in f3e9f21. The part about "but we don't actually want inheritance" was probably more of a reason to feel OK about making the change to fix the "real" problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants