
scala.tools.nsc.backend.jvm.BCodeHelpers.scala Maven / Gradle / Ivy
/* NSC -- new Scala compiler
* Copyright 2005-2012 LAMP/EPFL
* @author Martin Odersky
*/
package scala
package tools.nsc
package backend.jvm
import scala.tools.asm
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
import scala.reflect.internal.Flags
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
*
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded
* @version 1.0
*
*/
abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
import global._
import definitions._
import bTypes._
import coreBTypes._
import BTypes.{InternalName, InlineInfo, MethodInlineInfo}
/**
* True for classes generated by the Scala compiler that are considered top-level in terms of
* the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
*/
def considerAsTopLevelImplementationArtifact(classSym: Symbol) = classSym.isSpecialized
/**
* Cache the value of delambdafy == "inline" for each run. We need to query this value many
* times, so caching makes sense.
*/
object delambdafyInline {
private var runId = -1
private var value = false
def apply(): Boolean = {
if (runId != global.currentRunId) {
runId = global.currentRunId
value = settings.Ydelambdafy.value == "inline"
}
value
}
}
def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
final def traitSuperAccessorName(sym: Symbol): Name = {
val name = sym.javaSimpleName
if (sym.isMixinConstructor) name
else name.append(nme.NAME_JOIN_STRING)
}
/**
* True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
* member class. This method is used to decide if we should emit an EnclosingMethod attribute.
* It is also used to decide whether the "owner" field in the InnerClass attribute should be
* null.
*/
def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
assert(classSym.isClass, s"not a class: $classSym")
val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
if (r) {
// lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true.
// we prevent this, see `nonAnon` in LambdaLift.
// phase travel necessary: after flatten, the name includes the name of outer classes.
// if some outer name contains $lambda, a non-lambda class is considered lambda.
assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
}
r
}
/**
* The next enclosing definition in the source structure. Includes anonymous function classes
* under delambdafy:inline, even though they are only generated during UnCurry.
*/
def nextEnclosing(sym: Symbol): Symbol = {
val origOwner = sym.originalOwner
// phase travel necessary: after flatten, the name includes the name of outer classes.
// if some outer name contains $anon, a non-anon class is considered anon.
if (delambdafyInline() && exitingPickler(sym.rawowner.isAnonymousFunction)) {
// SI-9105: special handling for anonymous functions under delambdafy:inline.
//
// class C { def t = () => { def f { class Z } } }
//
// class C { def t = byNameMethod { def f { class Z } } }
//
// In both examples, the method f lambda-lifted into the anonfun class.
//
// In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
// So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ...
//
// In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
// In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
//
// In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
// above makes sure we don't jump over the anonymous function in the by-name argument case.
//
// However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
// in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
// and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
// we need to return it.
// If the rawowners are different, the symbol was not in between. In the first example, the
// originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
// of `f` is its rawowner, the anonFunClassSym.
//
// In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
// not into the anonymous function class. The originalOwner chain is Z - f - C.
if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
else sym.rawowner
} else {
origOwner
}
}
def nextEnclosingClass(sym: Symbol): Symbol =
if (sym.isClass) sym
else nextEnclosingClass(nextEnclosing(sym))
def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) =
nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
/**
* Returns the enclosing method for non-member classes. In the following example
*
* class A {
* def f = {
* class B {
* class C
* }
* }
* }
*
* the method returns Some(f) for B, but None for C, because C is a member class. For non-member
* classes that are not enclosed by a method, it returns None:
*
* class A {
* { class B }
* }
*
* In this case, for B, we return None.
*
* The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
* This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
*/
private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
assert(classSym.isClass, classSym)
def doesNotExist(method: Symbol) = {
// Value classes. Member methods of value classes exist in the generated box class. However,
// nested methods lifted into a value class are moved to the companion object and don't exist
// in the value class itself. We can identify such nested methods: the initial enclosing class
// is a value class, but the current owner is some other class (the module class).
val enclCls = nextEnclosingClass(method)
exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls
}
def enclosingMethod(sym: Symbol): Option[Symbol] = {
if (sym.isClass || sym == NoSymbol) None
else if (sym.isMethod && !sym.isGetter) {
if (doesNotExist(sym)) None else Some(sym)
}
else enclosingMethod(nextEnclosing(sym))
}
enclosingMethod(nextEnclosing(classSym))
}
/**
* The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
* property, this method looks at the originalOwner chain. See doc in BTypes.
*/
private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
assert(classSym.isClass, classSym)
val r = nextEnclosingClass(nextEnclosing(classSym))
r
}
final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
/**
* Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
* an anonymous or local class). See doc in BTypes.
*
* The class is parametrized by two functions to obtain a bytecode class descriptor for a class
* symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
* on the implementation of GenASM / GenBCode, so they need to be passed in.
*/
def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
// specialized classes are always top-level, see comment in BTypes
if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
Some(EnclosingMethodEntry(
classDesc(enclosingClass),
methodOpt.map(_.javaSimpleName.toString).orNull,
methodOpt.map(methodDesc).orNull))
} else {
None
}
}
/**
* This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
*
* The problem is that we are interested in a source-level property. Various phases changed the
* symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
* Therefore, `sym.isStatic` is not what we want. For example, in
* object T { def f { object U } }
* the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
*/
def isOriginallyStaticOwner(sym: Symbol): Boolean =
sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
/**
* This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
* cannot change the typer context of the completer at this point and make it silent: the context
* captured when creating the completer in the namer. However, we can temporarily replace
* global.reporter (it's a var) to store errors.
*/
def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean =
if (sym.hasCompleteInfo) false
else {
val originalReporter = global.reporter
val storeReporter = new reporters.StoreReporter()
global.reporter = storeReporter
try {
sym.info
} finally {
global.reporter = originalReporter
}
sym.isErroneous
}
/*
* must-single-thread
*/
def getFileForClassfile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
getFile(base, clsName, suffix)
}
/*
* must-single-thread
*/
def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile =
_root_.scala.util.Try {
outputDirectory(csym)
}.recover {
case ex: Throwable =>
reporter.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}")
null
}.get
var pickledBytes = 0 // statistics
// -----------------------------------------------------------------------------------------
// finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
// Background:
// http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf
// http://comments.gmane.org/gmane.comp.java.vm.languages/2293
// https://issues.scala-lang.org/browse/SI-3872
// -----------------------------------------------------------------------------------------
/* An `asm.ClassWriter` that uses `jvmWiseLUB()`
* The internal name of the least common ancestor of the types given by inameA and inameB.
* It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow
*/
final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
/**
* This method is thread-safe: it depends only on the BTypes component, which does not depend
* on global. TODO @lry move to a different place where no global is in scope, on bTypes.
*/
override def getCommonSuperClass(inameA: String, inameB: String): String = {
val a = classBTypeFromInternalName(inameA)
val b = classBTypeFromInternalName(inameB)
val lub = a.jvmWiseLUB(b).get
val lubName = lub.internalName
assert(lubName != "scala/Any")
lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
}
}
/*
* must-single-thread
*/
object isJavaEntryPoint {
/*
* must-single-thread
*/
def apply(sym: Symbol, csymCompUnit: CompilationUnit): Boolean = {
def fail(msg: String, pos: Position = sym.pos) = {
reporter.warning(sym.pos,
sym.name +
s" has a main method with parameter type Array[String], but ${sym.fullName('.')} will not be a runnable program.\n Reason: $msg"
// TODO: make this next claim true, if possible
// by generating valid main methods as static in module classes
// not sure what the jvm allows here
// + " You can still run the program by calling it as " + sym.javaSimpleName + " instead."
)
false
}
def failNoForwarder(msg: String) = {
fail(s"$msg, which means no static forwarder can be generated.\n")
}
val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil
val hasApproximate = possibles exists { m =>
m.info match {
case MethodType(p :: Nil, _) => p.tpe.typeSymbol == definitions.ArrayClass
case _ => false
}
}
// At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
hasApproximate && {
// Before erasure so we can identify generic mains.
enteringErasure {
val companion = sym.linkedClassOfClass
if (definitions.hasJavaMainMethod(companion))
failNoForwarder("companion contains its own main method")
else if (companion.tpe.member(nme.main) != NoSymbol)
// this is only because forwarders aren't smart enough yet
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
else if (companion.isTrait)
failNoForwarder("companion is a trait")
// Now either succeeed, or issue some additional warnings for things which look like
// attempts to be java main methods.
else (possibles exists definitions.isJavaMainMethod) || {
possibles exists { m =>
m.info match {
case PolyType(_, _) =>
fail("main methods cannot be generic.")
case MethodType(params, res) =>
if (res.typeSymbol :: params exists (_.isAbstractType))
fail("main methods cannot refer to type parameters or abstract types.", m.pos)
else
definitions.isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
case tp =>
fail(s"don't know what this is: $tp", m.pos)
}
}
}
}
}
}
}
/*
* must-single-thread
*/
def initBytecodeWriter(entryPoints: List[Symbol]): BytecodeWriter = {
settings.outputDirs.getSingleOutput match {
case Some(f) if f hasExtension "jar" =>
// If no main class was specified, see if there's only one
// entry point among the classes going into the jar.
if (settings.mainClass.isDefault) {
entryPoints map (_.fullName('.')) match {
case Nil =>
log("No Main-Class designated or discovered.")
case name :: Nil =>
log(s"Unique entry point: setting Main-Class to $name")
settings.mainClass.value = name
case names =>
log(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}")
}
}
else log(s"Main-Class was specified: ${settings.mainClass.value}")
new DirectToJarfileWriter(f.file)
case _ => factoryNonJarBytecodeWriter()
}
}
/*
* must-single-thread
*/
def fieldSymbols(cls: Symbol): List[Symbol] = {
for (f <- cls.info.decls.toList ;
if !f.isMethod && f.isTerm && !f.isModule
) yield f
}
/*
* can-multi-thread
*/
def methodSymbols(cd: ClassDef): List[Symbol] = {
cd.impl.body collect { case dd: DefDef => dd.symbol }
}
/*
* must-single-thread
*/
def serialVUID(csym: Symbol): Option[Long] = csym getAnnotation definitions.SerialVersionUIDAttr collect {
case AnnotationInfo(_, _, (_, LiteralAnnotArg(const)) :: Nil) => const.longValue
}
/*
* Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
* classes in BTypes.scala.
*
* `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
* each inner class it lists (those are looked up and included).
*
* This method serializes in the InnerClasses JVM attribute in an appropriate order, not
* necessarily that given by `refedInnerClasses`.
*
* can-multi-thread
*/
final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) {
// Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
val Some(e) = nestedClass.innerClassAttributeEntry.get
jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
}
}
/*
* Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only
* i.e., the pickle is contained in a custom annotation, see:
* (1) `addAnnotations()`,
* (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10
* (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5
* That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9)
* other than both ending up encoded as attributes (JVMS 4.7)
* (with the caveat that the "ScalaSig" attribute is associated to some classes,
* while the "Signature" attribute can be associated to classes, methods, and fields.)
*
*/
trait BCPickles {
import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
val versionPickle = {
val vp = new PickleBuffer(new Array[Byte](16), -1, 0)
assert(vp.writeIndex == 0, vp)
vp writeNat PickleFormat.MajorVersion
vp writeNat PickleFormat.MinorVersion
vp writeNat 0
vp
}
/*
* can-multi-thread
*/
def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = {
val dest = new Array[Byte](len)
System.arraycopy(b, offset, dest, 0, len)
new asm.CustomAttr(name, dest)
}
/*
* can-multi-thread
*/
def pickleMarkerLocal = {
createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
}
/*
* can-multi-thread
*/
def pickleMarkerForeign = {
createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0)
}
/* Returns a ScalaSignature annotation if it must be added to this class, none otherwise.
* This annotation must be added to the class' annotations list when generating them.
*
* Depending on whether the returned option is defined, it adds to `jclass` one of:
* (a) the ScalaSig marker attribute
* (indicating that a scala-signature-annotation aka pickle is present in this class); or
* (b) the Scala marker attribute
* (indicating that a scala-signature-annotation aka pickle is to be found in another file).
*
*
* @param jclassName The class file that is being readied.
* @param sym The symbol for which the signature has been entered in the symData map.
* This is different than the symbol
* that is being generated in the case of a mirror class.
* @return An option that is:
* - defined and contains an AnnotationInfo of the ScalaSignature type,
* instantiated with the pickle signature for sym.
* - empty if the jclass/sym pair must not contain a pickle.
*
* must-single-thread
*/
def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = {
currentRun.symData get sym match {
case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) =>
val scalaAnnot = {
val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex))
AnnotationInfo(sigBytes.sigAnnot, Nil, (nme.bytes, sigBytes) :: Nil)
}
pickledBytes += pickle.writeIndex
currentRun.symData -= sym
currentRun.symData -= sym.companionSymbol
Some(scalaAnnot)
case _ =>
None
}
}
} // end of trait BCPickles
trait BCInnerClassGen {
def debugLevel = settings.debuginfo.indexOfChoice
final val emitSource = debugLevel >= 1
final val emitLines = debugLevel >= 2
final val emitVars = debugLevel >= 3
/**
* The class internal name for a given class symbol.
*/
final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
} // end of trait BCInnerClassGen
trait BCAnnotGen extends BCInnerClassGen {
private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
/**
* Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the
* typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding
* symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure).
*
* For Scala annotations this is OK: they are stored in the pickle and ignored by the backend.
* Java annoations on the other hand are additionally emitted to the classfile in Java's format.
*
* This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples:
* - @(javax.annotation.Resource @annotation.meta.getter) val x = 0
* Here, annotationInfo.atp is an AnnotatedType.
* - @SomeAnnotation[T] val x = 0
* In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot
* actually happen because Java annotations cannot be generic.
* - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0
* The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the
* non-erased existential type.
*/
def erasedType(tp: Type): Type = enteringErasure {
// make sure we don't erase value class references to the type that the value class boxes
// this is basically the same logic as in erasure's preTransform, case Literal(classTag).
tp.dealiasWiden match {
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr)
case tpe => erasure.erasure(tpe.typeSymbol)(tpe)
}
}
def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor
/** Whether an annotation should be emitted as a Java annotation
* .initialize: if 'annot' is read from pickle, atp might be uninitialized
*/
private def shouldEmitAnnotation(annot: AnnotationInfo) = {
annot.symbol.initialize.isJavaDefined &&
annot.matches(ClassfileAnnotationClass) &&
retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
annot.args.isEmpty
}
private def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
case Some(retentionAnnot) =>
retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
case _ =>
// SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
// annotation is emitted with visibility `RUNTIME`
true
}
}
private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
assoc.collectFirst {
case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
}).getOrElse(AnnotationRetentionPolicyClassValue)
def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
val ca = new Array[Char](bytes.length)
var idx = 0
while(idx < bytes.length) {
val b: Byte = bytes(idx)
assert((b & ~0x7f) == 0)
ca(idx) = b.asInstanceOf[Char]
idx += 1
}
ca
}
final def arrEncode(sb: ScalaSigBytes): Array[String] = {
var strs: List[String] = Nil
val bSeven: Array[Byte] = sb.sevenBitsMayBeZero
// chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure)
var prevOffset = 0
var offset = 0
var encLength = 0
while(offset < bSeven.length) {
val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1)
val newEncLength = encLength.toLong + deltaEncLength
if(newEncLength >= 65535) {
val ba = bSeven.slice(prevOffset, offset)
strs ::= new java.lang.String(ubytesToCharArray(ba))
encLength = 0
prevOffset = offset
} else {
encLength += deltaEncLength
offset += 1
}
}
if(prevOffset < offset) {
assert(offset == bSeven.length)
val ba = bSeven.slice(prevOffset, offset)
strs ::= new java.lang.String(ubytesToCharArray(ba))
}
assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict?
strs.reverse.toArray
}
/*
* can-multi-thread
*/
private def strEncode(sb: ScalaSigBytes): String = {
val ca = ubytesToCharArray(sb.sevenBitsMayBeZero)
new java.lang.String(ca)
// debug val bvA = new asm.ByteVector; bvA.putUTF8(s)
// debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes)
// debug assert(enc(idx) == bvA.getByte(idx + 2))
// debug assert(bvA.getLength == enc.size + 2)
}
/*
* For arg a LiteralAnnotArg(constt) with const.tag in {ClazzTag, EnumTag}
* as well as for arg a NestedAnnotArg
* must-single-thread
* Otherwise it's safe to call from multiple threads.
*/
def emitArgument(av: asm.AnnotationVisitor,
name: String,
arg: ClassfileAnnotArg) {
(arg: @unchecked) match {
case LiteralAnnotArg(const) =>
if (const.isNonUnitAnyVal) { av.visit(name, const.value) }
else {
const.tag match {
case StringTag =>
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
case ClazzTag =>
av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType)
case EnumTag =>
val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class.
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
}
}
case sb @ ScalaSigBytes(bytes) =>
// see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files)
// also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure.
if (sb.fitsInOneString) {
av.visit(name, strEncode(sb))
} else {
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
arrAnnotV.visitEnd()
} // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
case ArrayAnnotArg(args) =>
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
for(arg <- args) { emitArgument(arrAnnotV, null, arg) }
arrAnnotV.visitEnd()
case NestedAnnotArg(annInfo) =>
val AnnotationInfo(typ, args, assocs) = annInfo
assert(args.isEmpty, args)
val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
val nestedVisitor = av.visitAnnotation(name, desc)
emitAssocs(nestedVisitor, assocs)
}
}
/*
* In general,
* must-single-thread
* but not necessarily always.
*/
def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
for ((name, value) <- assocs) {
emitArgument(av, name.toString(), value)
}
av.visitEnd()
}
/*
* must-single-thread
*/
def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
/*
* must-single-thread
*/
def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
/*
* must-single-thread
*/
def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
/*
* must-single-thread
*/
def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) {
val annotationss = pannotss map (_ filter shouldEmitAnnotation)
if (annotationss forall (_.isEmpty)) return
for ((annots, idx) <- annotationss.zipWithIndex;
annot <- annots) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(pannVisitor, assocs)
}
}
/*
* must-single-thread
*/
def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = {
for (param <- params) {
var access = asm.Opcodes.ACC_FINAL
if (param.isArtifact)
access |= asm.Opcodes.ACC_SYNTHETIC
jmethod.visitParameter(param.name.decoded, access)
}
}
} // end of trait BCAnnotGen
trait BCJGenSigGen {
// @M don't generate java generics sigs for (members of) implementation
// classes, as they are monomorphic (TODO: ok?)
private def needsGenericSignature(sym: Symbol) = !(
// PP: This condition used to include sym.hasExpandedName, but this leads
// to the total loss of generic information if a private member is
// accessed from a closure: both the field and the accessor were generated
// without it. This is particularly bad because the availability of
// generic information could disappear as a consequence of a seemingly
// unrelated change.
settings.Ynogenericsig
|| sym.isArtifact
|| sym.isLiftedMethod
|| sym.isBridge
)
/* @return
* - `null` if no Java signature is to be added (`null` is what ASM expects in these cases).
* - otherwise the signature in question
*
* must-single-thread
*/
def getGenericSignature(sym: Symbol, owner: Symbol): String = {
val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
getGenericSignature(sym, owner, memberTpe)
}
def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = {
if (!needsGenericSignature(sym)) { return null }
val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe)
if (jsOpt.isEmpty) { return null }
val sig = jsOpt.get
log(sig) // This seems useful enough in the general case.
def wrap(op: => Unit) = {
try { op; true }
catch { case _: Throwable => false }
}
if (settings.Xverify) {
// Run the signature parser to catch bogus signatures.
val isValidSignature = wrap {
// Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser)
import scala.tools.asm.util.CheckClassAdapter
if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar
else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig }
else { CheckClassAdapter checkClassSignature sig }
}
if(!isValidSignature) {
reporter.warning(sym.pos,
sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName}
|signature: $sig
|if this is reproducible, please report bug at https://issues.scala-lang.org/
""".trim)
return null
}
}
if ((settings.check containsName phaseName)) {
val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe))
val bytecodeTpe = owner.thisType.memberInfo(sym)
if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
reporter.warning(sym.pos,
sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure
|signature: $sig
|original type: $memberTpe
|normalized type: $normalizedTpe
|erasure type: $bytecodeTpe
|if this is reproducible, please report bug at http://issues.scala-lang.org/
""".trim)
return null
}
}
sig
}
} // end of trait BCJGenSigGen
trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen {
/* Adds a @remote annotation, actual use unknown.
*
* Invoked from genMethod() and addForwarder().
*
* must-single-thread
*/
def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
def hasThrowsRemoteException = meth.annotations.exists {
case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass
case _ => false
}
val needsAnnotation = {
(isRemoteClass ||
isRemote(meth) && isJMethodPublic
) && !hasThrowsRemoteException
}
if (needsAnnotation) {
val c = Constant(definitions.RemoteExceptionClass.tpe)
val arg = Literal(c) setType c.tpe
meth.addAnnotation(appliedType(definitions.ThrowsClass, c.tpe), arg)
}
}
/* Add a forwarder for method m. Used only from addForwarders().
*
* must-single-thread
*/
private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, moduleClass: Symbol, m: Symbol): Unit = {
def staticForwarderGenericSignature: String = {
// SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to.
// By rights, it should use the signature as-seen-from the module class, and add suitable
// primitive and value-class boxing/unboxing.
// But for now, just like we did in mixin, we just avoid writing a wrong generic signature
// (one that doesn't erase to the actual signature). See run/t3452b for a test case.
val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(m))
val erasedMemberType = erasure.erasure(m)(memberTpe)
if (erasedMemberType =:= m.info)
getGenericSignature(m, moduleClass, memberTpe)
else null
}
val moduleName = internalName(moduleClass)
val methodInfo = moduleClass.thisType.memberInfo(m)
val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType
// val paramNames = 0 until paramJavaTypes.length map ("x_" + _)
/* Forwarders must not be marked final,
* as the JVM will not allow redefinition of a final static method,
* and we don't know what classes might be subclassing the companion class. See SI-4827.
*/
// TODO: evaluate the other flags we might be dropping on the floor here.
// TODO: ACC_SYNTHETIC ?
val flags = GenBCode.PublicStatic | (
if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0
)
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
val jgensig = staticForwarderGenericSignature
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass)
val thrownExceptions: List[String] = getExceptions(throws)
val jReturnType = typeToBType(methodInfo.resultType)
val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor
val mirrorMethodName = m.javaSimpleName.toString
val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
flags,
mirrorMethodName,
mdesc,
jgensig,
mkArray(thrownExceptions)
)
emitAnnotations(mirrorMethod, others)
emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))
mirrorMethod.visitCode()
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(moduleClass).descriptor)
var index = 0
for(jparamType <- paramJavaTypes) {
mirrorMethod.visitVarInsn(jparamType.typedOpcode(asm.Opcodes.ILOAD), index)
assert(!jparamType.isInstanceOf[MethodBType], jparamType)
index += jparamType.size
}
mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false)
mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN))
mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
mirrorMethod.visitEnd()
}
/* Add forwarders for all methods defined in `module` that don't conflict
* with methods in the companion class of `module`. A conflict arises when
* a method with the same name is defined both in a class and its companion object:
* method signature is not taken into account.
*
* must-single-thread
*/
def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
assert(moduleClass.isModuleClass, moduleClass)
debuglog(s"Dumping mirror class for object: $moduleClass")
val linkedClass = moduleClass.companionClass
lazy val conflictingNames: Set[Name] = {
(linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet
}
debuglog(s"Potentially conflicting names for forwarders: $conflictingNames")
for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor)
debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}")
else if (conflictingNames(m.name))
log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}")
else if (m.hasAccessBoundary)
log(s"No forwarder for non-public member $m")
else {
log(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'")
addForwarder(isRemoteClass, jclass, moduleClass, m)
}
}
}
/*
* Quoting from JVMS 4.7.5 The Exceptions Attribute
* "The Exceptions attribute indicates which checked exceptions a method may throw.
* There may be at most one Exceptions attribute in each method_info structure."
*
* The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod()
* This method returns such list of internal names.
*
* must-single-thread
*/
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
for (ThrownException(tp) <- excs.distinct)
yield {
val erased = erasedType(tp)
internalName(erased.typeSymbol)
}
}
} // end of trait BCForwardersGen
trait BCClassGen extends BCInnerClassGen {
// Used as threshold above which a tableswitch bytecode instruction is preferred over a lookupswitch.
// There's a space tradeoff between these multi-branch instructions (details in the JVM spec).
// The particular value in use for `MIN_SWITCH_DENSITY` reflects a heuristic.
val MIN_SWITCH_DENSITY = 0.7
/*
* Add public static final field serialVersionUID with value `id`
*
* can-multi-thread
*/
def addSerialVUID(id: Long, jclass: asm.ClassVisitor) {
// add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
jclass.visitField(
GenBCode.PublicStaticFinal,
"serialVersionUID",
"J",
null, // no java-generic-signature
new java.lang.Long(id)
).visitEnd()
}
} // end of trait BCClassGen
/* functionality for building plain and mirror classes */
abstract class JCommonBuilder
extends BCInnerClassGen
with BCAnnotGen
with BCForwardersGen
with BCPickles { }
/* builder of mirror classes */
class JMirrorBuilder extends JCommonBuilder {
/* Generate a mirror class for a top-level module. A mirror class is a class
* containing only static methods that forward to the corresponding method
* on the MODULE instance of the given Scala object. It will only be
* generated if there is no companion class: if there is, an attempt will
* instead be made to add the forwarder methods to the companion class.
*
* must-single-thread
*/
def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
assert(moduleClass.isModuleClass)
assert(moduleClass.companionClass == NoSymbol, moduleClass)
val bType = mirrorClassClassBType(moduleClass)
val mirrorClass = new asm.tree.ClassNode
mirrorClass.visit(
classfileVersion,
bType.info.get.flags,
bType.internalName,
null /* no java-generic-signature */,
ObjectRef.internalName,
EMPTY_STRING_ARRAY
)
if (emitSource)
mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */)
val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol)
mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa)
addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
mirrorClass.visitEnd()
("" + moduleClass.name) // this side-effect is necessary, really.
mirrorClass
}
} // end of class JMirrorBuilder
/* builder of bean info classes */
class JBeanInfoBuilder extends BCInnerClassGen {
/*
* Generate a bean info class that describes the given class.
*
* @author Ross Judson ([email protected])
*
* must-single-thread
*/
def genBeanInfoClass(cls: Symbol, cunit: CompilationUnit, fieldSymbols: List[Symbol], methodSymbols: List[Symbol]): asm.tree.ClassNode = {
def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString }
val beanInfoType = beanInfoClassClassBType(cls)
val beanInfoClass = new asm.tree.ClassNode
beanInfoClass.visit(
classfileVersion,
beanInfoType.info.get.flags,
beanInfoType.internalName,
null, // no java-generic-signature
sbScalaBeanInfoRef.internalName,
EMPTY_STRING_ARRAY
)
beanInfoClass.visitSource(
cunit.source.toString,
null /* SourceDebugExtension */
)
var fieldList = List[String]()
for (f <- fieldSymbols if f.hasGetter;
g = f.getterIn(cls);
s = f.setterIn(cls);
if g.isPublic && !(f.name startsWith "$")
) {
// inserting $outer breaks the bean
fieldList = javaSimpleName(f) :: javaSimpleName(g) :: (if (s != NoSymbol) javaSimpleName(s) else null) :: fieldList
}
val methodList: List[String] =
for (m <- methodSymbols
if !m.isConstructor &&
m.isPublic &&
!(m.name startsWith "$") &&
!m.isGetter &&
!m.isSetter)
yield javaSimpleName(m)
val constructor = beanInfoClass.visitMethod(
asm.Opcodes.ACC_PUBLIC,
INSTANCE_CONSTRUCTOR_NAME,
"()V",
null, // no java-generic-signature
EMPTY_STRING_ARRAY // no throwable exceptions
)
val stringArrayJType: BType = ArrayBType(StringRef)
val conJType: BType = MethodBType(
classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil,
UNIT
)
def push(lst: List[String]) {
var fi = 0
for (f <- lst) {
constructor.visitInsn(asm.Opcodes.DUP)
constructor.visitLdcInsn(new java.lang.Integer(fi))
if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) }
else { constructor.visitLdcInsn(f) }
constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE))
fi += 1
}
}
constructor.visitCode()
constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
// push the class
constructor.visitLdcInsn(classBTypeFromSymbol(cls).toASMType)
// push the string array of field information
constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(fieldList)
// push the string array of method information
constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(methodList)
// invoke the superclass constructor, which will do the
// necessary java reflection and create Method objects.
constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.descriptor, false)
constructor.visitInsn(asm.Opcodes.RETURN)
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
beanInfoClass.visitEnd()
beanInfoClass
}
} // end of class JBeanInfoBuilder
trait JAndroidBuilder {
self: BCInnerClassGen =>
/* From the reference documentation of the Android SDK:
* The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`.
* Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`,
* which is an object implementing the `Parcelable.Creator` interface.
*/
val androidFieldName = newTermName("CREATOR")
/*
* must-single-thread
*/
def isAndroidParcelableClass(sym: Symbol) =
(AndroidParcelableInterface != NoSymbol) &&
(sym.parentSymbols contains AndroidParcelableInterface)
/*
* must-single-thread
*/
def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) {
val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass)
val tdesc_creator = androidCreatorType.descriptor
cnode.visitField(
GenBCode.PublicStaticFinal,
"CREATOR",
tdesc_creator,
null, // no java-generic-signature
null // no initial value
).visitEnd()
val moduleName = (thisName + "$")
// GETSTATIC `moduleName`.MODULE$ : `moduleName`;
clinit.visitFieldInsn(
asm.Opcodes.GETSTATIC,
moduleName,
strMODULE_INSTANCE_FIELD,
"L" + moduleName + ";"
)
// INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
val bt = MethodBType(Nil, androidCreatorType)
clinit.visitMethodInsn(
asm.Opcodes.INVOKEVIRTUAL,
moduleName,
"CREATOR",
bt.descriptor,
false
)
// PUTSTATIC `thisName`.CREATOR;
clinit.visitFieldInsn(
asm.Opcodes.PUTSTATIC,
thisName,
"CREATOR",
tdesc_creator
)
}
} // end of trait JAndroidBuilder
}
object BCodeHelpers {
val ExcludedForwarderFlags = {
import scala.tools.nsc.symtab.Flags._
// Should include DEFERRED but this breaks findMember.
SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
}
/**
* Valid flags for InnerClass attribute entry.
* See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
*/
val INNER_CLASSES_FLAGS = {
asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE |
asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
asm.Opcodes.ACC_ENUM
}
class TestOp(val op: Int) extends AnyVal {
import TestOp._
def negate = this match {
case EQ => NE
case NE => EQ
case LT => GE
case GE => LT
case GT => LE
case LE => GT
}
def opcodeIF = asm.Opcodes.IFEQ + op
def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op
}
object TestOp {
// the order here / op numbers are important to get the correct result when calling opcodeIF
val EQ = new TestOp(0)
val NE = new TestOp(1)
val LT = new TestOp(2)
val GE = new TestOp(3)
val GT = new TestOp(4)
val LE = new TestOp(5)
}
class InvokeStyle(val style: Int) extends AnyVal {
import InvokeStyle._
def isVirtual: Boolean = this == Virtual
def isStatic : Boolean = this == Static
def isSpecial: Boolean = this == Special
def isSuper : Boolean = this == Super
def hasInstance = this != Static
}
object InvokeStyle {
val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
val Static = new InvokeStyle(1) // InvokeStatic
val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy