JDK-8134329 : TeeOpTest.java fails across platforms after fix for JDK-8129547
Type:Bug
Component:core-libs
Sub-Component:java.util.stream
Affected Version:9
Priority:P2
Status:Closed
Resolution:Fixed
Submitted:2015-08-24
Updated:2016-08-24
Resolved:2015-08-25
The Version table provides details related to the release that this issue/RFE will be addressed.
Unresolved : Release in which this issue/RFE will be addressed. Resolved: Release in which this issue/RFE has been resolved. Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.
After the fix for JDK-8129547 was pushed, the test
java/util/stream/test/org/openjdk/tests/java/util/stream/TeeOpTest.java
fails across platforms.
Comments
Thanks for analysis, Maurizio. Yes, my fix seems to produce a proper BSM call for your testcase:
43: dup
44: invokestatic #4 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
47: pop
48: invokedynamic #11, 0 // InvokeDynamic #0:run:(LTest$3;)Ljava/lang/Runnable; <----- OKAY, references m()
53: astore_2
54: return
BootstrapMethods:
0: #29 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#30 ()V
#31 invokevirtual Test.m:()V
#30 ()V
25-08-2015
The failure is more nuanced than I was describing - in reality the root cause is that the existing logic was based on the assumption that BSM keys would have same hash/equal properties as their invokedynamic counterparts; under than assumption, the old logic was just fine. But nowe we have a new BSM key class, with different hash/equals properties than that of the invokedynamic entry pointing to it; so we can't safely assume that a new invokedynamic entry will always result in a new BSM key. More specifically, in cases where static aruments are similarly shaped, but dynamic arguments differ, we get a new invokedynamic entry, pointing to an old, shared BSM key. In this case the indexing logic is bogus, as shown by the following code:
class Test {
static void test() {
Runnable r1 = new Test() { }::m;
Runnable r2 = new Test() { }::g;
Runnable r3 = new Test() { }::m;
}
void m() { }
void g() { }
}
Which gives (after the fix for 8129547) the following result:
static void test();
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=3, args_size=0
0: new #2 // class Test$1
3: dup
4: invokespecial #3 // Method Test$1."<init>":()V
7: dup
8: invokestatic #4 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
11: pop
12: invokedynamic #5, 0 // InvokeDynamic #0:run:(LTest$1;)Ljava/lang/Runnable;
17: astore_0
18: new #6 // class Test$2
21: dup
22: invokespecial #7 // Method Test$2."<init>":()V
25: dup
26: invokestatic #4 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
29: pop
30: invokedynamic #8, 0 // InvokeDynamic #1:run:(LTest$2;)Ljava/lang/Runnable;
35: astore_1
36: new #9 // class Test$3
39: dup
40: invokespecial #10 // Method Test$3."<init>":()V
43: dup
44: invokestatic #4 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
47: pop
48: invokedynamic #11, 0 // InvokeDynamic #1:run:(LTest$3;)Ljava/lang/Runnable; <-------------------------------------- BOGUS
53: astore_2
54: return
Where:
BootstrapMethods:
0: #29 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#30 ()V
#31 invokevirtual Test.m:()V
#30 ()V
1: #29 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#30 ()V
#34 invokevirtual Test.g:()V
#30 ()V
Fix is to either revert to old behavior, or to go with the new fix by Alex.
25-08-2015
Thanks Maurizio, you're right -- the problem is with BSM indexes. I'd suggest a cleaner fix, see 8134329-poc-fix.patch. It passes the offending TeeOpTest now.
25-08-2015
Seems to me that the bug is here:
poolbuf.appendByte(CONSTANT_InvokeDynamic);
poolbuf.appendChar(bootstrapMethods.size() - 1);
poolbuf.appendChar(pool.put(nameType(dynSym)));
ClassWriter.writePool seems to erroneously assume that the indy index in the bsm array should always point to the latest BSM slot - if there are no duplicates this works out ok - but with duplicate this leads to errors - the code should recover whatever index has been associated previously with a duplicate entry and use that instead (where available). In other words:
DynamicMethod.BootstrapMethodsKey key = new DynamicMethod.BootstrapMethodsKey(dynSym, types);
boolean added = bootstrapMethods.put(key, handle);
final int index;
if (added) {
index = bootstrapMethods.size() -1;
} else {
int i = 0;
for (BootstrapMethodKey key2 : bootstrapMethods.entrySet()) {
if (key.equals(key2)) break;
i++;
}
index = key2;
}
25-08-2015
I've analysed the differences a bit more - I'm not sure this is a bug with BSM keys - generated code seems correct.
All the new version does is removing all the duplicate entries for the 'before'/'after' MHs - that is, the following entries in the original code are merged:
5: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#127 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.before:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
6: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#132 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.after:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
8: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#127 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.before:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
9: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#132 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.after:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
11: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#127 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.before:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
12: #105 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#106 (Ljava/lang/Object;)V
#132 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.after:(Ljava/util/stream/TestData;)V
#128 (Ljava/util/stream/TestData;)V
So, entries { 5,6 8, 9, 11, 12 } are all identical and are dropped in the new bytecode - this means that we go from 12 to 6 entries, as previously stated. But there's no erroneous merging here.
That said - the differences in the indy call themselves look suspicious - for instance this:
- 29: invokedynamic #33, 0 // InvokeDynamic #8:accept:(Lorg/openjdk/tests/java/util/stream/TeeOpTest$3RecordingConsumer;)Ljava/util/function/Consumer;
+ 29: invokedynamic #33, 0 // InvokeDynamic #5:accept:(Lorg/openjdk/tests/java/util/stream/TeeOpTest$3RecordingConsumer;)Ljava/util/function/Consumer;
This is odd as #8 (in the old code) and #5 (in the new code) map to completely unrelated BSM entries.
25-08-2015
One thing is not clear - it seems to me that the current equals/hash routines for BSM key do take into account all static arg values; the current logic only special cases those values that happen to be types (i.e. MethodType). But for non-type values, the value is preserved in the uniqueStaticArgs array - and that array is used to perform equality and hash - so it seems like it should work? In the case you mention, the difference seems to involve a method handle - which is an instance of the Pool.MethodHandle class - eyeballing this class it seems like it implements equals/hash correctly as well?
25-08-2015
Hence, I believe the bug is present in javac even before JDK-8129547, since BSM key included both static arg and dynamic arg types before, and we dropped the dynamic arg types. It would seem JDK-8129547 exposed this bug to more conventional cases of similarly-shaped indy calls. It seems TeeOpTest's lambdas differ enough in their dynamic args. This means blindly reverting JDK-8129547 is not a complete solution, and we need to rewire javac's DynamicMethod.BootstrapMethodsKey to handle the static arg *values*.
24-08-2015
Okay, I see the problem: BSMs are apparently getting glued together based on static argument *types*, not the static argument *values*. See:
10: #105 invokestatic <LMF>
Method arguments:
...
#146 invokestatic org/openjdk/tests/java/util/stream/TeeOpTest.lambda$testDoubleOps$3:(Lorg/openjdk/tests/java/util/stream/TeeOpTest$4RecordingConsumer;Ljava/util/stream/DoubleStream;)Ljava/util/stream/DoubleStream;
...
11: #105 invokestatic <LMF>
Method arguments:
...
#127 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.before:(Ljava/util/stream/TestData;)V
...
12: #105 invokestatic <LMF>
Method arguments:
...
#132 invokevirtual org/openjdk/tests/java/util/stream/TeeOpTest$AbstractRecordingConsumer.after:(Ljava/util/stream/TestData;)V
...
...got glued into:
6: #105 invokestatic <LMF>
Method arguments:
...
#146 invokestatic org/openjdk/tests/java/util/stream/TeeOpTest.lambda$testDoubleOps$3:(Lorg/openjdk/tests/java/util/stream/TeeOpTest$4RecordingConsumer;Ljava/util/stream/DoubleStream;)Ljava/util/stream/DoubleStream;
...
24-08-2015
Ah! The trick to reproduce was to remove JT* files from the last run. jtreg apparently fails us here. Indeed, it seems to fail with JDK-8129547 onboard, and passes with JDK-8129547 reverted. The "javap -c -v" dump yields an expected reduce in BSM count (12 -> 6), and minor expected changes in the bytecode, see javap.diff. Is this a VM bug?
24-08-2015
But... It fails even when JDK-8129547 is reverted.
24-08-2015
Yes, it fails for me on recent jdk9-dev like this:
test org.openjdk.tests.java.util.stream.TeeOpTest.testLongOps("LongStream.longRangeClosed(0,l): 100", java.util.stream.TestData$AbstractTestData$LongTestData@5f5be41a): failure
java.lang.AbstractMethodError: Method org/openjdk/tests/java/util/stream/TeeOpTest$$Lambda$113.accept(Ljava/lang/Object;)V is abstract
at org.openjdk.tests.java.util.stream.TeeOpTest$$Lambda$113/1811134886.accept(Unknown Source)
at java.util.stream.OpTestCase$ExerciseDataStreamBuilder.exercise(OpTestCase.java:384)
at org.openjdk.tests.java.util.stream.TeeOpTest.testLongOps(TeeOpTest.java:121)
at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:519)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)