Skip to content

Commit 7a7f992

Browse files
committed
SI-9393 fix modifiers of ClassBTypes for Java annotations
The Scala classfile and java source parsers make Java annotation classes (which are actually interfaces at the classfile level) look like Scala annotation classes: - the INTERFACE / ABSTRACT flags are not added - scala.annotation.Annotation is added as superclass - scala.annotation.ClassfileAnnotation is added as interface This makes type-checking @annot uniform, whether it is defined in Java or Scala. This is a hack that leads to various bugs (SI-9393, SI-9400). Instead the type-checking should be special-cased for Java annotations. This commit fixes SI-9393 and a part of SI-9400, but it's still easy to generate invalid classfiles. Restores the assertions that were disabled in scala#4621. I'd like to leave these assertions in: they are valuable and helped uncovering the issue being fixed here. A new flag JAVA_ANNOTATION is introduced for Java annotation ClassSymbols, similar to the existing ENUM flag. When building ClassBTypes for Java annotations, the flags, superclass and interfaces are recovered to represent the situation in the classfile. Cleans up and documents the flags space in the area of "late" and "anti" flags. The test for SI-9393 is extended to test both the classfile and the java source parser.
1 parent 8946d60 commit 7a7f992

File tree

18 files changed

+188
-40
lines changed

18 files changed

+188
-40
lines changed

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
256256
if (hasAbstractMethod) ACC_ABSTRACT else 0
257257
}
258258
GenBCode.mkFlags(
259+
// SI-9393: the classfile / java source parser make java annotation symbols look like classes.
260+
// here we recover the actual classfile flags.
261+
if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
259262
if (classSym.isPublic) ACC_PUBLIC else 0,
260263
if (classSym.isFinal) ACC_FINAL else 0,
261264
// see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
@@ -310,10 +313,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
310313
}
311314

312315
private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
313-
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).map(assoc =>
316+
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
314317
assoc.collectFirst {
315318
case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
316-
}).flatten.getOrElse(AnnotationRetentionPolicyClassValue)
319+
}).getOrElse(AnnotationRetentionPolicyClassValue)
317320

318321
def implementedInterfaces(classSym: Symbol): List[Symbol] = {
319322
// Additional interface parents based on annotations and other cues
@@ -322,9 +325,18 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
322325
case _ => None
323326
}
324327

325-
def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait
328+
// SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
329+
def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag
326330

327-
val allParents = classSym.info.parents ++ classSym.annotations.flatMap(newParentForAnnotation)
331+
val classParents = {
332+
val parents = classSym.info.parents
333+
// SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
334+
// parents of a java annotations. undo this for the backend (where we need classfile-level information).
335+
if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
336+
else parents
337+
}
338+
339+
val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)
328340

329341
// We keep the superClass when computing minimizeParents to eliminate more interfaces.
330342
// Example: T can be eliminated from D

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
153153
*/
154154
private def initJClass(jclass: asm.ClassVisitor) {
155155

156-
val ps = claszSymbol.info.parents
157-
val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol)
158-
val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map {
156+
val bType = classBTypeFromSymbol(claszSymbol)
157+
val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName
158+
val interfaceNames = bType.info.get.interfaces map {
159159
case classBType =>
160160
if (classBType.isNestedClass.get) { innerClassBufferASM += classBType }
161161
classBType.internalName

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -841,17 +841,16 @@ abstract class BTypes {
841841

842842
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
843843

844-
// TODO bring these back in a way that doesn't trip pos/t9393
845-
// assert(
846-
// if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
847-
// else if (isInterface.get) isJLO(info.get.superClass.get)
848-
// else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get),
849-
// s"Invalid superClass in $this: ${info.get.superClass}"
850-
// )
851-
// assert(
852-
// info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)),
853-
// s"Invalid interfaces in $this: ${info.get.interfaces}"
854-
// )
844+
assert(
845+
if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
846+
else if (isInterface.get) isJLO(info.get.superClass.get)
847+
else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get),
848+
s"Invalid superClass in $this: ${info.get.superClass}"
849+
)
850+
assert(
851+
info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)),
852+
s"Invalid interfaces in $this: ${info.get.interfaces}"
853+
)
855854

856855
assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses)
857856
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,18 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
216216
}
217217

