Invoking a method from an indirect superclass via Enclosing.super.method (JDK-8027789)
Jan Lahoda
jan.lahoda at oracle.com
Thu Nov 7 15:28:02 PST 2013
Hello,
Dan found out that in cases like this (JDK-8027789):
---
package p1;
/* package-access */ class A {
protected void m() {}
}
---
package p1;
public class B extends A {}
---
package p2;
public class C extends p1.B {
{ new Object() { { C.super.m(); } }; }
public static void main(String... args) { new C(); }
}
---
javac will generate an access method into the class p2.C like this:
---
static void access$001(p2.C);
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method p1/A.m:()V
4: return
LineNumberTable:
line 2: 0
---
Which, as I understood, is wrong: the invokespecial should refer to p1.B
(p1/B.m:()V), which is the direct superclass of the given class, not to
the indirect superclass p1.A. An exception are references to methods
from j.l.Object, where the reference should refer to j.l.Object. (This
is per JLS 13.1 - is my understanding correct?)
Based on my recent experiments, this problem only occurs when the access
method is generated, javac generates the correct reference in cases
where the access method is not required/generated (for target 1.2+).
The question is whether we (I) should attempt to fix this for JDK8. As
this is a change in the output binary form, this may pose some
compatibility risk, although I don't know a particular case where this
could actually introduce problems (but the fix for this would allow some
programs to run which do not run now, as e.g. in the above example).
I tried to estimate the impact of this, using my (somewhat old) copy of
the qualitas corpus. Out of 54 usable systems, only about 7 contain at
least one invokespecial instruction that would be changed, and there are
about 103 total invokespecial instructions that would be changed.
I also tried to analyze a fragment of the maven repo (about 34GB of
artifacts, a copy of Mandy's repo), and there were about 530 instances
of invokespecial instructions inside access methods (presumably
generated by javac), that might be affected by the fix. (For the record,
there are many more suspicious invokespecials, with somewhat unclear
origins - possibly other compilers?)
Would you suggest to try to fix this for JDK8? And if so, should the
new/correct code be generated only for target 8+, keeping the original
behavior for older targets?
I am attaching my current fix for this for reference.
Thanks,
Jan
-------------- next part --------------
# HG changeset patch
# Parent 44bedd5db979247b143537e08e9f9ae91e5642b1
8027789: Access method for Outer.super.m() references indirect superclass
Reviewed-by: TBD
diff -r 44bedd5db979 src/share/classes/com/sun/tools/javac/comp/Lower.java
--- a/src/share/classes/com/sun/tools/javac/comp/Lower.java Wed Nov 06 18:38:33 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Lower.java Wed Nov 06 18:38:57 2013 +0100
@@ -1379,7 +1379,8 @@
args = make.Idents(md.params);
} else {
Symbol msym = sym;
- if (sym.owner.isInterface()) {
+ if (target.useDirectSuperClassForSuperMethod() &&
+ sym.owner != vsym.owner && sym.owner != syms.objectType.tsym) {
msym = msym.clone(types.supertype(accessor.owner.type).tsym);
}
ref = make.Select(make.Ident(md.params.head), msym);
diff -r 44bedd5db979 src/share/classes/com/sun/tools/javac/jvm/Target.java
--- a/src/share/classes/com/sun/tools/javac/jvm/Target.java Wed Nov 06 18:38:33 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/jvm/Target.java Wed Nov 06 18:38:57 2013 +0100
@@ -289,6 +289,13 @@
return compareTo(JDK1_5) >= 0;
}
+ /**Starting with target 1.8, we use the direct superclass as the site when invoking
+ * indirect superclass' method.
+ */
+ public boolean useDirectSuperClassForSuperMethod() {
+ return compareTo(JDK1_8) >= 0;
+ }
+
/** In J2SE1.5.0, we introduced the "EnclosingMethod" attribute
* for improved reflection support.
*/
diff -r 44bedd5db979 test/tools/javac/expression/_super/NonDirectSuper/Base.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/expression/_super/NonDirectSuper/Base.java Wed Nov 06 18:38:57 2013 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package base;
+
+public class Base extends PackagePrivate { }
+
+class PackagePrivate {
+ protected int test() {
+ return 0;
+ }
+}
diff -r 44bedd5db979 test/tools/javac/expression/_super/NonDirectSuper/NonDirectSuper.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/expression/_super/NonDirectSuper/NonDirectSuper.java Wed Nov 06 18:38:57 2013 +0100
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8027789
+ * @summary check that the direct superclass is used as the site when calling
+ * a superclass' method
+ * @compile Base.java NonDirectSuper.java
+ * @run main test.NonDirectSuper
+ */
+
+package test;
+
+import java.io.File;
+
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.ConstantPool.CPRefInfo;
+import com.sun.tools.classfile.Instruction;
+import com.sun.tools.classfile.Method;
+import com.sun.tools.classfile.Opcode;
+
+public class NonDirectSuper extends base.Base {
+ @Override protected int test() {
+ return 1;
+ }
+
+ @Override public String toString() {
+ return null;
+ }
+
+ public static void main(String... args) {
+ new NonDirectSuper().run();
+ }
+
+ private void run() {
+ new Runnable() {
+ public void run() {
+ if (NonDirectSuper.super.test() != 0) {
+ throw new IllegalStateException();
+ }
+ if (NonDirectSuper.super.toString() == null) {
+ throw new IllegalStateException();
+ }
+ }
+ }.run();
+ if (super.test() != 0) {
+ throw new IllegalStateException();
+ }
+ if (super.toString() == null) {
+ throw new IllegalStateException();
+ }
+ if (NonDirectSuper.super.test() != 0) {
+ throw new IllegalStateException();
+ }
+ if (NonDirectSuper.super.toString() == null) {
+ throw new IllegalStateException();
+ }
+
+ verifyInvokeSpecialRefToObject("test/NonDirectSuper.class");
+ }
+
+ void verifyInvokeSpecialRefToObject(String classFile) {
+ String workDir = System.getProperty("test.classes");
+ File file = new File(workDir, classFile);
+ try {
+ final ClassFile cf = ClassFile.read(file);
+ for (Method m : cf.methods) {
+ Code_attribute codeAttr = (Code_attribute)m.attributes.get(Attribute.Code);
+ for (Instruction instr : codeAttr.getInstructions()) {
+ if (instr.getOpcode() == Opcode.INVOKESPECIAL) {
+ int pc_index = instr.getShort(1);
+ CPRefInfo ref = (CPRefInfo)cf.constant_pool.get(pc_index);
+ String className = ref.getClassName();
+ if (ref.getNameAndTypeInfo().getName().equals("toString") &&
+ !className.equals("java/lang/Object"))
+ throw new IllegalStateException("Must directly refer to j.l.Object");
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new Error("error reading " + file +": " + e);
+ }
+ }
+}
More information about the compiler-dev
mailing list