typetools / typetools/checker-framework

Different behaviours of Classloading In Java 8 and 11 Implementation

Open
#3,282 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Java
Stars
1.1k
Forks
440
Avg merge
1d 12h
Merged PRs (30d)
134

Description

The classloading strategy for checkers could be different across our Java 8 and 11 implementations.

Observation: When running the tests, checker class will be reloaded in every test case if runtime is Java 8, while Java 11 has the correct behaviour -- each class will only be loaded once. This can be problematic since the static fields may not work properly under Java 8.

Details: https://github.com/opprop/checker-framework-inference/pull/263#issuecomment-618216146
https://github.com/typetools/checker-framework/pull/3268#issuecomment-620188231

I think the root cause is we are loading external javac by the bootstrap class loader in Java 8, while we are using the internal javac in Java 11.

If you check the file "com/sun/tools/javac/file/BaseFileManager.java", there's a method called getClassLoader(URL[] urls) which is used by "com/sun/tools/javac/file/JavacFileManager.java" to get the classloader for processor (checker).

In Java 8,
ClassLoader thisClassLoader = getClass().getClassLoader();
will assign null to the variable thisClassLoader, while in Java 11 the variable will be non-null. Citing the java documentation, "this method will return null in such implementations if this class was loaded by the bootstrap class loader."

thisClassLoader will be the parent of processor's classloader, and if it's null, processor's classloader, which is just instantiated using new URLClassLoader, has no idea if any classes were loaded before or not. This classloader will then reload everything it sees including Checkers and QualiferDefaults.

If you try running (new URLClassLoader(urls, thisClassLoader)).loadClass("ostrusted.OsTrustedChecker") in a debugger, it will always return a new class in Java 8, and the same class in Java 11.

I think this is a tricky difference between using the bootstrapclasspath and the internal library, and potentially it could cause more bugs in the future.

Suggestions:
The behaviour we expect is what Java 11 follows -- loading every class for only once in the JVM lifetime.

  1. If CF and CFI guarantee to run javac (i.e. TypecheckExecutor::compile) for only once in the JVM lifetime, then we don't need to care about this issue.
  2. If not, we can consider using the javac option procloader with a customized class loader.
  3. If 2 doesn't work, I think we'll have to hack the javac9+ library.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with TypecheckExecutor::compile and the referenced javac file-loading path, especially BaseFileManager.getClassLoader(URL[] urls) and JavacFileManager. Reproduce the checker-loading behavior under Java 8 and Java 11, then determine whether compilation runs once per JVM or requires a customized processor class loader. Done means checker classes are not unexpectedly reloaded under Java 8 and the behavior is covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.