All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.nsc.backend.jvm.BCodeHelpers.scala Maven / Gradle / Ivy

There is a newer version: 2.11.2
Show newest version
/* 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.annotation.switch
import scala.collection.{ immutable, mutable }
import scala.tools.nsc.io.AbstractFile

/*
 *  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 BCodeTypes with BytecodeWriters {

  import global._

  /*
   * 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 = {
    try {
      outputDirectory(csym)
    } catch {
      case ex: Throwable =>
        cunit.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}")
        null
    }
  }

  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
  // -----------------------------------------------------------------------------------------

  /*
   * can-multi-thread
   */
  def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): BType = {
    var chainA = as
    var chainB = bs
    var fcs: Tracked = null
    do {
      if      (chainB contains chainA.head) fcs = chainA.head
      else if (chainA contains chainB.head) fcs = chainB.head
      else {
        chainA = chainA.tail
        chainB = chainB.tail
      }
    } while (fcs == null)
    fcs.c
  }

  /*  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 re-entrant because chrs never grows during its operation (that's because all TypeNames being looked up have already been entered).
     *  To stress this point, rather than using `newTypeName()` we use `lookupTypeName()`
     *
     *  can-multi-thread
     */
    override def getCommonSuperClass(inameA: String, inameB: String): String = {
      val a = brefType(lookupTypeName(inameA.toCharArray))
      val b = brefType(lookupTypeName(inameB.toCharArray))
      val lca = jvmWiseLUB(a, b)
      val lcaName = lca.getInternalName // don't call javaName because that side-effects innerClassBuffer.
      assert(lcaName != "scala/Any")

      lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
    }

  }

  /*
   *  Finding the least upper bound in agreement with the bytecode verifier (given two internal names handed out 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
   *
   *  can-multi-thread
   */
  def jvmWiseLUB(a: BType, b: BType): BType = {

    assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a")
    assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b")

    val ta = exemplars.get(a)
    val tb = exemplars.get(b)

    val res = (ta.isInterface, tb.isInterface) match {
      case (true, true) =>
        // exercised by test/files/run/t4761.scala
        if      (tb.isSubtypeOf(ta.c)) ta.c
        else if (ta.isSubtypeOf(tb.c)) tb.c
        else ObjectReference
      case (true, false) =>
        if (tb.isSubtypeOf(a)) a else ObjectReference
      case (false, true) =>
        if (ta.isSubtypeOf(b)) b else ObjectReference
      case _ =>
        firstCommonSuffix(ta :: ta.superClasses, tb :: tb.superClasses)
    }
    assert(res.isNonSpecial, "jvmWiseLUB() returned a non-plain-class.")
    res
  }

  /*
   * must-single-thread
   */
  object isJavaEntryPoint {

    /*
     * must-single-thread
     */
    def apply(sym: Symbol, csymCompUnit: CompilationUnit): Boolean = {
      def fail(msg: String, pos: Position = sym.pos) = {
        csymCompUnit.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`.
   * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted)
   * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass`
   * but otherwise not mentioned in `jclass`.
   *
   * `refedInnerClasses` may contain duplicates,
   * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency).
   *
   * This method serializes in the InnerClasses JVM attribute in an appropriate order,
   * not necessarily that given by `refedInnerClasses`.
   *
   * can-multi-thread
   */
  final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: Iterable[BType]) {
    // used to detect duplicates.
    val seen = mutable.Map.empty[String, String]
    // result without duplicates, not yet sorted.
    val result = mutable.Set.empty[InnerClassEntry]

    for(s: BType           <- refedInnerClasses;
        e: InnerClassEntry <- exemplars.get(s).innersChain) {

      assert(e.name != null, "saveInnerClassesFor() is broken.") // documentation
      val doAdd = seen.get(e.name) match {
        // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute)
        case Some(prevOName) =>
          // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State,
          // i.e. for them it must be the case that oname == java/lang/Thread
          assert(prevOName == e.outerName, "duplicate")
          false
        case None => true
      }

      if (doAdd) {
        seen   += (e.name -> e.outerName)
        result += e
      }

    }
    // sorting ensures inner classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
    for(e <- result.toList sortBy (_.name.toString)) {
      jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.access)
    }

  } // end of method addInnerClassesASM()

  /*
   * 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

    val emitSource = debugLevel >= 1
    val emitLines  = debugLevel >= 2
    val emitVars   = debugLevel >= 3

    /*
     *  Contains class-symbols that:
     *    (a) are known to denote inner classes
     *    (b) are mentioned somewhere in the class being generated.
     *
     *  In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated".
     */
    val innerClassBufferASM = mutable.Set.empty[BType]

    /*
     *  Tracks (if needed) the inner class given by `sym`.
     *
     *  must-single-thread
     */
    final def internalName(sym: Symbol): String = { asmClassType(sym).getInternalName }

    /*
     *  Tracks (if needed) the inner class given by `sym`.
     *
     *  must-single-thread
     */
    final def asmClassType(sym: Symbol): BType = {
      assert(
        hasInternalName(sym),
        {
          val msg0 = if (sym.isAbstractType) "An AbstractTypeSymbol (SI-7122) " else "A symbol ";
          msg0 + s"has reached the bytecode emitter, for which no JVM-level internal name can be found: ${sym.fullName}"
        }
      )
      val phantOpt = phantomTypeMap.get(sym)
      if (phantOpt.isDefined) {
        return phantOpt.get
      }
      val tracked = exemplar(sym)
      val tk = tracked.c
      if (tracked.isInnerClass) {
        innerClassBufferASM += tk
      }

      tk
    }

    /*
     *  Returns the BType for the given type.
     *  Tracks (if needed) the inner class given by `t`.
     *
     * must-single-thread
     */
    final def toTypeKind(t: Type): BType = {

      /* Interfaces have to be handled delicately to avoid introducing spurious errors,
       *  but if we treat them all as AnyRef we lose too much information.
       */
      def newReference(sym0: Symbol): BType = {
        assert(!primitiveTypeMap.contains(sym0), "Use primitiveTypeMap instead.")
        assert(sym0 != definitions.ArrayClass,   "Use arrayOf() instead.")

        if (sym0 == definitions.NullClass)    return RT_NULL;
        if (sym0 == definitions.NothingClass) return RT_NOTHING;

        val sym = (
          if (!sym0.isPackageClass) sym0
          else sym0.info.member(nme.PACKAGE) match {
            case NoSymbol => abort(s"SI-5604: Cannot use package as value: ${sym0.fullName}")
            case s        => abort(s"SI-5604: found package class where package object expected: $s")
          }
        )

        // Can't call .toInterface (at this phase) or we trip an assertion.
        // See PackratParser#grow for a method which fails with an apparent mismatch
        // between "object PackratParsers$class" and "trait PackratParsers"
        if (sym.isImplClass) {
          // pos/spec-List.scala is the sole failure if we don't check for NoSymbol
          val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name))
          if (traitSym != NoSymbol) {
            // this tracks the inner class in innerClassBufferASM, if needed.
            return asmClassType(traitSym)
          }
        }

        assert(hasInternalName(sym), s"Invoked for a symbol lacking JVM internal name: ${sym.fullName}")
        assert(!phantomTypeMap.contains(sym), "phantom types not supposed to reach here.")

        val tracked = exemplar(sym)
        val tk = tracked.c
        if (tracked.isInnerClass) {
          innerClassBufferASM += tk
        }

        tk
      }

      def primitiveOrRefType(sym: Symbol): BType = {
        assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.")

        primitiveTypeMap.getOrElse(sym, newReference(sym))
      }

      def primitiveOrRefType2(sym: Symbol): BType = {
        primitiveTypeMap.get(sym) match {
          case Some(pt) => pt
          case None =>
            sym match {
              case definitions.NullClass    => RT_NULL
              case definitions.NothingClass => RT_NOTHING
              case _ if sym.isClass         => newReference(sym)
              case _ =>
                assert(sym.isType, sym) // it must be compiling Array[a]
                ObjectReference
            }
        }
      }

      import definitions.ArrayClass

      // Call to .normalize fixes #3003 (follow type aliases). Otherwise, primitiveOrArrayOrRefType() would return ObjectReference.
      t.normalize match {

        case ThisType(sym) =>
          if (sym == ArrayClass) ObjectReference
          else                   phantomTypeMap.getOrElse(sym, exemplar(sym).c)

        case SingleType(_, sym) => primitiveOrRefType(sym)

        case _: ConstantType    => toTypeKind(t.underlying)

        case TypeRef(_, sym, args)    =>
          if (sym == ArrayClass) arrayOf(toTypeKind(args.head))
          else                   primitiveOrRefType2(sym)

        case ClassInfoType(_, _, sym) =>
          assert(sym != ArrayClass, "ClassInfoType to ArrayClass!")
          primitiveOrRefType(sym)

        // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType.
        case ExistentialType(_, t)   => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala
        case AnnotatedType(_, w)     => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here.
        case RefinedType(parents, _) => parents map toTypeKind reduceLeft jvmWiseLUB

        // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy.
        // case WildcardType    => REFERENCE(ObjectClass)
        case norm => abort(
          s"Unknown type: $t, $norm [${t.getClass}, ${norm.getClass}] TypeRef? ${t.isInstanceOf[TypeRef]}"
        )
      }

    } // end of method toTypeKind()

    /*
     * must-single-thread
     */
    def asmMethodType(msym: Symbol): BType = {
      assert(msym.isMethod, s"not a method-symbol: $msym")
      val resT: BType =
        if (msym.isClassConstructor || msym.isConstructor) BType.VOID_TYPE
        else toTypeKind(msym.tpe.resultType);
      BType.getMethodType( resT, mkArray(msym.tpe.paramTypes map toTypeKind) )
    }

    /*
     *  Returns all direct member inner classes of `csym`,
     *  thus making sure they get entries in the InnerClasses JVM attribute
     *  even if otherwise not mentioned in the class being built.
     *
     *  must-single-thread
     */
    final def trackMemberClasses(csym: Symbol, lateClosuresBTs: List[BType]): List[BType] = {
      val lateInnerClasses = exitingErasure {
        for (sym <- List(csym, csym.linkedClassOfClass); memberc <- sym.info.decls.map(innerClassSymbolFor) if memberc.isClass)
        yield memberc
      }
      // as a precaution, do the following outside the above `exitingErasure` otherwise funny internal names might be computed.
      val result = for(memberc <- lateInnerClasses) yield {
        val tracked = exemplar(memberc)
        val memberCTK = tracked.c
        assert(tracked.isInnerClass, s"saveInnerClassesFor() says this was no inner-class after all: ${memberc.fullName}")

        memberCTK
      }

      exemplar(csym).directMemberClasses = result

      result
    }

    /*
     *  Tracks (if needed) the inner class given by `t`.
     *
     *  must-single-thread
     */
    final def descriptor(t: Type):   String = { toTypeKind(t).getDescriptor   }

    /*
     *  Tracks (if needed) the inner class given by `sym`.
     *
     *  must-single-thread
     */
    final def descriptor(sym: Symbol): String = { asmClassType(sym).getDescriptor }

  } // end of trait BCInnerClassGen

  trait BCAnnotGen extends BCInnerClassGen {

    import genASM.{ubytesToCharArray, arrEncode}

    /*
     *  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, toTypeKind(const.typeValue).toASMType)
              case EnumTag =>
                val edesc  = descriptor(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 <- genASM.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 = descriptor(typ) // the class descriptor of the nested annotation class
          val nestedVisitor = av.visitAnnotation(name, desc)
          emitAssocs(nestedVisitor, assocs)
      }
    }

    /* Whether an annotation should be emitted as a Java annotation
     *   .initialize: if 'annot' is read from pickle, atp might be un-initialized
     *
     * must-single-thread
     */
    private def shouldEmitAnnotation(annot: AnnotationInfo) =
      annot.symbol.initialize.isJavaDefined &&
      annot.matches(definitions.ClassfileAnnotationClass) &&
      annot.args.isEmpty &&
      !annot.matches(definitions.DeprecatedAttr)

    /*
     * 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(descriptor(typ), true)
        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(descriptor(typ), true)
        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(descriptor(typ), true)
        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, descriptor(typ), true)
        emitAssocs(pannVisitor, assocs)
      }
    }

  } // end of trait BCAnnotGen

  trait BCJGenSigGen {

    def getCurrentCUnit(): CompilationUnit

    /* @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 = genASM.getGenericSignature(sym, owner, getCurrentCUnit())

  } // end of trait BCJGenSigGen

  trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen {

    // -----------------------------------------------------------------------------------------
    // Static forwarders (related to mirror classes but also present in
    // a plain class lacking companion module, for details see `isCandidateForForwarders`).
    // -----------------------------------------------------------------------------------------

    val ExcludedForwarderFlags = genASM.ExcludedForwarderFlags

    /* Adds a @remote annotation, actual use unknown.
     *
     * Invoked from genMethod() and addForwarder().
     *
     * must-single-thread
     */
    def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
      val needsAnnotation = (
        (  isRemoteClass ||
           isRemote(meth) && isJMethodPublic
        ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass)
      )
      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, module: Symbol, m: Symbol) {
      val moduleName     = internalName(module)
      val methodInfo     = module.thisType.memberInfo(m)
      val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind
      // 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 = PublicStatic | (
        if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0
      )

      // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
      val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit())
      addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
      val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass)
      val thrownExceptions: List[String] = getExceptions(throws)

      val jReturnType = toTypeKind(methodInfo.resultType)
      val mdesc = BType.getMethodType(jReturnType, mkArray(paramJavaTypes)).getDescriptor
      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, descriptor(module))

      var index = 0
      for(jparamType <- paramJavaTypes) {
        mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index)
        assert(jparamType.sort != BType.METHOD, jparamType)
        index += jparamType.getSize
      }

      mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor)
      mirrorMethod.visitInsn(jReturnType.getOpcode(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(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'")
        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(exc) <- excs.distinct)
      yield internalName(exc)
    }

  } // 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(
        PublicStaticFinal,
        "serialVersionUID",
        "J",
        null, // no java-generic-signature
        new java.lang.Long(id)
      ).visitEnd()
    }

    /*
     * @param owner internal name of the enclosing class of the class.
     *
     * @param name the name of the method that contains the class.

     * @param methodType the method that contains the class.
     */
    case class EnclMethodEntry(owner: String, name: String, methodType: BType)

    /*
     * @return null if the current class is not internal to a method
     *
     * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute
     *   A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class.
     *   A class may have no more than one EnclosingMethod attribute.
     *
     * must-single-thread
     */
    def getEnclosingMethodAttribute(clazz: Symbol): EnclMethodEntry = { // JVMS 4.7.7

      def newEEE(eClass: Symbol, m: Symbol) = {
        EnclMethodEntry(
          internalName(eClass),
          m.javaSimpleName.toString,
          asmMethodType(m)
        )
      }

      var res: EnclMethodEntry = null
      val sym = clazz.originalEnclosingMethod
      if (sym.isMethod) {
        debuglog(s"enclosing method for $clazz is $sym (in ${sym.enclClass})")
        res = newEEE(sym.enclClass, sym)
      } else if (clazz.isAnonymousClass) {
        val enclClass = clazz.rawowner
        assert(enclClass.isClass, enclClass)
        val sym = enclClass.primaryConstructor
        if (sym == NoSymbol) {
          log(s"Ran out of room looking for an enclosing method for $clazz: no constructor here: $enclClass.")
        } else {
          debuglog(s"enclosing method for $clazz is $sym (in $enclClass)")
          res = newEEE(enclClass, sym)
        }
      }

      res
    }

  } // end of trait BCClassGen

  /* basic functionality for class file building of plain, mirror, and beaninfo classes. */
  abstract class JBuilder extends BCInnerClassGen {

  } // end of class JBuilder

  /* functionality for building plain and mirror classes */
  abstract class JCommonBuilder
    extends JBuilder
    with    BCAnnotGen
    with    BCForwardersGen
    with    BCPickles { }

  /* builder of mirror classes */
  class JMirrorBuilder extends JCommonBuilder {

    private var cunit: CompilationUnit = _
    def getCurrentCUnit(): CompilationUnit = cunit;

    /* 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(modsym: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
      assert(modsym.companionClass == NoSymbol, modsym)
      innerClassBufferASM.clear()
      this.cunit = cunit
      val moduleName = internalName(modsym) // + "$"
      val mirrorName = moduleName.substring(0, moduleName.length() - 1)

      val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL)
      val mirrorClass = new asm.tree.ClassNode
      mirrorClass.visit(
        classfileVersion,
        flags,
        mirrorName,
        null /* no java-generic-signature */,
        JAVA_LANG_OBJECT.getInternalName,
        EMPTY_STRING_ARRAY
      )

      if (emitSource) {
        mirrorClass.visitSource("" + cunit.source,
                                null /* SourceDebugExtension */)
      }

      val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol)
      mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
      emitAnnotations(mirrorClass, modsym.annotations ++ ssa)

      addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)

      innerClassBufferASM ++= trackMemberClasses(modsym, Nil /* TODO what about Late-Closure-Classes */ )
      addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)

      mirrorClass.visitEnd()

      ("" + modsym.name) // this side-effect is necessary, really.

      mirrorClass
    }

  } // end of class JMirrorBuilder

  /* builder of bean info classes */
  class JBeanInfoBuilder extends JBuilder {

    /*
     * 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 }

      innerClassBufferASM.clear()

      val flags = mkFlags(
        javaFlags(cls),
        if (isDeprecated(cls)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
      )

      val beanInfoName  = (internalName(cls) + "BeanInfo")
      val beanInfoClass = new asm.tree.ClassNode
      beanInfoClass.visit(
        classfileVersion,
        flags,
        beanInfoName,
        null, // no java-generic-signature
        "scala/beans/ScalaBeanInfo",
        EMPTY_STRING_ARRAY
      )

      beanInfoClass.visitSource(
        cunit.source.toString,
        null /* SourceDebugExtension */
      )

      var fieldList = List[String]()

      for (f <- fieldSymbols if f.hasGetter;
	         g = f.getter(cls);
	         s = f.setter(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 = arrayOf(JAVA_LANG_STRING)
      val conJType: BType =
        BType.getMethodType(
          BType.VOID_TYPE,
          Array(exemplar(definitions.ClassClass).c, stringArrayJType, stringArrayJType)
        )

      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(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE))
          fi += 1
        }
      }

      constructor.visitCode()

      constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
      // push the class
      constructor.visitLdcInsn(exemplar(cls).c.toASMType)

      // push the string array of field information
      constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
      constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
      push(fieldList)

      // push the string array of method information
      constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
      constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
      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.getDescriptor)
      constructor.visitInsn(asm.Opcodes.RETURN)

      constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
      constructor.visitEnd()

      innerClassBufferASM ++= trackMemberClasses(cls, Nil /* TODO what about Late-Closure-Classes */ )
      addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)

      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) {
      // this tracks the inner class in innerClassBufferASM, if needed.
      val androidCreatorType = asmClassType(AndroidCreatorClass)
      val tdesc_creator = androidCreatorType.getDescriptor

      cnode.visitField(
        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 = BType.getMethodType(androidCreatorType, Array.empty[BType])
      clinit.visitMethodInsn(
        asm.Opcodes.INVOKEVIRTUAL,
        moduleName,
        "CREATOR",
        bt.getDescriptor
      )

      // PUTSTATIC `thisName`.CREATOR;
      clinit.visitFieldInsn(
        asm.Opcodes.PUTSTATIC,
        thisName,
        "CREATOR",
        tdesc_creator
      )
    }

  } // end of trait JAndroidBuilder

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy