summaryrefslogtreecommitdiff
blob: 964b323d9b30780eb888445a666886c83bf53b6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
commit f3f1064c90371449949892f30de91cc1f2662c55
Merge: 0b7dddb 050b4c9
Author: Grzegorz Kossakowski <grzegorz.kossakowski@gmail.com>
Date:   Sat Jul 27 22:39:44 2013 -0700

    Merge pull request #2750 from retronym/ticket/7455-2.10.x
    
    SI-7455 Drop dummy param for synthetic access constructor

commit 050b4c951c838699c2fe30cbf01b63942c63a299
Author: Jason Zaugg <jzaugg@gmail.com>
Date:   Wed Jul 17 15:52:48 2013 +1000

    SI-7455 Drop dummy param for synthetic access constructor
    
    Java synthesizes public constructors in private classes to
    allow access from inner classes. The signature of
    that synthetic constructor (known as a "access constructor")
    has a dummy parameter appended to avoid overloading clashes.
    javac chooses the type "Enclosing$1" for the dummy parameter
    (called the "access constructor tag") which is either an
    existing anonymous class or a synthesized class for this purpose.
    
    In OpenJDK, this transformation is performed in:
    
      langtools/src/share/classes/com/sun/tools/javac/comp/Lower.java
    
    (Incidentally, scalac would just emits a byte-code public
    constructor in this situation, rather than a private constructor /
    access constructor pair.)
    
    Scala parses the signature of the access contructor, and drops
    the $outer parameter, but retains the dummy parameter. This causes
    havoc when it tries to parse the bytecode for that anonymous class;
    the class file parser doesn't have the enclosing type parameters
    of Vector in scope and crash ensues.
    
    In any case, we shouldn't allow user code to see that constructor;
    it should only be called from within its own compilation unit.
    
    This commit drops the dummy parameter from access constructor
    signatures in class file parsing.

diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index da11754..4e5204f 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -626,7 +626,7 @@ abstract class ClassfileParser {
         sawPrivateConstructor = true
       in.skip(2); skipAttributes()
     } else {
-      if ((sflags & PRIVATE) != 0L && global.settings.optimise.value) {
+      if ((sflags & PRIVATE) != 0L && global.settings.optimise.value) { // TODO this should be !optimize, no? See c4181f656d.
         in.skip(4); skipAttributes()
       } else {
         val name = pool.getName(in.nextChar)
@@ -636,7 +636,7 @@ abstract class ClassfileParser {
           info match {
             case MethodType(params, restpe) =>
               // if this is a non-static inner class, remove the explicit outer parameter
-              val newParams = innerClasses getEntry currentClass match {
+              val paramsNoOuter = innerClasses getEntry currentClass match {
                 case Some(entry) if !isScalaRaw && !isStatic(entry.jflags) =>
                   /* About `clazz.owner.isPackage` below: SI-5957
                    * For every nested java class A$B, there are two symbols in the scala compiler.
@@ -650,6 +650,15 @@ abstract class ClassfileParser {
                 case _ =>
                   params
               }
+              val newParams = paramsNoOuter match {
+                case (init :+ tail) if (jflags & JAVA_ACC_SYNTHETIC) != 0L =>
+                  // SI-7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
+                  // are added when an inner class needs to access a private constructor.
+                  init
+                case _ =>
+                  paramsNoOuter
+              }
+
               info = MethodType(newParams, clazz.tpe)
           }
         sym.setInfo(info)
diff --git a/test/files/run/t7455.check b/test/files/run/t7455.check
new file mode 100644
index 0000000..0eb9342
--- /dev/null
+++ b/test/files/run/t7455.check
@@ -0,0 +1,4 @@
+private[package <empty>] def <init>(x$1: String): Outer[E]
+private[package <empty>] def <init>(): Outer$PrivateInner
+private[package <empty>] def <init>(): Outer$PrivateStaticInner
+private[package <empty>] def <init>(x$2: String): Outer$PublicInner
diff --git a/test/files/run/t7455/Outer.java b/test/files/run/t7455/Outer.java
new file mode 100644
index 0000000..10c97a9
--- /dev/null
+++ b/test/files/run/t7455/Outer.java
@@ -0,0 +1,31 @@
+public class Outer<E> {
+    public void elements() {
+        new C<E>() {
+        };
+    }
+
+    private Outer(String a) {}
+
+    static class SubSelf extends Outer<String> {
+        public SubSelf() { super(""); }
+    }
+
+    private class PrivateInner {
+    }
+    class SubPrivateInner extends PrivateInner {
+    }
+
+    private class PublicInner {
+        private PublicInner(String a) {}
+    }
+    class SubPublicInner extends PublicInner {
+        public SubPublicInner() { super(""); }
+    }
+
+    private static class PrivateStaticInner {
+    }
+    public static class SubPrivateStaticInner extends PrivateStaticInner {
+    }
+}
+
+class C<E> {}
diff --git a/test/files/run/t7455/Test.scala b/test/files/run/t7455/Test.scala
new file mode 100644
index 0000000..b23a724
--- /dev/null
+++ b/test/files/run/t7455/Test.scala
@@ -0,0 +1,30 @@
+import scala.tools.partest._
+
+// javac adds dummy parameters of type Outer$1 to synthetic access constructors
+// This test shows that we strip them from the signatures. If we don't, we trigger
+// parsing of Outer$1 which can fail if it references type parameters of the Outer.
+//
+// OLD OUTPUT:
+//  private[package <empty>] def <init>(x$2: Outer$1): Outer$PrivateInner
+//  error: error while loading Outer$1, class file 't7455-run.obj/Outer$1.class' is broken
+//  (class java.util.NoSuchElementException/key not found: E)
+//  ...
+object Test extends DirectTest {
+  override def code = ""
+
+  def show {
+    val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
+    val compiler = newCompiler("-cp", classpath, "-d", testOutput.path)
+    import compiler._, definitions._
+    new compiler.Run
+
+    for {
+      name <- Seq("Outer", "Outer$PrivateInner", "Outer$PrivateStaticInner", "Outer$PublicInner")
+      clazz = compiler.rootMirror.staticClass(name)
+      constr <- clazz.info.member(nme.CONSTRUCTOR).alternatives
+    } {
+      println(constr.defString)
+      fullyInitializeSymbol(constr)
+    }
+  }
+}