Skip to content

Commit 75886dc

Browse files
committed
Merge pull request scala#4026 from soc/SI-4788-new
SI-4788/SI-5948 Respect RetentionPolicy of Java annotations
2 parents 382b33e + c14e053 commit 75886dc

File tree

24 files changed

+238
-120
lines changed

24 files changed

+238
-120
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import PartialFunction._
1414
*/
1515
final class BCodeAsmCommon[G <: Global](val global: G) {
1616
import global._
17+
import definitions._
18+
19+
val ExcludedForwarderFlags = {
20+
import scala.tools.nsc.symtab.Flags._
21+
// Should include DEFERRED but this breaks findMember.
22+
( SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO )
23+
}
1724

1825
/**
1926
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
@@ -124,4 +131,29 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
124131
assert(r != NoSymbol, sym.fullLocationString)
125132
r
126133
})(collection.breakOut)
134+
135+
lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
136+
lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
137+
lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
138+
lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
139+
140+
/** Whether an annotation should be emitted as a Java annotation
141+
* .initialize: if 'annot' is read from pickle, atp might be un-initialized
142+
*/
143+
def shouldEmitAnnotation(annot: AnnotationInfo) = {
144+
annot.symbol.initialize.isJavaDefined &&
145+
annot.matches(ClassfileAnnotationClass) &&
146+
retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
147+
annot.args.isEmpty
148+
}
149+
150+
def isRuntimeVisible(annot: AnnotationInfo): Boolean =
151+
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr)
152+
.exists(_.assocs.contains((nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))))
153+
154+
private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
155+
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).map(assoc =>
156+
assoc.collectFirst {
157+
case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
158+
}).flatten.getOrElse(AnnotationRetentionPolicyClassValue)
127159
}

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

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
469469
trait BCAnnotGen extends BCInnerClassGen {
470470

471471
import genASM.{ubytesToCharArray, arrEncode}
472+
import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible}
472473

473474
/*
474475
* can-multi-thread
@@ -533,17 +534,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
533534
}
534535
}
535536

536-
/* Whether an annotation should be emitted as a Java annotation
537-
* .initialize: if 'annot' is read from pickle, atp might be un-initialized
538-
*
539-
* must-single-thread
540-
*/
541-
private def shouldEmitAnnotation(annot: AnnotationInfo) =
542-
annot.symbol.initialize.isJavaDefined &&
543-
annot.matches(definitions.ClassfileAnnotationClass) &&
544-
annot.args.isEmpty &&
545-
!annot.matches(definitions.DeprecatedAttr)
546-
547537
/*
548538
* In general,
549539
* must-single-thread
@@ -563,7 +553,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
563553
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
564554
val AnnotationInfo(typ, args, assocs) = annot
565555
assert(args.isEmpty, args)
566-
val av = cw.visitAnnotation(descriptor(typ), true)
556+
val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
567557
emitAssocs(av, assocs)
568558
}
569559
}
@@ -575,7 +565,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
575565
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
576566
val AnnotationInfo(typ, args, assocs) = annot
577567
assert(args.isEmpty, args)
578-
val av = mw.visitAnnotation(descriptor(typ), true)
568+
val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
579569
emitAssocs(av, assocs)
580570
}
581571
}
@@ -587,7 +577,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
587577
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
588578
val AnnotationInfo(typ, args, assocs) = annot
589579
assert(args.isEmpty, args)
590-
val av = fw.visitAnnotation(descriptor(typ), true)
580+
val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
591581
emitAssocs(av, assocs)
592582
}
593583
}
@@ -602,7 +592,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
602592
annot <- annots) {
603593
val AnnotationInfo(typ, args, assocs) = annot
604594
assert(args.isEmpty, args)
605-
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true)
595+
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
606596
emitAssocs(pannVisitor, assocs)
607597
}
608598
}
@@ -625,13 +615,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
625615

626616
trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen {
627617

628-
// -----------------------------------------------------------------------------------------
629-
// Static forwarders (related to mirror classes but also present in
630-
// a plain class lacking companion module, for details see `isCandidateForForwarders`).
631-
// -----------------------------------------------------------------------------------------
632-
633-
val ExcludedForwarderFlags = genASM.ExcludedForwarderFlags
634-
635618
/* Adds a @remote annotation, actual use unknown.
636619
*
637620
* Invoked from genMethod() and addForwarder().
@@ -727,7 +710,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
727710
}
728711
debuglog(s"Potentially conflicting names for forwarders: $conflictingNames")
729712

730-
for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD)) {
713+
for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
731714
if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor)
732715
debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
733716
else if (conflictingNames(m.name))

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

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import scala.annotation.tailrec
2020
*
2121
* Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
2222
*/
23-
abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { self =>
23+
abstract class GenASM extends SubComponent with BytecodeWriters { self =>
2424
import global._
2525
import icodes._
2626
import icodes.opcodes._
@@ -99,6 +99,63 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
9999
}
100100
}
101101