218218
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
219-
val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass
219+
// Check for isImplClass: trait implementation classes have NoSymbol as superClass
220+
// Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add
221+
// scala.annotation.Annotation as superclass to java annotations. In reality, java
222+
// annotation classfiles have superclass Object (like any interface classfile).
223+
val superClassSym = if (classSym.isImplClass || classSym.hasJavaAnnotationFlag) ObjectClass else {
224+
val sc = classSym.superClass
225+
// SI-9393: Java annotation classes don't have the ABSTRACT/INTERFACE flag, so they appear
226+
// (wrongly) as superclasses. Fix this for BTypes: the java annotation will appear as interface
227+
// (handled by method implementedInterfaces), the superclass is set to Object.
228+
if (sc.hasJavaAnnotationFlag) ObjectClass
229+
else sc
230+
}
220231
assert(
221232
if (classSym == ObjectClass)
222233
superClassSym == NoSymbol

src/compiler/scala/tools/nsc/javac/JavaParsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
751751
val (statics, body) = typeBody(AT, name)
752752
val templ = makeTemplate(annotationParents, body)
753753
addCompanionObject(statics, atPos(pos) {
754-
ClassDef(mods, name, List(), templ)
754+
ClassDef(mods | Flags.JAVA_ANNOTATION, name, List(), templ)
755755
})
756756
}
757757

