Bug 561227 - the temp class type generated for anonymous class in a static function is different with JDK javac
Summary: the temp class type generated for anonymous class in a static function is dif...
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.16   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords: needinfo
Depends on:
Blocks:
 
Reported: 2020-03-18 17:55 EDT by Wei Lao CLA
Modified: 2024-03-06 16:11 EST (History)
2 users (show)

See Also:


Attachments
FakingBaseTypesTest classes generated by JDT (19.17 KB, application/zip)
2020-03-18 23:49 EDT, Wei Lao CLA
no flags Details
FakingBaseTypesTest classes generated by JDK8 javac (18.98 KB, application/zip)
2020-03-18 23:50 EDT, Wei Lao CLA
no flags Details
FakingBaseTypesTest classes generated by JDK13 javac (19.08 KB, application/zip)
2020-03-19 08:04 EDT, Wei Lao CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Wei Lao CLA 2020-03-18 17:55:44 EDT
I try Maven Update Project to compile JMockit forced by Eclipse 2019-12 JDT, but after doing that, there are 3 failures of Junit Test,
By contrast,there are 0 failure after maven clean install which using javac.

I found one failure cause:
JDT took anonymous class in a static function as a normal class, but JDK8 javac took it as static final class.

for example:
JMockit test class snippet:
public final class FakingBaseTypesTest
{
   public static <T extends BeforeClassBaseAction> void applyFakeForAllTests() {
      new MockUp<T>() {
         @Mock int perform() { return 34; }
      };
   } 
}

decompile temp class generated by JDK8 javac:
static final class FakingBaseTypesTest
extends MockUp<T> {
    FakingBaseTypesTest() {
    }

    @Mock
    int perform() {
        return 34;
    }
}

decompile temp class generated by JDT (Eclipse 2019-12)
class FakingBaseTypesTest
extends MockUp<T> {
    FakingBaseTypesTest() {
    }

    @Mock
    int perform() {
        return 34;
    }
}

github repo: https://github.com/jmockit/jmockit1
Eclipse: 2019-12
JDK: 1.8.0_241
OS: PC Win7 x64
Comment 1 Stephan Herrmann CLA 2020-03-18 18:45:05 EDT
I cannot reproduce the class-file difference you describe. For me both compilers clearly mark FakingBaseTypesTest.class as public final:

$ javap -p -v -c main/target/test-classes/mockit/FakingBaseTypesTest.class
  Classfile main/target/test-classes/mockit/FakingBaseTypesTest.class
  Last modified Mar 18, 2020; size 7037 bytes
  MD5 checksum 6c708a6d959e2bdd30c8c1232d0096d7
  Compiled from "FakingBaseTypesTest.java"
public final class mockit.FakingBaseTypesTest
  minor version: 0
  major version: 51
  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
...

To ensure we are looking at the same thing you may want to attach the class files from your disk (from both compilers) and mention the command line used for decompiling. I showed what I used above.


Still I *am* able to reproduce a test failure in this class after modifying the compiler section in your pom:

     <plugin>
        <artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version>
        <configuration>
           <encoding>UTF-8</encoding>
           <source>1.7</source><target>1.7</target>
           <compilerId>eclipse</compilerId>
           <useIncrementalCompilation>false</useIncrementalCompilation>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>org.codehaus.plexus</groupId>
                <artifactId>plexus-compiler-eclipse</artifactId>
                <version>2.8.6</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt</groupId>
                <artifactId>ecj</artifactId>
                <version>3.20.0</version>
            </dependency>
        </dependencies>
     </plugin>

Please understand that I'm unable to analyse the test failure as I have no idea about what it is testing.

As this is about mocking and probably involves some byte code modifications behind the scenes, I could imagine that some byte code transformer makes specific assumptions about specific structurs in the byte code, which are however unspecified in JVMS, i.e., to be considered an arbitrary implementation detail of compilers.


I suggest you debug the test failure to the point where you can blame the difference to a particular line of code. Then we can look together at the byte codes for this particular line of code.
Comment 2 Wei Lao CLA 2020-03-18 23:49:54 EDT
Created attachment 282133 [details]
FakingBaseTypesTest classes generated by JDT
Comment 3 Wei Lao CLA 2020-03-18 23:50:25 EDT
Created attachment 282134 [details]
FakingBaseTypesTest classes generated by JDK8 javac
Comment 4 Wei Lao CLA 2020-03-18 23:53:02 EDT
one JUnit Test failure case with JDT:
https://github.com/jmockit/jmockit1/blob/master/main/test/mockit/FakingBaseTypesTest.java
...
   @BeforeClass
   public static <T extends BeforeClassBaseAction> void applyFakeForAllTests() {
      new MockUp<T>() { // <=== generate as FakingBaseTypesTest$1.class
         @Mock int perform() { return 34; }
      };
   }
...

JDT: 
first maven update, 
then right click on FakingBaseTypesTest,  Run As JUnit Test with VM arguments:  -javaagent:../agent.jar
the JDT will gernerate FakingBaseTypesTest$1.class, FakingBaseTypesTest$2.class, ... at main\target\test-classes\mockit
  
$ javap -p -v -c FakingBaseTypesTest$1.class
Classfile /F:/Temp/jdt/mockit/FakingBaseTypesTest$1.class
  Last modified 2020-3-17; size 676 bytes
  MD5 checksum fb568dafe3e9e06c156115354c642555
  Compiled from "FakingBaseTypesTest.java"
