/* 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 parameterized 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 succeed, 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 annotations on the other hand are additionally emitted to the classfile in Java's format. * * This means that [[Type]] instances within an AnnotationInfo 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 (ross.judson@soletta.com) * * 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) } }