Skip to content

Commit 4083742

Browse files
committed
Comment summarizing the JVM spec for InnerClass / EnclosingMethod
1 parent fe1cbd2 commit 4083742

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,205 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) {
232232
}
233233
}
234234

235+
/**
236+
* InnerClass and EnclosingMethod attributes (EnclosingMethod is displayed as OUTERCLASS in asm).
237+
*
238+
* In this summary, "class" means "class or interface".
239+
*
240+
* JLS: http://docs.oracle.com/javase/specs/jls/se8/html/index.html
241+
* JVMS: http://docs.oracle.com/javase/specs/jvms/se8/html/index.html
242+
*
243+
* Terminology
244+
* -----------
245+
*
246+
* - Nested class (JLS 8): class whose declaration occurs within the body of another class
247+
*
248+
* - Top-level class (JLS 8): non-nested class
249+
*
250+
* - Inner class (JLS 8.1.3): nested class that is not (explicitly or implicitly) static
251+
*
252+
* - Member class (JLS 8.5): class directly enclosed in the body of a class (and not, for
253+
* example, defined in a method). Member classes cannot be anonymous. May be static.
254+
*
255+
* - Local class (JLS 14.3): nested, non-anonymous class that is not a member of a class
256+
* - cannot be static (therefore they are "inner" classes)
257+
* - can be defined in a method, a constructor or in an initializer block
258+
*
259+
* - Initializer block (JLS 8.6 / 8.7): block of statements in a java class
260+
* - static initializer: executed before constructor body
261+
* - instance initializer: exectued when class is initialized (instance creation, static
262+
* field access, ...)
263+
*
264+
*
265+
* InnerClass
266+
* ----------
267+
*
268+
* The JVMS 4.7.6 requires an entry for every class mentioned in a CONSTANT_Class_info in the
269+
* constant pool (CP) that is not a member of a package (JLS 7.1).
270+
*
271+
* The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP)
272+
* - its immediately enclosing class
273+
* - all of its member classes
274+
* - all local and anonymous classes that appear elsewhere (method, constructor, initializer
275+
* block, field initializer)
276+
*
277+
* In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for
278+
* - All enclosing classes (except the outermost, which is top-level)
279+
* - My comment: not sure how this is implied, below (*) a Java counter-example.
280+
* In any case, the Java compiler seems to add all enclosing classes, even if they are not
281+
* otherwise mentioned in the CP. So we should do the same.
282+
* - All nested classes (including anonymous and local, but not transitively)
283+
*
284+
* Fields in the InnerClass entries:
285+
* - inner class: the (nested) class C we are talking about
286+
* - outer class: the class of which C is a member. Has to be null for non-members, i.e. for
287+
* local and anonymous classes.
288+
* - inner name: A string with the simple name of the inner class. Null for anonymous classes.
289+
* - flags: access property flags, details in JVMS, table in 4.7.6.
290+
*
291+
*
292+
* Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing
293+
* classes have to be present as well (by the rules above). Example:
294+
*
295+
* class Outer { class I1 { class I2 { } } }
296+
* class User { Outer.I1.I2 foo() { } }
297+
*
298+
* The return type "Outer.I1.I2" puts "Outer$I1$I2" in the CP, therefore the class is added to the
299+
* InnerClass attribute. For this entry, the "outer class" field will be "Outer$I1". This in turn
300+
* adds "Outer$I1" to the CP, which requires adding that class to the InnerClass attribute.
301+
* (For local / anonymous classes this would not be the case, since the "outer class" attribute
302+
* would be empty. However, no class (other than the enclosing class) can refer to them, as they
303+
* have no name.)
304+
*
305+
* In the current implementation of the Scala compiler, when adding a class to the InnerClass
306+
* attribute, all of its enclosing classes will be added as well. Javac seems to do the same,
307+
* see (*).
308+
*
309+
*
310+
* Note 2: If a class name is mentioned only in a CONSTANT_Utf8_info, but not in a
311+
* CONSTANT_Class_info, the JVMS does not require an entry in the InnerClass attribute. However,
312+
* the Java compiler seems to add such classes anyway. For example, when using an annotation, the
313+
* annotation class is stored as a CONSTANT_Utf8_info in the CP:
314+
*
315+
* @O.Ann void foo() { }
316+
*
317+
* adds "const #13 = Asciz LO$Ann;;" in the constant pool. The "RuntimeInvisibleAnnotations"
318+
* attribute refers to that constant pool entry. Even though there is no other reference to
319+
* `O.Ann`, the java compiler adds an entry for that class to the InnerClass attribute (which
320+
* entails adding a CONSTANT_Class_info for the class).
321+
*
322+
*
323+
*
324+
* EnclosingMethod
325+
* ---------------
326+
*
327+
* JVMS 4.7.7: the attribute must be present "if and only if it represents a local class
328+
* or an anonymous class" (i.e. not for member classes).
329+
*
330+
* Fields:
331+
* - class: the enclosing class
332+
* - method: the enclosing method (or constructor). Null if the class is not enclosed by a
333+
* method, i.e. for
334+
* - local or anonymous classes defined in (static or non-static) initializer blocks
335+
* - anonymous classes defined in initializer blocks or field initializers
336+
*
337+
* Note: the field is required for anonymous classes defined within local variable
338+
* initializers (within a method), Java example below (**).
339+
*
340+
* Currently, the Scala compiler sets "method" to the class constructor for classes
341+
* defined in initializer blocks or field initializers. This is probably OK, since the
342+
* Scala compiler desugars these statements into to the primary constructor.
343+
*
344+
*
345+
* (*)
346+
* public class Test {
347+
* void foo() {
348+
* class Foo1 {
349+
* // constructor statement block
350+
* {
351+
* class Foo2 {
352+
* class Foo3 { }
353+
* }
354+
* }
355+
* }
356+
* }
357+
* }
358+
*
359+
* The class file Test$1Foo1$1Foo2$Foo3 has no reference to the class Test$1Foo1, however it
360+
* still contains an InnerClass attribute for Test$1Foo1.
361+
* Maybe this is just because the Java compiler follows the JVMS comment ("InnerClasses
362+
* information for each enclosing class").
363+
*
364+
*
365+
* (**)
366+
* void foo() {
367+
* // anonymous class defined in local variable initializer expression.
368+
* Runnable x = true ? (new Runnable() {
369+
* public void run() { return; }
370+
* }) : null;
371+
* }
372+
*
373+
* The EnclosingMethod attribute of the anonymous class mentions "foo" in the "method" field.
374+
*
375+
*
376+
* Java Compatibility
377+
* ------------------
378+
*
379+
* In the InnerClass entry for classes in top-level modules, the "outer class" is emitted as the
380+
* mirror class (or the existing companion class), i.e. C1 is nested in T (not T$).
381+
* For classes nested in a nested object, the "outer class" is the module class: C2 is nested in T$N$
382+
* object T {
383+
* class C1
384+
* object N { class C2 }
385+
* }
386+
*
387+
* Reason: java compat. It's a "best effort" "solution". If you want to use "C1" from Java, you
388+
* can write "T.C1", and the Java compiler will translate that to the classfile T$C1.
389+
*
390+
* If we would emit the "outer class" of C1 as "T$", then in Java you'd need to write "T$.C1"
391+
* because the java compiler looks at the InnerClass attribute to find if an inner class exists.
392+
* However, the Java compiler would then translate the '.' to '$' and you'd get the class name
393+
* "T$$C1". This class file obviously does not exist.
394+
*
395+
* Directly using the encoded class name "T$C1" in Java does not work: since the classfile
396+
* describes a nested class, the Java compiler hides it from the classpath and will report
397+
* "cannot find symbol T$C1". This means that the class T.N.C2 cannot be referenced from a
398+
* Java source file in any way.
399+
*
400+
*
401+
* STATIC flag
402+
* -----------
403+
*
404+
* Java: static nested classes have the "static" flag in the InnerClass attribute. This is not the
405+
* case for local classes defined within a static method, even though such classes, as they are
406+
* defined in a static context, don't take an "outer" instance.
407+
* Non-static nested classes (inner classes, including local classes defined in a non-static
408+
* method) take an "outer" instance on construction.
409+
*
410+
* Scala: Explicitouter adds an "outer" parameter to nested classes, except for classes defined
411+
* in a static context, i.e. when all outer classes are module classes.
412+
* package p
413+
* object O1 {
414+
* class C1 // static
415+
* object O2 {
416+
* def f = {
417+
* class C2 { // static
418+
* class C3 // non-static, needs outer
419+
* }
420+
* }
421+
* }
422+
* }
423+
*
424+
* Int the InnerClass attribute, the `static` flag is added for all classes defined in a static
425+
* context, i.e. also for C2. This is different than in Java.
426+
*
427+
*
428+
* Mirror Classes
429+
* --------------
430+
*
431+
* TODO: innerclass attributes on mirror class, bean info class
432+
*/
433+
235434
/**
236435
* Class or Interface type.
237436
*

0 commit comments

Comments
 (0)