class mockit.FakingBaseTypesTest$1 extends mockit.MockUp<T>
  minor version: 0
  major version: 51
  flags: ACC_SUPER
...

Javac:
first maven clean install and Skip Testsnot not checked
then javac will gernerate FakingBaseTypesTest$1.class, FakingBaseTypesTest$2.class, ... at main\target\test-classes\mockit
right click FakingBaseTypesTest to Run As JUnit Test with VM arguments:  -javaagent:../agent.jar, the test will be all ok. 
$ javap -p -v -c FakingBaseTypesTest$1.class
Classfile /F:/Temp/javac/mockit/FakingBaseTypesTest$1.class
  Last modified 2020-3-17; size 590 bytes
  MD5 checksum 1fd5c8a9c961ca1ae28be0a4743a4fc4
  Compiled from "FakingBaseTypesTest.java"
final class mockit.FakingBaseTypesTest$1 extends mockit.MockUp<T>
  minor version: 0
  major version: 51
  flags: ACC_FINAL, ACC_SUPER
Comment 5 Stephan Herrmann CLA 2020-03-19 06:24:36 EDT
Thanks, the picture is much clearer now.

So the question to answer is: should a class file for an anonymous class have the ACC_FINAL modifier set, or not?

This question has a shady history. IIUC in Java 8 JLS considered anonymous classes as implicitly final (without saying whether or not this should be reflected in the class file). At some point JLS said:
    An anonymous class is always implicitly final (8.1.1.2). 

I believe the final :) word on this was said in
   https://bugs.openjdk.java.net/browse/JDK-8161009
Where it was stated that
 - javac illegally omitted the final modifier from class files
 - javac could not be corrected to comply with JLS for compatibility reasons
 - it was therefor decided to achieve spec compliance by changing the spec
This "spec bug" was resolved for Java 9.

Normally, whenever JLS is adjusted to match javac (rather than the opposite) this is very bad news for another implementation of the same spec (here: ecj), but it seems that in this particular case ecj has always been bug-compatible with javac.

Unfortunately, I couldn't yet figure out why in your particular case the final modifier *is* generated by javac. Is it that particular version 1.8.0_241? Is something special about this particular program causing javac to omit different code than it normally does?

Meanwhile you could try compiling your test with javac from JDK 9. I would expect it to fail for the same reason as when using ecj. If so, that would indicate that your test expectation (or the implementation) is wrong, at least for current versions of Java.
Comment 6 Wei Lao CLA 2020-03-19 08:04:27 EDT
Created attachment 282144 [details]
FakingBaseTypesTest classes generated by JDK13 javac
Comment 7 Wei Lao CLA 2020-03-19 10:15:39 EDT
The JDK1.8.0_241 i used is downloaded from Oracle JDK site, so i don't think it's a special version.

I tested it on JDK 13.0.2 ( download from Oracle JDK site ):

1. set Eclipse installed JRE default to JDK 13.0.2
2. change JRE of the build path from JavaSE1.7(jdk1.8.0_241) to JavaSE1.7(jdk13.0.2)
3. bypass maven test phase by adding -DskipTests in maven build goals:
 maven clean install -e -DskipTests
(Jacoco plugin JMockit used don't support JDK 13, so have to bypass test phase)
4. check the result of javap:
$ javap -p -v -c FakingBaseTypesTest$1.class
Classfile /F:/Temp/javac13/mockit/FakingBaseTypesTest$1.class
  Last modified 2020-3-19; size 590 bytes
  MD5 checksum 3e802742eb65cfabe0f90001ec104535
  Compiled from "FakingBaseTypesTest.java"
class mockit.FakingBaseTypesTest$1 extends mockit.MockUp<T>
  minor version: 0
  major version: 51
  flags: ACC_SUPER
...
you are right, the final modifier is gone
5. right click on FakingBaseTypesTest and Run as JUnit test, still OK!
6. maven update project, force JDT to generate classes for JUnit Test
7. right click on FakingBaseTypesTest and Run as JUnit test, Surprise! the test become to OK! 
8. run project JUnit Test, there are still 2 failures:
mockEnumWithValueSpecificMethods of Class MockedEnumsTest,
captureArgumentsIntoListInExpectationBlock of Class WithCaptureTest.
9. gernerate the project by javac and show the option of javac: maven clean install -e -X -DskipTests 
the options like this:
-d test-classes-path -classpath test-classes-path;classes-path;maven-dependcy-jars  -sourcepath test-source-path -g -nowarn -target 1.7 -source 1.7 -encoding UTF-8 -Xlint:none  test-sources-list
10. run project JUnit Test, there are 1015 tests ok and 0 failures.

Our project uses JMockit to mock some classes for the project testing, and don't known the details of it's internal mechanism.    
I'm just curious to find the reason of JUnit Test difference between JDT compiling and javac compiling.
Comment 8 Wei Lao CLA 2020-03-20 00:47:02 EDT
another strange behavior is JDT choice moment of compiling.
if JRE of project build path changed, JDT will re-generate all the classes again no matter that the classes are already built by maven install, so if i want run JUnit Test with classes generated by javac, i had to maven install again.
Comment 9 Wei Lao CLA 2020-03-20 01:03:58 EDT
Supplementary Note:
All JUnit Tests of JMockit should add -javaagent:../agent.jar in VM arguments.
Comment 10 Eclipse Genie CLA 2022-03-11 16:52:45 EST
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.
Comment 11 Eclipse Genie CLA 2024-03-06 16:11:19 EST
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.