102+
private def isJavaEntryPoint(icls: IClass) = {
103+
val sym = icls.symbol
104+
def fail(msg: String, pos: Position = sym.pos) = {
105+
reporter.warning(sym.pos,
106+
sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" +
107+
" Reason: " + msg
108+
// TODO: make this next claim true, if possible
109+
// by generating valid main methods as static in module classes
110+
// not sure what the jvm allows here
111+
// + " You can still run the program by calling it as " + sym.javaSimpleName + " instead."
112+
)
113+
false
114+
}
115+
def failNoForwarder(msg: String) = {
116+
fail(msg + ", which means no static forwarder can be generated.\n")
117+
}
118+
val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil
119+
val hasApproximate = possibles exists { m =>
120+
m.info match {
121+
case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass
122+
case _ => false
123+
}
124+
}
125+
// At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
126+
hasApproximate && {
127+
// Before erasure so we can identify generic mains.
128+
enteringErasure {
129+
val companion = sym.linkedClassOfClass
130+
131+
if (hasJavaMainMethod(companion))
132+
failNoForwarder("companion contains its own main method")
133+
else if (companion.tpe.member(nme.main) != NoSymbol)
134+
// this is only because forwarders aren't smart enough yet
135+
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
136+
else if (companion.isTrait)
137+
failNoForwarder("companion is a trait")
138+
// Now either succeeed, or issue some additional warnings for things which look like
139+
// attempts to be java main methods.
140+
else (possibles exists isJavaMainMethod) || {
141+
possibles exists { m =>
142+
m.info match {
143+
case PolyType(_, _) =>
144+
fail("main methods cannot be generic.")
145+
case MethodType(params, res) =>
146+
if (res.typeSymbol :: params exists (_.isAbstractType))
147+
fail("main methods cannot refer to type parameters or abstract types.", m.pos)
148+
else
149+
isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
150+
case tp =>
151+
fail("don't know what this is: " + tp, m.pos)
152+
}
153+
}
154+
}
155+
}
156+
}
157+
}
158+
102159
override def run() {
103160

104161
if (settings.debug)
@@ -807,15 +864,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
807864
for (ThrownException(exc) <- excs.distinct)
808865
yield javaName(exc)
809866

810-
/** Whether an annotation should be emitted as a Java annotation
811-
* .initialize: if 'annot' is read from pickle, atp might be un-initialized
812-
*/
813-
private def shouldEmitAnnotation(annot: AnnotationInfo) =
814-
annot.symbol.initialize.isJavaDefined &&
815-
annot.matches(ClassfileAnnotationClass) &&
816-
annot.args.isEmpty &&
817-
!annot.matches(DeprecatedAttr)
818-
819867
def getCurrentCUnit(): CompilationUnit
820868

821869
def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit())
@@ -877,7 +925,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
877925
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
878926
val AnnotationInfo(typ, args, assocs) = annot
879927
assert(args.isEmpty, args)
880-
val av = cw.visitAnnotation(descriptor(typ), true)
928+
val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
881929
emitAssocs(av, assocs)
882930
}
883931
}
@@ -886,7 +934,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
886934
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
887935
val AnnotationInfo(typ, args, assocs) = annot
888936
assert(args.isEmpty, args)
889-
val av = mw.visitAnnotation(descriptor(typ), true)
937+
val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
890938
emitAssocs(av, assocs)
891939
}
892940
}
@@ -895,7 +943,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
895943
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
896944
val AnnotationInfo(typ, args, assocs) = annot
897945
assert(args.isEmpty, args)
898-
val av = fw.visitAnnotation(descriptor(typ), true)
946+
val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
899947
emitAssocs(av, assocs)
900948
}
901949
}
@@ -907,7 +955,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
907955
annot <- annots) {
908956
val AnnotationInfo(typ, args, assocs) = annot
909957
assert(args.isEmpty, args)
910-
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true)
958+
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
911959
emitAssocs(pannVisitor, assocs)
912960
}
913961
}

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

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

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,10 @@ trait Definitions extends api.StandardDefinitions {
10871087
lazy val ClassfileAnnotationClass = requiredClass[scala.annotation.ClassfileAnnotation]
10881088
lazy val StaticAnnotationClass = requiredClass[scala.annotation.StaticAnnotation]
10891089

1090+
// Java retention annotations
1091+
lazy val AnnotationRetentionAttr = requiredClass[java.lang.annotation.Retention]
1092+
lazy val AnnotationRetentionPolicyAttr = requiredClass[java.lang.annotation.RetentionPolicy]
1093+
10901094
// Annotations
10911095
lazy val BridgeClass = requiredClass[scala.annotation.bridge]
10921096
lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable]

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,8 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
360360
definitions.AnnotationClass
361361
definitions.ClassfileAnnotationClass
362362
definitions.StaticAnnotationClass
363+
definitions.AnnotationRetentionAttr
364+
definitions.AnnotationRetentionPolicyAttr
363365
definitions.BridgeClass
364366
definitions.ElidableMethodClass
365367
definitions.ImplicitNotFoundClass
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Some(@Ljava/lang/Deprecated;())
2+
None
3+
None
4+
Some(@LCAnnotation;() // invisible)
5+
Some(@LRAnnotation;())
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java.lang.annotation.Retention;
2+
import static java.lang.annotation.RetentionPolicy.CLASS;
3+
4+
@Retention(value=CLASS)
5+
@interface CAnnotation {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@CAnnotation
2+
class C
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@Deprecated
2+
class DJava
3+
4+
@deprecated("", "")
5+
class DScala

0 commit comments

Comments
 (0)