src/reflect/scala/reflect/internal/ClassfileConstants.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ object ClassfileConstants {
345345
case JAVA_ACC_ABSTRACT => if (isAnnotation) 0L else if (isClass) ABSTRACT else DEFERRED
346346
case JAVA_ACC_INTERFACE => if (isAnnotation) 0L else TRAIT | INTERFACE | ABSTRACT
347347
case JAVA_ACC_ENUM => ENUM
348+
case JAVA_ACC_ANNOTATION => JAVA_ANNOTATION
348349
case _ => 0L
349350
}
350351
private def translateFlags(jflags: Int, baseFlags: Long, isClass: Boolean): Long = {
@@ -360,6 +361,7 @@ object ClassfileConstants {
360361
res |= translateFlag0(jflags & JAVA_ACC_ABSTRACT)
361362
res |= translateFlag0(jflags & JAVA_ACC_INTERFACE)
362363
res |= translateFlag0(jflags & JAVA_ACC_ENUM)
364+
res |= translateFlag0(jflags & JAVA_ACC_ANNOTATION)
363365
res
364366
}
365367

src/reflect/scala/reflect/internal/Flags.scala

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import scala.collection.{ mutable, immutable }
6464
// 46: ARTIFACT
6565
// 47: DEFAULTMETHOD/M
6666
// 48: ENUM
67-
// 49:
67+
// 49: JAVA_ANNOTATION
6868
// 50:
6969
// 51: lateDEFERRED
7070
// 52: lateFINAL
@@ -120,7 +120,8 @@ class ModifierFlags {
120120
final val ARTIFACT = 1L << 46 // symbol should be ignored when typechecking; will be marked ACC_SYNTHETIC in bytecode
121121
// to see which symbols are marked as ARTIFACT, see scaladocs for FlagValues.ARTIFACT
122122
final val DEFAULTMETHOD = 1L << 47 // symbol is a java default method
123-
final val ENUM = 1L << 48 // symbol is an enum
123+
final val ENUM = 1L << 48 // symbol is a java enum
124+
final val JAVA_ANNOTATION = 1L << 49 // symbol is a java annotation
124125

125126
// Overridden.
126127
def flagToString(flag: Long): String = ""
@@ -172,12 +173,28 @@ class Flags extends ModifierFlags {
172173
final val SYNCHRONIZED = 1L << 45 // symbol is a method which should be marked ACC_SYNCHRONIZED
173174

174175
// ------- shift definitions -------------------------------------------------------
176+
//
177+
// Flags from 1L to (1L << 50) are normal flags.
178+
//
179+
// The flags DEFERRED (1L << 4) to MODULE (1L << 8) have a `late` counterpart. Late flags change
180+
// their counterpart from 0 to 1 after a specific phase (see below). The first late flag
181+
// (lateDEFERRED) is at (1L << 51), i.e., late flags are shifted by 47. The last one is (1L << 55).
182+
//
183+
// The flags PROTECTED (1L) to PRIVATE (1L << 2) have a `not` counterpart. Negated flags change
184+
// their counterpart from 1 to 0 after a specific phase (see below). They are shifted by 56, i.e.,
185+
// the first negated flag (notPROTECTED) is at (1L << 56), the last at (1L << 58).
186+
//
187+
// Late and negative flags are only enabled after certain phases, implemented by the phaseNewFlags
188+
// method of the SubComponent, so they implement a bit of a flag history.
189+
//
190+
// The flags (1L << 59) to (1L << 63) are currently unused. If added to the InitialFlags mask,
191+
// they could be used as normal flags.
175192

176-
final val InitialFlags = 0x0001FFFFFFFFFFFFL // flags that are enabled from phase 1.
177-
final val LateFlags = 0x00FE000000000000L // flags that override flags in 0x1FC.
178-
final val AntiFlags = 0x7F00000000000000L // flags that cancel flags in 0x07F
179-
final val LateShift = 47L
180-
final val AntiShift = 56L
193+
final val InitialFlags = 0x0007FFFFFFFFFFFFL // normal flags, enabled from the first phase: 1L to (1L << 50)
194+
final val LateFlags = 0x00F8000000000000L // flags that override flags in (1L << 4) to (1L << 8): DEFERRED, FINAL, INTERFACE, METHOD, MODULE
195+
final val AntiFlags = 0x0700000000000000L // flags that cancel flags in 1L to (1L << 2): PROTECTED, OVERRIDE, PRIVATE
196+
final val LateShift = 47
197+
final val AntiShift = 56
181198

182199
// Flags which sketchily share the same slot
183200
// 16: BYNAMEPARAM/M CAPTURED COVARIANT/M
@@ -436,7 +453,7 @@ class Flags extends ModifierFlags {
436453
case ARTIFACT => "<artifact>" // (1L << 46)
437454
case DEFAULTMETHOD => "<defaultmethod>" // (1L << 47)
438455
case ENUM => "<enum>" // (1L << 48)
439-
case 0x2000000000000L => "" // (1L << 49)
456+
case JAVA_ANNOTATION => "<annotation>" // (1L << 49)
440457
case 0x4000000000000L => "" // (1L << 50)
441458
case `lateDEFERRED` => "<latedeferred>" // (1L << 51)
442459
case `lateFINAL` => "<latefinal>" // (1L << 52)

src/reflect/scala/reflect/internal/HasFlags.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ trait HasFlags {
8383
def hasAccessorFlag = hasFlag(ACCESSOR)
8484
def hasDefault = hasFlag(DEFAULTPARAM) && hasFlag(METHOD | PARAM) // Second condition disambiguates with TRAIT
8585
def hasEnumFlag = hasFlag(ENUM)
86+
def hasJavaAnnotationFlag = hasFlag(JAVA_ANNOTATION)
8687
@deprecated("Use isLocalToThis instead", "2.11.0")
8788
def hasLocalFlag = hasFlag(LOCAL)
8889
def isLocalToThis = hasFlag(LOCAL)

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
732732
final def flags: Long = {
733733
if (Statistics.hotEnabled) Statistics.incCounter(flagsCount)
734734
val fs = _rawflags & phase.flagMask
735-
(fs | ((fs & LateFlags) >>> LateShift)) & ~(fs >>> AntiShift)
735+
(fs | ((fs & LateFlags) >>> LateShift)) & ~((fs & AntiFlags) >>> AntiShift)
736736
}
737737
def flags_=(fs: Long) = _rawflags = fs
738738
def rawflags_=(x: Long) { _rawflags = x }

test/files/jvm/innerClassAttribute/Test.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ object Test extends BytecodeTest {
149149

150150
def testA11() = {
151151
val List(ann) = innerClassNodes("A11")
152-
// in the java class file, the INNERCLASS attribute has more flags (public | static | abstract | interface | annotation)
153-
// the scala compiler has its own interpretation of java annotations ant their flags.. it only emits publicStatic.
154-
assertMember(ann, "JavaAnnot_1", "Ann", flags = publicStatic)
152+
assertMember(ann, "JavaAnnot_1", "Ann", flags = publicAbstractInterface | Flags.ACC_STATIC | Flags.ACC_ANNOTATION)
155153
}
156154

157155
def testA13() = {

test/files/pos/t9393/Named.java

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/files/pos/t9393/NamedImpl.java renamed to test/files/pos/t9393/NamedImpl_1.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
*/
44
package bug;
55

6-
import bug.Named;
6+
import bug.Named_1;
77
import java.io.Serializable;
88
import java.lang.annotation.Annotation;
99

10-
public class NamedImpl implements Named {
10+
public class NamedImpl_1 implements Named_1 {
1111

1212
public Class<? extends Annotation> annotationType() {
1313
return null;

test/files/pos/t9393/NamedImpl_2.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
3+
*/
4+
package bug;
5+
6+
import bug.Named_2;
7+
import java.io.Serializable;
8+
import java.lang.annotation.Annotation;
9+
10+
public class NamedImpl_2 implements Named_2 {
11+
12+
public Class<? extends Annotation> annotationType() {
13+
return null;
14+
}
15+
}

test/files/pos/t9393/Named_1.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package bug;
2+
3+
public @interface Named_1 {}

test/files/pos/t9393/Named_2.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package bug;
2+
3+
public @interface Named_2 {}

test/files/pos/t9393/test.scala

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/files/pos/t9393/test_2.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class C {
2+
new bug.NamedImpl_1 // separate compilation, testing the classfile parser
3+
new bug.NamedImpl_2 // mixed compilation, testing the java source parser
4+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package scala.tools.nsc
2+
package symtab
3+
4+
import org.junit.Assert._
5+
import scala.tools.testing.AssertUtil._
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
import org.junit.runners.JUnit4
9+
10+
@RunWith(classOf[JUnit4])
11+
class FlagsTest {
12+
object symbolTable extends SymbolTableForUnitTesting
13+
import symbolTable._
14+
import Flags._
15+
16+
def sym = NoSymbol.newTermSymbol(nme.EMPTY)
17+
18+
def withFlagMask[A](mask: Long)(body: => A): A = enteringPhase(new Phase(NoPhase) {
19+
override def flagMask = mask
20+
def name = ""
21+
def run() = ()
22+
})(body)
23+
24+
def testTimedFlag(flag: Long, test: Symbol => Boolean, enabling: Boolean) = {
25+
assertEquals(withFlagMask(InitialFlags)(test(sym.setFlag(flag))), !enabling)
26+
assertEquals(withFlagMask(InitialFlags | flag)(test(sym.setFlag(flag))), enabling)
27+
}
28+
29+
def testLate(flag: Long, test: Symbol => Boolean) = testTimedFlag(flag, test, enabling = true)
30+
def testNot(flag: Long, test: Symbol => Boolean) = testTimedFlag(flag, test, enabling = false)
31+
32+
@Test
33+
def testTimedFlags(): Unit = {
34+
testLate(lateDEFERRED, _.isDeferred)
35+
testLate(lateFINAL, _.isFinal)
36+
testLate(lateINTERFACE, _.isInterface)
37+
testLate(lateMETHOD, _.isMethod)
38+
testLate(lateMODULE, _.isModule)
39+
testNot(PROTECTED | notPROTECTED, _.isProtected)
40+
testNot(OVERRIDE | notOVERRIDE, _.isOverride)
41+
testNot(PRIVATE | notPRIVATE, _.isPrivate)
42+
43+
assertFalse(withFlagMask(AllFlags)(sym.setFlag(PRIVATE | notPRIVATE).isPrivate))
44+
45+
assertEquals(withFlagMask(InitialFlags)(sym.setFlag(PRIVATE | notPRIVATE).flags & PRIVATE), PRIVATE)
46+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(PRIVATE | notPRIVATE).flags & PRIVATE), 0)
47+
}
48+
49+
@Test
50+
def normalLateOverlap(): Unit = {
51+
// late flags are shifted by LateShift == 47.
52+
// however, the first late flag is lateDEFERRED, which is DEFERRED << 47 == (1 << 4) << 47 == 1 << 51
53+
// the flags from 1 << 47 to 1 << 50 are not late flags. this is ensured by the LateFlags mask.
54+
55+
for (i <- 0 to 3) {
56+
val f = 1L << i
57+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(f << LateShift).flags & f), 0) // not treated as late flag
58+
}
59+
for (i <- 4 to 8) {
60+
val f = 1L << i
61+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(f << LateShift).flags & f), f) // treated as late flag
62+
}
63+
}
64+
65+
@Test
66+
def normalAnti(): Unit = {
67+
for (i <- 0 to 2) {
68+
val f = 1L << i
69+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(f | (f << AntiShift)).flags & f), 0) // negated flags
70+
}
71+
for (i <- 3 to 7) {
72+
val f = 1L << i
73+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(f | (f << AntiShift)).flags & f), f) // not negated
74+
}
75+
}
76+
77+
@Test
78+
def lateAntiCrossCheck(): Unit = {
79+
val allButNegatable = AllFlags & ~(PROTECTED | OVERRIDE | PRIVATE)
80+
val lateable = 0L | DEFERRED | FINAL | INTERFACE | METHOD | MODULE
81+
val lateFlags = lateable << LateShift
82+
val allButLateable = AllFlags & ~lateable
83+
84+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(AllFlags).flags), allButNegatable)
85+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(allButLateable).flags), allButNegatable)
86+
87+
assertEquals(withFlagMask(AllFlags)(sym.setFlag(lateFlags).flags), lateFlags | lateable)
88+
}
89+
}

0 commit comments

Comments
 (0)