diff options
146 files changed, 4905 insertions, 3988 deletions
@@ -1337,7 +1337,7 @@ QUICK BUILD (QUICK) <stopwatch name="quick.scalap.timer" action="total"/> </target> - <target name="quick.pre-partest" depends="quick.scalap"> + <target name="quick.pre-partest" depends="quick.scalap, asm.done"> <uptodate property="quick.partest.available" targetfile="${build-quick.dir}/partest.complete"> <srcfiles dir="${src.dir}/partest"/> </uptodate> @@ -1356,6 +1356,7 @@ QUICK BUILD (QUICK) <pathelement location="${build-quick.dir}/classes/compiler"/> <pathelement location="${build-quick.dir}/classes/scalap"/> <pathelement location="${build-quick.dir}/classes/partest"/> + <path refid="asm.classpath"/> </classpath> <include name="**/*.java"/> <compilerarg line="${javac.args}"/> @@ -1575,7 +1576,14 @@ PACKED QUICK BUILD (PACK) <target name="pack.partest" depends="pack.pre-partest" unless="pack.partest.available"> <mkdir dir="${build-pack.dir}/lib"/> <jar destfile="${build-pack.dir}/lib/scala-partest.jar"> - <fileset dir="${build-quick.dir}/classes/partest"/> + <fileset dir="${build-quick.dir}/classes/partest"> + <exclude name="scala/tools/partest/javaagent/**"/> + </fileset> + </jar> + <jar destfile="${build-pack.dir}/lib/scala-partest-javaagent.jar" manifest="${src.dir}/partest/scala/tools/partest/javaagent/MANIFEST.MF"> + <fileset dir="${build-quick.dir}/classes/partest"> + <include name="scala/tools/partest/javaagent/**"/> + </fileset> </jar> </target> @@ -1974,7 +1982,7 @@ BOOTSTRAPPING BUILD (STRAP) <stopwatch name="strap.scalap.timer" action="total"/> </target> - <target name="strap.pre-partest" depends="strap.scalap"> + <target name="strap.pre-partest" depends="strap.scalap, asm.done"> <uptodate property="strap.partest.available" targetfile="${build-strap.dir}/partest.complete"> <srcfiles dir="${src.dir}/partest"/> </uptodate> @@ -1993,6 +2001,7 @@ BOOTSTRAPPING BUILD (STRAP) <pathelement location="${build-strap.dir}/classes/compiler"/> <pathelement location="${build-strap.dir}/classes/scalap"/> <pathelement location="${build-strap.dir}/classes/partest"/> + <path refid="asm.classpath"/> </classpath> <include name="**/*.java"/> <compilerarg line="${javac.args}"/> @@ -2012,6 +2021,7 @@ BOOTSTRAPPING BUILD (STRAP) <pathelement location="${build-strap.dir}/classes/partest"/> <pathelement location="${ant.jar}"/> <path refid="forkjoin.classpath"/> + <path refid="asm.classpath"/> <pathelement location="${scalacheck.jar}"/> </compilationpath> </scalacfork> @@ -2393,6 +2403,9 @@ BOOTRAPING TEST AND TEST SUITE <specializedtests dir="${partest.dir}/${partest.srcdir}/specialized"> <include name="*.scala"/> </specializedtests> + <instrumentedtests dir="${partest.dir}/${partest.srcdir}/instrumented"> + <include name="*.scala"/> + </instrumentedtests> <presentationtests dir="${partest.dir}/${partest.srcdir}/presentation"> <include name="*/*.scala"/> </presentationtests> diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 8d243a1dd0..756d90bc53 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -1,3323 +1,3324 @@ -/* NSC -- new Scala compiler
- * Copyright 2005-2011 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.jvm
-
-import java.nio.ByteBuffer
-import scala.collection.{ mutable, immutable }
-import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
-import scala.tools.nsc.symtab._
-import scala.tools.nsc.io.AbstractFile
-
-import scala.tools.asm
-import asm.Label
-
-/**
- * @author Iulian Dragos (version 1.0, FJBG-based implementation)
- * @author Miguel Garcia (version 2.0, ASM-based implementation)
- *
- * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
- */
-abstract class GenASM extends SubComponent with BytecodeWriters {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
-
- val phaseName = "jvm"
-
- /** Create a new phase */
- override def newPhase(p: Phase): Phase = new AsmPhase(p)
-
- private def outputDirectory(sym: Symbol): AbstractFile =
- settings.outputDirs outputDirFor beforeFlatten(sym.sourceFile)
-
- private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
- var dir = base
- val pathParts = clsName.split("[./]").toList
- for (part <- pathParts.init) {
- dir = dir.subdirectoryNamed(part)
- }
- dir.fileNamed(pathParts.last + suffix)
- }
- private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile =
- getFile(outputDirectory(sym), clsName, suffix)
-
- /** JVM code generation phase
- */
- class AsmPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
- override def erasedTypes = true
- def apply(cls: IClass) = sys.error("no implementation")
-
- val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo")
-
- def isJavaEntryPoint(icls: IClass) = {
- val sym = icls.symbol
- def fail(msg: String, pos: Position = sym.pos) = {
- icls.cunit.warning(sym.pos,
- sym.name + " 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(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 == 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.
- beforeErasure {
- val companion = sym.linkedClassOfClass
- val companionMain = companion.tpe.member(nme.main)
-
- if (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 { 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
- isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
- case tp =>
- fail("don't know what this is: " + tp, m.pos)
- }
- }
- }
- }
- }
-
- private def initBytecodeWriter(entryPoints: List[IClass]): 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 (_.symbol fullName '.') match {
- case Nil =>
- log("No Main-Class designated or discovered.")
- case name :: Nil =>
- log("Unique entry point: setting Main-Class to " + name)
- settings.mainClass.value = name
- case names =>
- log("No Main-Class due to multiple entry points:\n " + names.mkString("\n "))
- }
- }
- else log("Main-Class was specified: " + settings.mainClass.value)
-
- new DirectToJarfileWriter(f.file)
-
- case _ =>
- if (settings.Ygenjavap.isDefault) {
- if(settings.Ydumpclasses.isDefault)
- new ClassBytecodeWriter { }
- else
- new ClassBytecodeWriter with DumpBytecodeWriter { }
- }
- else new ClassBytecodeWriter with JavapBytecodeWriter { }
-
- // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point.
- // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are:
- // (a) unreadable pickle;
- // (b) two constant pools, while having identical contents, are displayed differently due to physical layout.
- // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead.
- }
- }
-
- override def run() {
-
- if (settings.debug.value)
- inform("[running phase " + name + " on icode]")
-
- if (settings.Xdce.value)
- for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym))
- icodes.classes -= sym
-
- // For predictably ordered error messages.
- var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName)
-
- debuglog("Created new bytecode generator for " + classes.size + " classes.")
- val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
- val plainCodeGen = new JPlainBuilder(bytecodeWriter)
- val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter)
- val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter)
-
- while(!sortedClasses.isEmpty) {
- val c = sortedClasses.head
-
- if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) {
- if (c.symbol.companionClass == NoSymbol) {
- mirrorCodeGen.genMirrorClass(c.symbol, c.cunit)
- } else {
- log("No mirror class for module with linked class: " + c.symbol.fullName)
- }
- }
-
- plainCodeGen.genClass(c)
-
- if (c.symbol hasAnnotation BeanInfoAttr) {
- beanInfoCodeGen.genBeanInfoClass(c)
- }
-
- sortedClasses = sortedClasses.tail
- classes -= c.symbol // GC opportunity
- }
-
- bytecodeWriter.close()
- classes.clear()
- reverseJavaName.clear()
-
- /* don't javaNameCache.clear() because that causes the following tests to fail:
- * test/files/run/macro-repl-dontexpand.scala
- * test/files/jvm/interpreter.scala
- * TODO but why? what use could javaNameCache possibly see once GenJVM is over?
- */
-
- /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification:
- *
- * (1) call the asm.util.CheckAdapter.verify() overload:
- * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw)
- *
- * (2) passing a custom ClassLoader to verify inter-dependent classes.
- *
- * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool).
- */
-
- } // end of AsmPhase.run()
-
- } // end of class AsmPhase
-
- var pickledBytes = 0 // statistics
-
- // Don't put this in per run caches. Contains entries for classes as well as members.
- val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List(
- NothingClass -> binarynme.RuntimeNothing,
- RuntimeNothingClass -> binarynme.RuntimeNothing,
- NullClass -> binarynme.RuntimeNull,
- RuntimeNullClass -> binarynme.RuntimeNull
- )
-
- // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
- val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List(
- binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type.
- binarynme.RuntimeNull.toString() -> RuntimeNullClass
- )
-
- private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _)
-
- @inline final private def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0)
-
- @inline final private def isRemote(s: Symbol) = (s hasAnnotation RemoteAttr)
-
- /**
- * Return the Java modifiers for the given symbol.
- * Java modifiers for classes:
- * - public, abstract, final, strictfp (not used)
- * for interfaces:
- * - the same as for classes, without 'final'
- * for fields:
- * - public, private (*)
- * - static, final
- * for methods:
- * - the same as for fields, plus:
- * - abstract, synchronized (not used), strictfp (not used), native (not used)
- *
- * (*) protected cannot be used, since inner classes 'see' protected members,
- * and they would fail verification after lifted.
- */
- def javaFlags(sym: Symbol): Int = {
- // constructors of module classes should be private
- // PP: why are they only being marked private at this stage and not earlier?
- val privateFlag =
- sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))
-
- // Final: the only fields which can receive ACC_FINAL are eager vals.
- // Neither vars nor lazy vals can, because:
- //
- // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
- // "Another problem is that the specification allows aggressive
- // optimization of final fields. Within a thread, it is permissible to
- // reorder reads of a final field with those modifications of a final
- // field that do not take place in the constructor."
- //
- // A var or lazy val which is marked final still has meaning to the
- // scala compiler. The word final is heavily overloaded unfortunately;
- // for us it means "not overridable". At present you can't override
- // vars regardless; this may change.
- //
- // The logic does not check .isFinal (which checks flags for the FINAL flag,
- // and includes symbols marked lateFINAL) instead inspecting rawflags so
- // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
- // avoid breaking proxy software which depends on subclassing, we do not
- // emit ACC_FINAL.
- // Nested objects won't receive ACC_FINAL in order to allow for their overriding.
-
- val finalFlag = (
- (sym.hasFlag(Flags.FINAL) || isTopLevelModule(sym))
- && !sym.enclClass.isInterface
- && !sym.isClassConstructor
- && !sym.isMutable // lazy vals and vars both
- )
-
- // Primitives are "abstract final" to prohibit instantiation
- // without having to provide any implementations, but that is an
- // illegal combination of modifiers at the bytecode level so
- // suppress final if abstract if present.
- import asm.Opcodes._
- mkFlags(
- if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (sym.isInterface) ACC_INTERFACE else 0,
- if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
- if (sym.isStaticMember) ACC_STATIC else 0,
- if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
- if (sym.isHidden) ACC_SYNTHETIC else 0,
- if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
- if (sym.isVarargsMethod) ACC_VARARGS else 0,
- if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
- )
- }
-
- def javaFieldFlags(sym: Symbol) = {
- javaFlags(sym) | mkFlags(
- if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
- if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
- if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
- )
- }
-
- def isTopLevelModule(sym: Symbol): Boolean =
- afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
-
- def isStaticModule(sym: Symbol): Boolean = {
- sym.isModuleClass && !sym.isImplClass && !sym.isLifted
- }
-
- // -----------------------------------------------------------------------------------------
- // 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
- // -----------------------------------------------------------------------------------------
-
- /**
- * Given an internal name (eg "java/lang/Integer") returns the class symbol for it.
- *
- * Better not to need this method (an example where control flow arrives here is welcome).
- * This method is invoked only upon both (1) and (2) below happening:
- * (1) providing an asm.ClassWriter with an internal name by other means than javaName()
- * (2) forgetting to track the corresponding class-symbol in reverseJavaName.
- *
- * (The first item is already unlikely because we rely on javaName()
- * to do the bookkeeping for entries that should go in innerClassBuffer.)
- *
- * (We could do completely without this method at the expense of computing stack-map-frames ourselves and
- * invoking visitFrame(), but that would require another pass over all instructions.)
- *
- * Right now I can't think of any invocation of visitSomething() on MethodVisitor
- * where we hand an internal name not backed by a reverseJavaName.
- * However, I'm leaving this note just in case any such oversight is discovered.
- */
- def inameToSymbol(iname: String): Symbol = {
- val name = global.newTypeName(iname)
- val res0 =
- if (nme.isModuleName(name)) rootMirror.getModule(nme.stripModuleSuffix(name))
- else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested).
- assert(res0 != NoSymbol)
- val res = jsymbol(res0)
- res
- }
-
- def jsymbol(sym: Symbol): Symbol = {
- if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass
- else if(sym.isModule) sym.moduleClass
- else sym // we track only module-classes and plain-classes
- }
-
- private def superClasses(s: Symbol): List[Symbol] = {
- assert(!s.isInterface)
- s.superClass match {
- case NoSymbol => List(s)
- case sc => s :: superClasses(sc)
- }
- }
-
- private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = {
- assert(!(as contains NoSymbol))
- assert(!(bs contains NoSymbol))
- var chainA = as
- var chainB = bs
- var fcs: Symbol = NoSymbol
- 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 == NoSymbol)
- fcs
- }
-
- @inline final private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = {
-
- assert(a.isClass)
- assert(b.isClass)
-
- val res = Pair(a.isInterface, b.isInterface) match {
- case (true, true) =>
- global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents
- case (true, false) =>
- if(b isSubClass a) a else ObjectClass
- case (false, true) =>
- if(a isSubClass b) b else ObjectClass
- case _ =>
- firstCommonSuffix(superClasses(a), superClasses(b))
- }
- assert(res != NoSymbol)
- res
- }
-
- /* 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 */
- def getCommonSuperClass(inameA: String, inameB: String): String = {
- val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA))
- val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB))
-
- // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString()
- // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType
- val lcaSym = jvmWiseLUB(a, b)
- val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer.
- val oldsym = reverseJavaName.put(lcaName, lcaSym)
- assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption")
- assert(lcaName != "scala/Any")
-
- lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching.
- }
-
- class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
- override def getCommonSuperClass(iname1: String, iname2: String): String = {
- GenASM.this.getCommonSuperClass(iname1, iname2)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // constants
- // -----------------------------------------------------------------------------------------
-
- private val classfileVersion: Int = settings.target.value match {
- case "jvm-1.5" => asm.Opcodes.V1_5
- case "jvm-1.5-asm" => asm.Opcodes.V1_5
- case "jvm-1.6" => asm.Opcodes.V1_6
- case "jvm-1.7" => asm.Opcodes.V1_7
- }
-
- private val majorVersion: Int = (classfileVersion & 0xFF)
- private val emitStackMapFrame = (majorVersion >= 50)
-
- private val extraProc: Int = mkFlags(
- asm.ClassWriter.COMPUTE_MAXS,
- if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
- )
-
- val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object")
- val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
-
- /** basic functionality for class file building */
- abstract class JBuilder(bytecodeWriter: BytecodeWriter) {
-
- val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type]
- val EMPTY_STRING_ARRAY = Array.empty[String]
-
- val mdesc_arglessvoid = "()V"
-
- val CLASS_CONSTRUCTOR_NAME = "<clinit>"
- val INSTANCE_CONSTRUCTOR_NAME = "<init>"
-
- val INNER_CLASSES_FLAGS =
- (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
- asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT)
-
- // -----------------------------------------------------------------------------------------
- // factory methods
- // -----------------------------------------------------------------------------------------
-
- /**
- * Returns a new ClassWriter for the class given by arguments.
- *
- * @param access the class's access flags. This parameter also indicates if the class is deprecated.
- *
- * @param name the internal name of the class.
- *
- * @param signature the signature of this class. May be <tt>null</tt> if
- * the class is not a generic one, and does not extend or implement
- * generic classes or interfaces.
- *
- * @param superName the internal of name of the super class. For interfaces,
- * the super class is {@link Object}. May be <tt>null</tt>, but
- * only for the {@link Object} class.
- *
- * @param interfaces the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}). May be
- * <tt>null</tt>.
- */
- def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = {
- val cw = new CClassWriter(extraProc)
- cw.visit(classfileVersion,
- access, name, signature,
- superName, interfaces)
-
- cw
- }
-
- 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)
- }
-
- // -----------------------------------------------------------------------------------------
- // utitilies useful when emitting plain, mirror, and beaninfo classes.
- // -----------------------------------------------------------------------------------------
-
- def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
- try {
- val arr = jclass.toByteArray()
- bytecodeWriter.writeClass(label, jclassName, arr, sym)
- } catch {
- case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") =>
- // TODO check where ASM throws the equivalent of CodeSizeTooBigException
- log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).")
- }
- }
-
- /** Specialized array conversion to prevent calling
- * java.lang.reflect.Array.newInstance via TraversableOnce.toArray
- */
- def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a }
- def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a }
-
- // -----------------------------------------------------------------------------------------
- // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances).
- // These getters track behind the scenes the inner classes referred to in the class being emitted,
- // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()`
- // (which also adds as member classes those inner classes that have been declared,
- // thus also covering the case of inner classes declared but otherwise not referred).
- // -----------------------------------------------------------------------------------------
-
- val innerClassBuffer = mutable.LinkedHashSet[Symbol]()
-
- /** For given symbol return a symbol corresponding to a class that should be declared as inner class.
- *
- * For example:
- * class A {
- * class B
- * object C
- * }
- *
- * then method will return:
- * NoSymbol for A,
- * the same symbol for A.B (corresponding to A$B class), and
- * A$C$ symbol for A.C.
- */
- def innerClassSymbolFor(s: Symbol): Symbol =
- if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol
-
- /** Return the a name of this symbol that can be used on the Java platform. It removes spaces from names.
- *
- * Special handling:
- * scala.Nothing erases to scala.runtime.Nothing$
- * scala.Null erases to scala.runtime.Null$
- *
- * This is needed because they are not real classes, and they mean
- * 'abrupt termination upon evaluation of that expression' or null respectively.
- * This handling is done already in GenICode, but here we need to remove
- * references from method signatures to these types, because such classes
- * cannot exist in the classpath: the type checker will be very confused.
- */
- def javaName(sym: Symbol): String = {
-
- /**
- * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer
- *
- * Note: This method is called recursively thus making sure that we add complete chain
- * of inner class all until root class.
- */
- def collectInnerClass(s: Symbol): Unit = {
- // TODO: some beforeFlatten { ... } which accounts for
- // being nested in parameterized classes (if we're going to selectively flatten.)
- val x = innerClassSymbolFor(s)
- if(x ne NoSymbol) {
- assert(x.isClass, "not an inner-class symbol")
- val isInner = !x.rawowner.isPackageClass
- if (isInner) {
- innerClassBuffer += x
- collectInnerClass(x.rawowner)
- }
- }
- }
-
- collectInnerClass(sym)
-
- var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod))
- val cachedJN = javaNameCache.getOrElseUpdate(sym, {
- if (hasInternalName) { sym.javaBinaryName }
- else { sym.javaSimpleName }
- })
-
- if(emitStackMapFrame && hasInternalName) {
- val internalName = cachedJN.toString()
- val trackedSym = jsymbol(sym)
- reverseJavaName.get(internalName) match {
- case None =>
- reverseJavaName.put(internalName, trackedSym)
- case Some(oldsym) =>
- assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass), // In contrast, neither NothingClass nor NullClass show up bytecode-level.
- "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.")
- }
- }
-
- cachedJN.toString
- }
-
- def descriptor(t: Type): String = { javaType(t).getDescriptor }
- def descriptor(k: TypeKind): String = { javaType(k).getDescriptor }
- def descriptor(s: Symbol): String = { javaType(s).getDescriptor }
-
- def javaType(tk: TypeKind): asm.Type = {
- if(tk.isValueType) {
- if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL => asm.Type.BOOLEAN_TYPE
- case BYTE => asm.Type.BYTE_TYPE
- case SHORT => asm.Type.SHORT_TYPE
- case CHAR => asm.Type.CHAR_TYPE
- case INT => asm.Type.INT_TYPE
- }
- } else {
- (tk: @unchecked) match {
- case UNIT => asm.Type.VOID_TYPE
- case LONG => asm.Type.LONG_TYPE
- case FLOAT => asm.Type.FLOAT_TYPE
- case DOUBLE => asm.Type.DOUBLE_TYPE
- }
- }
- } else {
- assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway)
- (tk: @unchecked) match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- }
- }
- }
-
- def javaType(t: Type): asm.Type = javaType(toTypeKind(t))
-
- def javaType(s: Symbol): asm.Type = {
- if (s.isMethod) {
- val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType);
- asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _* )
- } else { javaType(s.tpe) }
- }
-
- def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) }
-
- def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
-
- def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) {
- /** The outer name for this inner class. Note that it returns null
- * when the inner class should not get an index in the constant pool.
- * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
- */
- def outerName(innerSym: Symbol): String = {
- if (innerSym.originalEnclosingMethod != NoSymbol)
- null
- else {
- val outerName = javaName(innerSym.rawowner)
- if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName))
- else outerName
- }
- }
-
- def innerName(innerSym: Symbol): String =
- if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction)
- null
- else
- innerSym.rawname + innerSym.moduleSuffix
-
- // add inner classes which might not have been referenced yet
- afterErasure {
- for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass)
- innerClassBuffer += m
- }
-
- val allInners: List[Symbol] = innerClassBuffer.toList
- if (allInners.nonEmpty) {
- debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.")
-
- // entries ready to be serialized into the classfile, used to detect duplicates.
- val entries = mutable.Map.empty[String, String]
-
- // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler
- for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ??
- val flags = mkFlags(
- if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0,
- javaFlags(innerSym),
- if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag
- ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
- val jname = javaName(innerSym) // never null
- val oname = outerName(innerSym) // null when method-enclosed
- val iname = innerName(innerSym) // null for anonymous inner class
-
- // Mimicking javap inner class output
- debuglog(
- if (oname == null || iname == null) "//class " + jname
- else "//%s=class %s of class %s".format(iname, jname, oname)
- )
-
- assert(jname != null, "javaName is broken.") // documentation
- val doAdd = entries.get(jname) 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 == oname, "duplicate")
- false
- case None => true
- }
-
- if(doAdd) {
- entries += (jname -> oname)
- jclass.visitInnerClass(jname, oname, iname, flags)
- }
-
- /*
- * TODO assert (JVMS 4.7.6 The InnerClasses attribute)
- * If a class file has a version number that is greater than or equal to 51.0, and
- * has an InnerClasses attribute in its attributes table, then for all entries in the
- * classes array of the InnerClasses attribute, the value of the
- * outer_class_info_index item must be zero if the value of the
- * inner_name_index item is zero.
- */
-
- }
- }
- }
-
- } // end of class JBuilder
-
-
- /** functionality for building plain and mirror classes */
- abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
-
- def debugLevel = settings.debuginfo.indexOfChoice
-
- val emitSource = debugLevel >= 1
- val emitLines = debugLevel >= 2
- val emitVars = debugLevel >= 3
-
- // -----------------------------------------------------------------------------------------
- // more constants
- // -----------------------------------------------------------------------------------------
-
- val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
- val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL
-
- val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString
-
- // -----------------------------------------------------------------------------------------
- // 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.)
- // -----------------------------------------------------------------------------------------
-
- 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
- }
-
- def pickleMarkerLocal = {
- createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
- }
-
- 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.
- *
- */
- 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, List((nme.bytes, sigBytes)))
- }
- pickledBytes += pickle.writeIndex
- currentRun.symData -= sym
- currentRun.symData -= sym.companionSymbol
- Some(scalaAnnot)
- case _ =>
- None
- }
- }
-
- /**
- * 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.
- *
- */
- def getExceptions(excs: List[AnnotationInfo]): List[String] = {
- for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass)
- yield {
- val Literal(const) = exc
- javaName(const.typeValue.typeSymbol)
- }
- }
-
- /** Whether an annotation should be emitted as a Java annotation
- * .initialize: if 'annot' is read from pickle, atp might be un-initialized
- */
- private def shouldEmitAnnotation(annot: AnnotationInfo) =
- annot.symbol.initialize.isJavaDefined &&
- annot.matches(ClassfileAnnotationClass) &&
- annot.args.isEmpty &&
- !annot.matches(DeprecatedAttr)
-
- // @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.
- sym.isHidden
- || sym.isLiftedMethod
- || sym.isBridge
- || (sym.ownerChain exists (_.isImplClass))
- )
-
- 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
- */
- def getGenericSignature(sym: Symbol, owner: Symbol): String = {
-
- if (!needsGenericSignature(sym)) { return null }
-
- val memberTpe = beforeErasure(owner.thisType.memberInfo(sym))
-
- 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 _ => false }
- }
-
- if (settings.Xverify.value) {
- // 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.SignatureChecker
- if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar
- else if (sym.isTerm) { SignatureChecker checkFieldSignature sig }
- else { SignatureChecker checkClassSignature sig }
- }
-
- if(!isValidSignature) {
- getCurrentCUnit().warning(sym.pos,
- """|compiler bug: created invalid generic signature for %s in %s
- |signature: %s
- |if this is reproducible, please report bug at https://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig))
- return null
- }
- }
-
- if ((settings.check containsName phaseName)) {
- val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe))
- val bytecodeTpe = owner.thisType.memberInfo(sym)
- if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
- getCurrentCUnit().warning(sym.pos,
- """|compiler bug: created generic signature for %s in %s that does not conform to its erasure
- |signature: %s
- |original type: %s
- |normalized type: %s
- |erasure type: %s
- |if this is reproducible, please report bug at http://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe))
- return null
- }
- }
-
- sig
- }
-
- def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
- val ca = new Array[Char](bytes.size)
- var idx = 0
- while(idx < bytes.size) {
- val b: Byte = bytes(idx)
- assert((b & ~0x7f) == 0)
- ca(idx) = b.asInstanceOf[Char]
- idx += 1
- }
-
- ca
- }
-
- // TODO this method isn't exercised during bootstrapping. Open question: is it bug free?
- private 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.size) {
- val newEncLength = encLength.toLong + (if(bSeven(offset) == 0) 2 else 1)
- if(newEncLength > 65535) {
- val ba = bSeven.slice(prevOffset, offset)
- strs ::= new java.lang.String(ubytesToCharArray(ba))
- encLength = 0
- prevOffset = offset
- } else {
- encLength += 1
- 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
- }
-
- 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)
- }
-
- def emitArgument(av: asm.AnnotationVisitor,
- name: String,
- arg: ClassfileAnnotArg) {
- arg 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, javaType(const.typeValue))
- 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.
- val assocValue = (if(sb.fitsInOneString) strEncode(sb) else arrEncode(sb))
- av.visit(name, assocValue)
- // 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)
- }
- }
-
- def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
- for ((name, value) <- assocs) {
- emitArgument(av, name.toString(), value)
- }
- av.visitEnd()
- }
-
- 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)
- }
- }
-
- 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)
- }
- }
-
- 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)
- }
- }
-
- def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) {
- val annotationss = pannotss map (_ filter shouldEmitAnnotation)
- if (annotationss forall (_.isEmpty)) return
- for (Pair(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)
- }
- }
-
- /** Adds a @remote annotation, actual use unknown.
- *
- * Invoked from genMethod() and addForwarder().
- */
- def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
- val needsAnnotation = (
- ( isRemoteClass ||
- isRemote(meth) && isJMethodPublic
- ) && !(meth.throwsAnnotations contains RemoteExceptionClass)
- )
- if (needsAnnotation) {
- val c = Constant(RemoteExceptionClass.tpe)
- val arg = Literal(c) setType c.tpe
- meth.addAnnotation(ThrowsClass, arg)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // Static forwarders (related to mirror classes but also present in
- // a plain class lacking companion module, for details see `isCandidateForForwarders`).
- // -----------------------------------------------------------------------------------------
-
- val ExcludedForwarderFlags = {
- import Flags._
- // Should include DEFERRED but this breaks findMember.
- ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags )
- }
-
- /** Add a forwarder for method m. Used only from addForwarders(). */
- private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
- val moduleName = javaName(module)
- val methodInfo = module.thisType.memberInfo(m)
- val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType
- // 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 = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745
- addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
- val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(throws)
-
- val jReturnType = javaType(methodInfo.resultType)
- val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*)
- val mirrorMethodName = javaName(m)
- val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
- flags,
- mirrorMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(mirrorMethod, others)
- emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))
-
- // typestate: entering mode with valid call sequences:
- // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- 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.getSort() != asm.Type.METHOD, jparamType)
- index += jparamType.getSize()
- }
-
- mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(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.
- */
- def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
- assert(moduleClass.isModuleClass, moduleClass)
- debuglog("Dumping mirror class for object: " + moduleClass)
-
- val linkedClass = moduleClass.companionClass
- val linkedModule = linkedClass.companionSymbol
- lazy val conflictingNames: Set[Name] = {
- linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet
- }
- debuglog("Potentially conflicting names for forwarders: " + conflictingNames)
-
- for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) {
- if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor)
- debuglog("No forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass))
- else if (conflictingNames(m.name))
- log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name))
- else {
- log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass))
- addForwarder(isRemoteClass, jclass, moduleClass, m)
- }
- }
- }
-
- } // end of class JCommonBuilder
-
-
- trait JAndroidBuilder {
- self: JPlainBuilder =>
-
- /** 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.
- */
- private val androidFieldName = newTermName("CREATOR")
-
- private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable")
- private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator")
-
- def isAndroidParcelableClass(sym: Symbol) =
- (AndroidParcelableInterface != NoSymbol) &&
- (sym.parentSymbols contains AndroidParcelableInterface)
-
- /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */
- def addCreatorCode(block: BasicBlock) {
- val fieldSymbol = (
- clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL)
- setInfo AndroidCreatorClass.tpe
- )
- val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName)
- clasz addField new IField(fieldSymbol)
- block emit CALL_METHOD(methodSymbol, Static(false))
- block emit STORE_FIELD(fieldSymbol, true)
- }
-
- def legacyAddCreatorCode(clinit: asm.MethodVisitor) {
- val creatorType: asm.Type = javaType(AndroidCreatorClass)
- val tdesc_creator = creatorType.getDescriptor
-
- jclass.visitField(
- PublicStaticFinal,
- androidFieldName,
- tdesc_creator,
- null, // no java-generic-signature
- null // no initial value
- ).visitEnd()
-
- val moduleName = javaName(clasz.symbol)+"$"
-
- // GETSTATIC `moduleName`.MODULE$ : `moduleName`;
- clinit.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- moduleName,
- strMODULE_INSTANCE_FIELD,
- asm.Type.getObjectType(moduleName).getDescriptor
- )
-
- // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
- clinit.visitMethodInsn(
- asm.Opcodes.INVOKEVIRTUAL,
- moduleName,
- androidFieldName,
- asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*)
- )
-
- // PUTSTATIC `thisName`.CREATOR;
- clinit.visitFieldInsn(
- asm.Opcodes.PUTSTATIC,
- thisName,
- androidFieldName,
- tdesc_creator
- )
- }
-
- } // end of trait JAndroidBuilder
-
- /** Map from type kinds to the Java reference types.
- * It is used to push class literals onto the operand stack.
- * @see Predef.classOf
- * @see genConstant()
- */
- private val classLiteral = immutable.Map[TypeKind, asm.Type](
- UNIT -> asm.Type.getObjectType("java/lang/Void"),
- BOOL -> asm.Type.getObjectType("java/lang/Boolean"),
- BYTE -> asm.Type.getObjectType("java/lang/Byte"),
- SHORT -> asm.Type.getObjectType("java/lang/Short"),
- CHAR -> asm.Type.getObjectType("java/lang/Character"),
- INT -> asm.Type.getObjectType("java/lang/Integer"),
- LONG -> asm.Type.getObjectType("java/lang/Long"),
- FLOAT -> asm.Type.getObjectType("java/lang/Float"),
- DOUBLE -> asm.Type.getObjectType("java/lang/Double")
- )
-
- def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT }
-
- case class MethodNameAndType(mname: String, mdesc: String)
-
- private val jBoxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) ,
- BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) ,
- CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") ,
- SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) ,
- INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) ,
- LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) ,
- FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) ,
- DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" )
- )
- }
-
- private val jUnboxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") ,
- BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") ,
- CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") ,
- SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") ,
- INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") ,
- LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") ,
- FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") ,
- DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D")
- )
- }
-
- case class BlockInteval(start: BasicBlock, end: BasicBlock)
-
- /** builder of plain classes */
- class JPlainBuilder(bytecodeWriter: BytecodeWriter)
- extends JCommonBuilder(bytecodeWriter)
- with JAndroidBuilder {
-
- val MIN_SWITCH_DENSITY = 0.7
-
- val StringBuilderClassName = javaName(definitions.StringBuilderClass)
- val BoxesRunTime = "scala/runtime/BoxesRunTime"
-
- val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName)
- val mdesc_toString = "()Ljava/lang/String;"
- val mdesc_arrayClone = "()Ljava/lang/Object;"
-
- val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J"
-
- def isParcelableClass = isAndroidParcelableClass(clasz.symbol)
-
- def serialVUID: Option[Long] = clasz.symbol getAnnotation SerialVersionUIDAttr collect {
- case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue
- }
-
- private def getSuperInterfaces(c: IClass): Array[String] = {
-
- // Additional interface parents based on annotations and other cues
- def newParentForAttr(attr: Symbol): Option[Symbol] = attr match {
- case SerializableAttr => Some(SerializableClass)
- case CloneableAttr => Some(CloneableClass)
- case RemoteAttr => Some(RemoteInterfaceClass)
- case _ => None
- }
-
- /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
- * This is important on Android because there is otherwise an interface explosion.
- */
- def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
- var rest = lstIfaces
- var leaves = List.empty[Symbol]
- while(!rest.isEmpty) {
- val candidate = rest.head
- val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
- if(!nonLeaf) {
- leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
- }
- rest = rest.tail
- }
-
- leaves
- }
-
- val ps = c.symbol.info.parents
- val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses;
- val superInterfaces = superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol)) distinct
-
- if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY
- else mkArray(minimizeInterfaces(superInterfaces) map javaName)
- }
-
- var clasz: IClass = _ // this var must be assigned only by genClass()
- var jclass: asm.ClassWriter = _ // the classfile being emitted
- var thisName: String = _ // the internal name of jclass
-
- def thisDescr: String = {
- assert(thisName != null, "thisDescr invoked too soon.")
- asm.Type.getObjectType(thisName).getDescriptor
- }
-
- def getCurrentCUnit(): CompilationUnit = { clasz.cunit }
-
- def genClass(c: IClass) {
- clasz = c
- innerClassBuffer.clear()
-
- thisName = javaName(c.symbol) // the internal name of the class being emitted
-
- val ps = c.symbol.info.parents
- val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol);
-
- val ifaces = getSuperInterfaces(c)
-
- val thisSignature = getGenericSignature(c.symbol, c.symbol.owner)
- val flags = mkFlags(
- javaFlags(c.symbol),
- if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
- jclass = createJClass(flags,
- thisName, thisSignature,
- superClass, ifaces)
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- if(emitSource) {
- jclass.visitSource(c.cunit.source.toString,
- null /* SourceDebugExtension */)
- }
-
- val enclM = getEnclosingMethodAttribute()
- if(enclM != null) {
- val EnclMethodEntry(className, methodName, methodType) = enclM
- jclass.visitOuterClass(className, methodName, methodType.getDescriptor)
- }
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )*
-
- val ssa = getAnnotPickle(thisName, c.symbol)
- jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
- emitAnnotations(jclass, c.symbol.annotations ++ ssa)
-
- // typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- if (isStaticModule(c.symbol) || isParcelableClass) {
-
- if (isStaticModule(c.symbol)) { addModuleInstanceField() }
- addStaticInit(c.lookupStaticCtor)
-
- } else {
-
- for (constructor <- c.lookupStaticCtor) {
- addStaticInit(Some(constructor))
- }
- val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value)
- if (!skipStaticForwarders) {
- val lmoc = c.symbol.companionModule
- // add static forwarders if there are no name conflicts; see bugs #363 and #1735
- if (lmoc != NoSymbol) {
- // it must be a top level class (name contains no $s)
- val isCandidateForForwarders = {
- afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
- }
- if (isCandidateForForwarders) {
- log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc))
- addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass)
- }
- }
- }
-
- }
-
- // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
- serialVUID foreach { value =>
- val fieldName = "serialVersionUID"
- jclass.visitField(
- PublicStaticFinal,
- fieldName,
- tdesc_long,
- null, // no java-generic-signature
- value
- ).visitEnd()
- }
-
- clasz.fields foreach genField
- clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) }
-
- addInnerClasses(clasz.symbol, jclass)
- jclass.visitEnd()
- writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol)
-
- }
-
- /**
- * @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: asm.Type)
-
- /**
- * @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.
- *
- */
- private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7
- var res: EnclMethodEntry = null
- val clazz = clasz.symbol
- val sym = clazz.originalEnclosingMethod
- if (sym.isMethod) {
- debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass))
- res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym))
- } else if (clazz.isAnonymousClass) {
- val enclClass = clazz.rawowner
- assert(enclClass.isClass, enclClass)
- val sym = enclClass.primaryConstructor
- if (sym == NoSymbol) {
- log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz))
- } else {
- debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass))
- res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym))
- }
- }
-
- res
- }
-
- def genField(f: IField) {
- debuglog("Adding field: " + f.symbol.fullName)
-
- val javagensig = getGenericSignature(f.symbol, clasz.symbol)
-
- val flags = mkFlags(
- javaFieldFlags(f.symbol),
- if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val jfield: asm.FieldVisitor = jclass.visitField(
- flags,
- javaName(f.symbol),
- javaType(f.symbol.tpe).getDescriptor(),
- javagensig,
- null // no initial value
- )
-
- emitAnnotations(jfield, f.symbol.annotations)
- jfield.visitEnd()
- }
-
- var method: IMethod = _
- var jmethod: asm.MethodVisitor = _
- var jMethodName: String = _
-
- @inline final def emit(opc: Int) { jmethod.visitInsn(opc) }
-
- def genMethod(m: IMethod, isJInterface: Boolean) {
-
- def isClosureApply(sym: Symbol): Boolean = {
- (sym.name == nme.apply) &&
- sym.owner.isSynthetic &&
- sym.owner.tpe.parents.exists { t =>
- val TypeRef(_, sym, _) = t
- FunctionClass contains sym
- }
- }
-
- if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return
-
- debuglog("Generating method " + m.symbol.fullName)
- method = m
- computeLocalVarsIndex(m)
-
- var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
- if (m.symbol.isClassConstructor)
- resTpe = asm.Type.VOID_TYPE
-
- val flags = mkFlags(
- javaFlags(m.symbol),
- if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0,
- if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
- if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
- if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
- val jgensig = getGenericSignature(m.symbol, clasz.symbol)
- addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol)
- val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(excs)
-
- jMethodName = javaName(m.symbol)
- val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*)
- jmethod = jclass.visitMethod(
- flags,
- jMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // TODO param names: (m.params map (p => javaName(p.sym)))
-
- // typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(jmethod, others)
- emitParamAnnotations(jmethod, m.params.map(_.sym.annotations))
-
- // typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
- // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code,
- // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and
- // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited.
-
- val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0)
- val hasCodeAttribute = (!hasAbstractBitSet && !method.native)
- if (hasCodeAttribute) {
-
- jmethod.visitCode()
-
- if (emitVars && isClosureApply(method.symbol)) {
- // add a fake local for debugging purposes
- val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL)
- if (outerField != NoSymbol) {
- log("Adding fake local to represent outer 'this' for closure " + clasz)
- val _this =
- new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS),
- toTypeKind(outerField.tpe),
- false)
- m.locals = m.locals ::: List(_this)
- computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.GETFIELD,
- javaName(clasz.symbol), // field owner
- javaName(outerField), // field name
- descriptor(outerField) // field descriptor
- )
- assert(_this.kind.isReferenceType, _this.kind)
- jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this))
- }
- }
-
- assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals )
-
- val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
- genCode(m, emitVars, hasStaticBitSet)
-
- jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- }
-
- jmethod.visitEnd()
-
- }
-
- def addModuleInstanceField() {
- val fv =
- jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- strMODULE_INSTANCE_FIELD,
- thisDescr,
- null, // no java-generic-signature
- null // no initial value
- )
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )* visitEnd.
-
- fv.visitEnd()
- }
-
-
- /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */
- def addStaticInit(mopt: Option[IMethod]) {
-
- val clinitMethod: asm.MethodVisitor = jclass.visitMethod(
- PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- CLASS_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- null // no throwable exceptions
- )
-
- mopt match {
-
- case Some(m) =>
-
- val oldLastBlock = m.lastBlock
- val lastBlock = m.newBlock()
- oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock))
-
- if (isStaticModule(clasz.symbol)) {
- // call object's private ctor from static ctor
- lastBlock emit NEW(REFERENCE(m.symbol.enclClass))
- lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true))
- }
-
- if (isParcelableClass) { addCreatorCode(lastBlock) }
-
- lastBlock emit RETURN(UNIT)
- lastBlock.close
-
- method = m
- jmethod = clinitMethod
- jMethodName = CLASS_CONSTRUCTOR_NAME
- jmethod.visitCode()
- genCode(m, false, true)
- jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- jmethod.visitEnd()
-
- case None =>
- clinitMethod.visitCode()
- legacyStaticInitializer(clinitMethod)
- clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- clinitMethod.visitEnd()
-
- }
- }
-
- /* used only from addStaticInit() */
- private def legacyStaticInitializer(clinit: asm.MethodVisitor) {
- if (isStaticModule(clasz.symbol)) {
- clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
- clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
- thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid)
- }
-
- if (isParcelableClass) { legacyAddCreatorCode(clinit) }
-
- clinit.visitInsn(asm.Opcodes.RETURN)
- }
-
- // -----------------------------------------------------------------------------------------
- // Emitting bytecode instructions.
- // -----------------------------------------------------------------------------------------
-
- private def genConstant(mv: asm.MethodVisitor, const: Constant) {
- const.tag match {
-
- case BooleanTag => jcode.boolconst(const.booleanValue)
-
- case ByteTag => jcode.iconst(const.byteValue)
- case ShortTag => jcode.iconst(const.shortValue)
- case CharTag => jcode.iconst(const.charValue)
- case IntTag => jcode.iconst(const.intValue)
-
- case LongTag => jcode.lconst(const.longValue)
- case FloatTag => jcode.fconst(const.floatValue)
- case DoubleTag => jcode.dconst(const.doubleValue)
-
- case UnitTag => ()
-
- case StringTag =>
- assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
- mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag
-
- case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL)
-
- case ClazzTag =>
- val kind = toTypeKind(const.typeValue)
- val toPush: asm.Type =
- if (kind.isValueType) classLiteral(kind)
- else javaType(kind);
- mv.visitLdcInsn(toPush)
-
- case EnumTag =>
- val sym = const.symbolValue
- mv.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- javaName(sym.owner),
- javaName(sym),
- javaType(sym.tpe.underlying).getDescriptor()
- )
-
- case _ => abort("Unknown constant value: " + const)
- }
- }
-
- /** Just a namespace for utilities that encapsulate MethodVisitor idioms.
- * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role,
- * but the methods here allow choosing when to transition from ICode to ASM types
- * (including not at all, e.g. for performance).
- */
- object jcode {
-
- import asm.Opcodes;
-
- def aconst(cst: AnyRef) {
- if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) }
- else { jmethod.visitLdcInsn(cst) }
- }
-
- @inline final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) }
-
- def iconst(cst: Int) {
- if (cst >= -1 && cst <= 5) {
- jmethod.visitInsn(Opcodes.ICONST_0 + cst)
- } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.BIPUSH, cst)
- } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.SIPUSH, cst)
- } else {
- jmethod.visitLdcInsn(new Integer(cst))
- }
- }
-
- def lconst(cst: Long) {
- if (cst == 0L || cst == 1L) {
- jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Long(cst))
- }
- }
-
- def fconst(cst: Float) {
- val bits: Int = java.lang.Float.floatToIntBits(cst)
- if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2
- jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Float(cst))
- }
- }
-
- def dconst(cst: Double) {
- val bits: Long = java.lang.Double.doubleToLongBits(cst)
- if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d
- jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Double(cst))
- }
- }
-
- def newarray(elem: TypeKind) {
- if(elem.isRefOrArrayType) {
- jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName)
- } else {
- val rand = {
- if(elem.isIntSizedType) {
- (elem: @unchecked) match {
- case BOOL => Opcodes.T_BOOLEAN
- case BYTE => Opcodes.T_BYTE
- case SHORT => Opcodes.T_SHORT
- case CHAR => Opcodes.T_CHAR
- case INT => Opcodes.T_INT
- }
- } else {
- (elem: @unchecked) match {
- case LONG => Opcodes.T_LONG
- case FLOAT => Opcodes.T_FLOAT
- case DOUBLE => Opcodes.T_DOUBLE
- }
- }
- }
- jmethod.visitIntInsn(Opcodes.NEWARRAY, rand)
- }
- }
-
-
- @inline def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) }
- @inline def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) }
-
- @inline def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) }
- @inline def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) }
-
- @inline def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) }
- @inline def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) }
- @inline def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) }
- @inline def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) }
- @inline def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) }
- @inline def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) }
-
- @inline def invokespecial(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc)
- }
- @inline def invokestatic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc)
- }
- @inline def invokeinterface(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc)
- }
- @inline def invokevirtual(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc)
- }
-
- @inline def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
- @inline def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
- @inline def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
- @inline def emitIF_ACMP(cond: TestOp, label: asm.Label) {
- assert((cond == EQ) || (cond == NE), cond)
- val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
- jmethod.visitJumpInsn(opc, label)
- }
- @inline def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) }
- @inline def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) }
-
- @inline def emitRETURN(tk: TypeKind) {
- if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) }
- else { emitTypeBased(returnOpcodes, tk) }
- }
-
- /** Emits one of tableswitch or lookoupswitch. */
- def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) {
- assert(keys.length == branches.length)
-
- // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only.
- // Similar to what javac emits for a switch statement consisting only of a default case.
- if (keys.length == 0) {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- return
- }
-
- // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort
- var i = 1
- while (i < keys.length) {
- var j = 1
- while (j <= keys.length - i) {
- if (keys(j) < keys(j - 1)) {
- val tmp = keys(j)
- keys(j) = keys(j - 1)
- keys(j - 1) = tmp
- val tmpL = branches(j)
- branches(j) = branches(j - 1)
- branches(j - 1) = tmpL
- }
- j += 1
- }
- i += 1
- }
-
- // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011)
- i = 1
- while (i < keys.length) {
- if(keys(i-1) == keys(i)) {
- abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.")
- }
- i += 1
- }
-
- val keyMin = keys(0)
- val keyMax = keys(keys.length - 1)
-
- val isDenseEnough: Boolean = {
- /** Calculate in long to guard against overflow. TODO what overflow??? */
- val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double]
- val klenD: Double = keys.length
- val kdensity: Double = (klenD / keyRangeD)
-
- kdensity >= minDensity
- }
-
- if (isDenseEnough) {
- // use a table in which holes are filled with defaultBranch.
- val keyRange = (keyMax - keyMin + 1)
- val newBranches = new Array[asm.Label](keyRange)
- var oldPos = 0;
- var i = 0
- while(i < keyRange) {
- val key = keyMin + i;
- if (keys(oldPos) == key) {
- newBranches(i) = branches(oldPos)
- oldPos += 1
- } else {
- newBranches(i) = defaultBranch
- }
- i += 1
- }
- assert(oldPos == keys.length, "emitSWITCH")
- jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*)
- } else {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- }
- }
-
- // internal helpers -- not part of the public API of `jcode`
- // don't make private otherwise inlining will suffer
-
- def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) {
- assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc)
- jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx)
- }
-
- // ---------------- array load and store ----------------
-
- val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) }
- val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) }
-
- val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) }
-
- def emitTypeBased(opcs: Array[Int], tk: TypeKind) {
- assert(tk != UNIT, tk)
- val opc = {
- if(tk.isRefOrArrayType) { opcs(0) }
- else if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL | BYTE => opcs(1)
- case SHORT => opcs(2)
- case CHAR => opcs(3)
- case INT => opcs(4)
- }
- } else {
- (tk: @unchecked) match {
- case LONG => opcs(5)
- case FLOAT => opcs(6)
- case DOUBLE => opcs(7)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- // ---------------- primitive operations ----------------
-
- val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) }
- val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) }
- val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) }
- val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) }
- val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) }
- val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) }
-
- def emitPrimitive(opcs: Array[Int], tk: TypeKind) {
- val opc = {
- if(tk.isIntSizedType) { opcs(0) }
- else {
- (tk: @unchecked) match {
- case LONG => opcs(1)
- case FLOAT => opcs(2)
- case DOUBLE => opcs(3)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- }
-
- /** Invoked from genMethod() and addStaticInit() */
- def genCode(m: IMethod,
- emitVars: Boolean, // this param name hides the instance-level var
- isStatic: Boolean) {
-
-
- newNormal.normalize(m)
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization`
- // ------------------------------------------------------------------------------------------------------------
-
- val linearization: List[BasicBlock] = linearizer.linearize(m)
- if(linearization.isEmpty) { return }
-
- var isModuleInitialized = false
-
- val labels: collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*)
-
- val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted
-
- // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label.
- val linNext: collection.Map[BasicBlock, asm.Label] = {
- val result = mutable.HashMap.empty[BasicBlock, asm.Label]
- var rest = linearization
- var prev = rest.head
- rest = rest.tail
- while(!rest.isEmpty) {
- result += (prev -> labels(rest.head))
- prev = rest.head
- rest = rest.tail
- }
- assert(!result.contains(prev))
- result += (prev -> onePastLast)
-
- result
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock())
- // ------------------------------------------------------------------------------------------------------------
-
- /**Generate exception handlers for the current method.
- *
- * Quoting from the JVMS 4.7.3 The Code Attribute
- * The items of the Code_attribute structure are as follows:
- * . . .
- * exception_table[]
- * Each entry in the exception_table array describes one
- * exception handler in the code array. The order of the handlers in
- * the exception_table array is significant.
- * Each exception_table entry contains the following four items:
- * start_pc, end_pc:
- * ... The value of end_pc either must be a valid index into
- * the code array of the opcode of an instruction or must be equal to code_length,
- * the length of the code array.
- * handler_pc:
- * The value of the handler_pc item indicates the start of the exception handler
- * catch_type:
- * ... If the value of the catch_type item is zero,
- * this exception handler is called for all exceptions.
- * This is used to implement finally
- */
- def genExceptionHandlers() {
-
- /** Return a list of pairs of intervals where the handler is active.
- * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end].
- * Preconditions:
- * - e.covered non-empty
- * Postconditions for the result:
- * - always non-empty
- * - intervals are sorted as per `linearization`
- * - the argument's `covered` blocks have been grouped into maximally contiguous intervals,
- * ie. between any two intervals in the result there is a non-empty gap.
- * - each of the `covered` blocks in the argument is contained in some interval in the result
- */
- def intervals(e: ExceptionHandler): List[BlockInteval] = {
- assert(e.covered.nonEmpty, e)
- var result: List[BlockInteval] = Nil
- var rest = linearization
-
- // find intervals
- while(!rest.isEmpty) {
- // find interval start
- var start: BasicBlock = null
- while(!rest.isEmpty && (start eq null)) {
- if(e.covered(rest.head)) { start = rest.head }
- rest = rest.tail
- }
- if(start ne null) {
- // find interval end
- var end = start // for the time being
- while(!rest.isEmpty && (e.covered(rest.head))) {
- end = rest.head
- rest = rest.tail
- }
- result = BlockInteval(start, end) :: result
- }
- }
-
- assert(result.nonEmpty, e)
-
- result
- }
-
- /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains
- * blocks not in the linearization (dead-code?). Is that well-formed or not?
- * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does).
- */
- for (e <- this.method.exh) {
- val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } )
- // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)")
- if(ignore.nonEmpty) {
- e.covered = e.covered filterNot ignore
- }
- }
-
- // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table.
- // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class?
- for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) {
- debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method +
- " from: " + p.start + " to: " + p.end + " catching: " + e.cls);
- val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null
- else javaName(e.cls)
- jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls)
- }
- } // end of genCode()'s genExceptionHandlers()
-
- if (m.exh.nonEmpty) { genExceptionHandlers() }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute).
- // ------------------------------------------------------------------------------------------------------------
-
- case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive.
-
- case class Interval(lstart: asm.Label, lend: asm.Label) {
- @inline final def start = lstart.getOffset
- @inline final def end = lend.getOffset
-
- def precedes(that: Interval): Boolean = { this.end < that.start }
-
- def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) }
-
- def mergeWith(that: Interval): Interval = {
- val newStart = if(this.start <= that.start) this.lstart else that.lstart;
- val newEnd = if(this.end <= that.end) that.lend else this.lend;
- Interval(newStart, newEnd)
- }
-
- def repOK: Boolean = { start <= end }
-
- }
-
- /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */
- object scoping {
-
- private val pending = mutable.Map.empty[Local, mutable.Stack[Label]]
- private var seen: List[LocVarEntry] = Nil
-
- private def fuse(ranges: List[Interval], added: Interval): List[Interval] = {
- assert(added.repOK, added)
- if(ranges.isEmpty) { return List(added) }
- // precond: ranges is sorted by increasing start
- var fused: List[Interval] = Nil
- var done = false
- var rest = ranges
- while(!done && rest.nonEmpty) {
- val current = rest.head
- assert(current.repOK, current)
- rest = rest.tail
- if(added precedes current) {
- fused = fused ::: ( added :: current :: rest )
- done = true
- } else if(current overlaps added) {
- fused = fused ::: ( added.mergeWith(current) :: rest )
- done = true
- }
- }
- if(!done) { fused = fused ::: List(added) }
- assert(repOK(fused), fused)
-
- fused
- }
-
- def pushScope(lv: Local, start: Label) {
- val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label])
- st.push(start)
- }
- def popScope(lv: Local, end: Label, iPos: Position) {
- pending.get(lv) match {
- case Some(st) if st.nonEmpty =>
- val start = st.pop()
- seen ::= LocVarEntry(lv, start, end)
- case _ =>
- // TODO SI-6049
- getCurrentCUnit().warning(iPos, "Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6049")
- }
- }
-
- def getMerged(): collection.Map[Local, List[Interval]] = {
- // TODO should but isn't: unbalanced start(s) of scope(s)
- val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty };
-
- val merged = mutable.Map.empty[Local, List[Interval]]
-
- def addToMerged(lv: Local, start: Label, end: Label) {
- val ranges = merged.getOrElseUpdate(lv, Nil)
- val coalesced = fuse(ranges, Interval(start, end))
- merged.update(lv, coalesced)
- }
-
- for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) }
-
- /* for each var with unbalanced start(s) of scope(s):
- (a) take the earliest start (among unbalanced and balanced starts)
- (b) take the latest end (onePastLast if none available)
- (c) merge the thus made-up interval
- */
- for(Pair(k, st) <- shouldBeEmpty) {
- var start = st.toList.sortBy(_.getOffset).head
- if(merged.isDefinedAt(k)) {
- val balancedStart = merged(k).head.lstart
- if(balancedStart.getOffset < start.getOffset) {
- start = balancedStart;
- }
- }
- val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend;
- val end = endOpt.getOrElse(onePastLast)
- addToMerged(k, start, end)
- }
-
- merged
- }
-
- private def repOK(fused: List[Interval]): Boolean = {
- fused match {
- case Nil => true
- case h :: Nil => h.repOK
- case h :: n :: rest =>
- h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest)
- }
- }
-
- }
-
- def genLocalVariableTable() {
- // adding `this` and method params.
- if (!isStatic) {
- jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0)
- }
- for(lv <- m.params) {
- jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv))
- }
- // adding non-param locals
- var anonCounter = 0
- var fltnd: List[Triple[String, Local, Interval]] = Nil
- for(Pair(local, ranges) <- scoping.getMerged()) {
- var name = javaName(local.sym)
- if (name == null) {
- anonCounter += 1;
- name = "<anon" + anonCounter + ">"
- }
- for(intrvl <- ranges) {
- fltnd ::= Triple(name, local, intrvl)
- }
- }
- // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain).
- val srtd = fltnd.sortBy { kr =>
- val Triple(name: String, local: Local, intrvl: Interval) = kr
-
- Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name)
- }
-
- for(Triple(name, local, Interval(start, end)) <- srtd) {
- jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local))
- }
- // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute"
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position.
- // ------------------------------------------------------------------------------------------------------------
-
- case class LineNumberEntry(line: Int, start: asm.Label)
- var lastLineNr: Int = -1
- var lnEntries: List[LineNumberEntry] = Nil
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()).
- // ------------------------------------------------------------------------------------------------------------
-
- var nextBlock: BasicBlock = linearization.head
-
- def genBlocks(l: List[BasicBlock]): Unit = l match {
- case Nil => ()
- case x :: Nil => nextBlock = null; genBlock(x)
- case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys)
- }
-
- def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = {
- target.isPublic || target.isProtected && {
- (site.enclClass isSubClass target.enclClass) ||
- (site.enclosingPackage == target.privateWithin)
- }
- } // end of genCode()'s isAccessibleFrom()
-
- def genCallMethod(call: CALL_METHOD) {
- val CALL_METHOD(method, style) = call
- val siteSymbol = clasz.symbol
- val hostSymbol = call.hostClass
- val methodOwner = method.owner
- // info calls so that types are up to date; erasure may add lateINTERFACE to traits
- hostSymbol.info ; methodOwner.info
-
- def isInterfaceCall(sym: Symbol) = (
- sym.isInterface && methodOwner != ObjectClass
- || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass)
- )
- // whether to reference the type of the receiver or
- // the type of the method owner (if not an interface!)
- val useMethodOwner = (
- style != Dynamic
- || !isInterfaceCall(hostSymbol) && isAccessibleFrom(methodOwner, siteSymbol)
- || hostSymbol.isBottomClass
- )
- val receiver = if (useMethodOwner) methodOwner else hostSymbol
- val jowner = javaName(receiver)
- val jname = javaName(method)
- val jtype = javaType(method).getDescriptor()
-
- def dbg(invoke: String) {
- debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype))
- }
-
- def initModule() {
- // we initialize the MODULE$ field immediately after the super ctor
- if (isStaticModule(siteSymbol) && !isModuleInitialized &&
- jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
- jname == INSTANCE_CONSTRUCTOR_NAME) {
- isModuleInitialized = true
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr)
- }
- }
-
- style match {
- case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype)
- case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype)
- case Dynamic if isInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype)
- case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype)
- case SuperCall(_) =>
- dbg("invokespecial")
- jcode.invokespecial(jowner, jname, jtype)
- initModule()
- }
- } // end of genCode()'s genCallMethod()
-
- def genBlock(b: BasicBlock) {
- jmethod.visitLabel(labels(b))
-
- import asm.Opcodes;
-
- debuglog("Generating code for block: " + b)
-
- // val lastInstr = b.lastInstruction
-
- for (instr <- b) {
-
- if(instr.pos.isDefined) {
- val iPos = instr.pos
- val currentLineNr = iPos.line
- val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else
- if(!skip) {
- lastLineNr = currentLineNr
- val lineLab = new asm.Label
- jmethod.visitLabel(lineLab)
- lnEntries ::= LineNumberEntry(currentLineNr, lineLab)
- }
- }
-
- (instr.category: @scala.annotation.switch) match {
-
- case icodes.localsCat => (instr: @unchecked) match {
- case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
- case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
- case STORE_THIS(_) =>
- // this only works for impl classes because the self parameter comes first
- // in the method signature. If that changes, this code has to be revisited.
- jmethod.visitVarInsn(Opcodes.ASTORE, 0)
-
- case SCOPE_ENTER(lv) =>
- // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val start = new asm.Label
- jmethod.visitLabel(start)
- scoping.pushScope(lv, start)
- }
-
- case SCOPE_EXIT(lv) =>
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if(relevant) {
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val end = new asm.Label
- jmethod.visitLabel(end)
- scoping.popScope(lv, end, instr.pos)
- }
- }
-
- case icodes.stackCat => (instr: @unchecked) match {
-
- case LOAD_MODULE(module) =>
- // assert(module.isModule, "Expected module: " + module)
- debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags));
- if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) {
- jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- } else {
- jmethod.visitFieldInsn(
- Opcodes.GETSTATIC,
- javaName(module) /* + "$" */ ,
- strMODULE_INSTANCE_FIELD,
- descriptor(module)
- )
- }
-
- case DROP(kind) => emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP)
-
- case DUP(kind) => emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
-
- case LOAD_EXCEPTION(_) => ()
- }
-
- case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant)
-
- case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos)
-
- case icodes.castsCat => (instr: @unchecked) match {
-
- case IS_INSTANCE(tpe) =>
- val jtyp: asm.Type =
- tpe match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
- jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
-
- case CHECK_CAST(tpe) =>
- tpe match {
-
- case REFERENCE(cls) =>
- if (cls != ObjectClass) { // No need to checkcast for Objects
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
- }
-
- case ARRAY(elem) =>
- val iname = javaArrayType(javaType(elem)).getInternalName
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
-
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
-
- }
-
- case icodes.objsCat => (instr: @unchecked) match {
-
- case BOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case UNBOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case NEW(REFERENCE(cls)) =>
- val className = javaName(cls)
- jmethod.visitTypeInsn(Opcodes.NEW, className)
-
- case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
- case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
- }
-
- case icodes.fldsCat => (instr: @unchecked) match {
-
- case lf @ LOAD_FIELD(field, isStatic) =>
- var owner = javaName(lf.hostClass)
- debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags))
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- case STORE_FIELD(field, isStatic) =>
- val owner = javaName(field.owner)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- }
-
- case icodes.mthdsCat => (instr: @unchecked) match {
-
- /** Special handling to access native Array.clone() */
- case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
- val target: String = javaType(call.targetTypeKind).getInternalName
- jcode.invokevirtual(target, "clone", mdesc_arrayClone)
-
- case call @ CALL_METHOD(method, style) => genCallMethod(call)
-
- }
-
- case icodes.arraysCat => (instr: @unchecked) match {
- case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
- case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
- case CREATE_ARRAY(elem, 1) => jcode newarray elem
- case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
- }
-
- case icodes.jumpsCat => (instr: @unchecked) match {
-
- case sw @ SWITCH(tagss, branches) =>
- assert(branches.length == tagss.length + 1, sw)
- val flatSize = sw.flatTagsCount
- val flatKeys = new Array[Int](flatSize)
- val flatBranches = new Array[asm.Label](flatSize)
-
- var restTagss = tagss
- var restBranches = branches
- var k = 0 // ranges over flatKeys and flatBranches
- while(restTagss.nonEmpty) {
- val currLabel = labels(restBranches.head)
- for(cTag <- restTagss.head) {
- flatKeys(k) = cTag;
- flatBranches(k) = currLabel
- k += 1
- }
- restTagss = restTagss.tail
- restBranches = restBranches.tail
- }
- val defaultLabel = labels(restBranches.head)
- assert(restBranches.tail.isEmpty)
- debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
- jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
-
- case JUMP(whereto) =>
- if (nextBlock != whereto) {
- jcode goTo labels(whereto)
- }
-
- case CJUMP(success, failure, cond, kind) =>
- if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF_ICMP(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ICMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- if (nextBlock == success) {
- jcode.emitIF_ACMP(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ACMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else {
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- case FLOAT =>
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- case CZJUMP(success, failure, cond, kind) =>
- if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- val Success = success
- val Failure = failure
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- ((cond, nextBlock) : @unchecked) match {
- case (EQ, Success) => jcode emitIFNONNULL labels(failure)
- case (NE, Failure) => jcode emitIFNONNULL labels(success)
- case (EQ, Failure) => jcode emitIFNULL labels(success)
- case (NE, Success) => jcode emitIFNULL labels(failure)
- case (EQ, _) =>
- jcode emitIFNULL labels(success)
- jcode goTo labels(failure)
- case (NE, _) =>
- jcode emitIFNONNULL labels(success)
- jcode goTo labels(failure)
- }
- } else {
- (kind: @unchecked) match {
- case LONG =>
- emit(Opcodes.LCONST_0)
- emit(Opcodes.LCMP)
- case FLOAT =>
- emit(Opcodes.FCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- emit(Opcodes.DCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate, labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- }
-
- case icodes.retCat => (instr: @unchecked) match {
- case RETURN(kind) => jcode emitRETURN kind
- case THROW(_) => emit(Opcodes.ATHROW)
- }
-
- }
-
- }
-
- } // end of genCode()'s genBlock()
-
- /**
- * Emits one or more conversion instructions based on the types given as arguments.
- *
- * @param from The type of the value to be converted into another type.
- * @param to The type the value will be converted into.
- */
- def emitT2T(from: TypeKind, to: TypeKind) {
- assert(isNonUnitValueTK(from), from)
- assert(isNonUnitValueTK(to), to)
-
- def pickOne(opcs: Array[Int]) {
- val chosen = (to: @unchecked) match {
- case BYTE => opcs(0)
- case SHORT => opcs(1)
- case CHAR => opcs(2)
- case INT => opcs(3)
- case LONG => opcs(4)
- case FLOAT => opcs(5)
- case DOUBLE => opcs(6)
- }
- if(chosen != -1) { emit(chosen) }
- }
-
- if(from == to) { return }
- if((from == BOOL) || (to == BOOL)) {
- // the only conversion involving BOOL that is allowed is (BOOL -> BOOL)
- throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString())
- }
-
- if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already)
-
- val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT)
- val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing
- val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing
- val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) }
-
- (from: @unchecked) match {
- case BYTE => pickOne(fromByte)
- case SHORT => pickOne(fromShort)
- case CHAR => pickOne(fromChar)
- case INT => pickOne(fromInt)
- }
-
- } else { // FLOAT, LONG, DOUBLE
-
- (from: @unchecked) match {
- case FLOAT =>
- import asm.Opcodes.{ F2L, F2D, F2I }
- (to: @unchecked) match {
- case LONG => emit(F2L)
- case DOUBLE => emit(F2D)
- case _ => emit(F2I); emitT2T(INT, to)
- }
-
- case LONG =>
- import asm.Opcodes.{ L2F, L2D, L2I }
- (to: @unchecked) match {
- case FLOAT => emit(L2F)
- case DOUBLE => emit(L2D)
- case _ => emit(L2I); emitT2T(INT, to)
- }
-
- case DOUBLE =>
- import asm.Opcodes.{ D2L, D2F, D2I }
- (to: @unchecked) match {
- case FLOAT => emit(D2F)
- case LONG => emit(D2L)
- case _ => emit(D2I); emitT2T(INT, to)
- }
- }
- }
- } // end of genCode()'s emitT2T()
-
- def genPrimitive(primitive: Primitive, pos: Position) {
-
- import asm.Opcodes;
-
- primitive match {
-
- case Negation(kind) => jcode.neg(kind)
-
- case Arithmetic(op, kind) =>
- op match {
-
- case ADD => jcode.add(kind)
- case SUB => jcode.sub(kind)
- case MUL => jcode.mul(kind)
- case DIV => jcode.div(kind)
- case REM => jcode.rem(kind)
-
- case NOT =>
- if(kind.isIntSizedType) {
- emit(Opcodes.ICONST_M1)
- emit(Opcodes.IXOR)
- } else if(kind == LONG) {
- jmethod.visitLdcInsn(new java.lang.Long(-1))
- jmethod.visitInsn(Opcodes.LXOR)
- } else {
- abort("Impossible to negate an " + kind)
- }
-
- case _ =>
- abort("Unknown arithmetic primitive " + primitive)
- }
-
- // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey)
- // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead.
- // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed).
- case Logical(op, kind) => ((op, kind): @unchecked) match {
- case (AND, LONG) => emit(Opcodes.LAND)
- case (AND, INT) => emit(Opcodes.IAND)
- case (AND, _) =>
- emit(Opcodes.IAND)
- if (kind != BOOL) { emitT2T(INT, kind) }
-
- case (OR, LONG) => emit(Opcodes.LOR)
- case (OR, INT) => emit(Opcodes.IOR)
- case (OR, _) =>
- emit(Opcodes.IOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
-
- case (XOR, LONG) => emit(Opcodes.LXOR)
- case (XOR, INT) => emit(Opcodes.IXOR)
- case (XOR, _) =>
- emit(Opcodes.IXOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
-
- case Shift(op, kind) => ((op, kind): @unchecked) match {
- case (LSL, LONG) => emit(Opcodes.LSHL)
- case (LSL, INT) => emit(Opcodes.ISHL)
- case (LSL, _) =>
- emit(Opcodes.ISHL)
- emitT2T(INT, kind)
-
- case (ASR, LONG) => emit(Opcodes.LSHR)
- case (ASR, INT) => emit(Opcodes.ISHR)
- case (ASR, _) =>
- emit(Opcodes.ISHR)
- emitT2T(INT, kind)
-
- case (LSR, LONG) => emit(Opcodes.LUSHR)
- case (LSR, INT) => emit(Opcodes.IUSHR)
- case (LSR, _) =>
- emit(Opcodes.IUSHR)
- emitT2T(INT, kind)
- }
-
- case Comparison(op, kind) => ((op, kind): @unchecked) match {
- case (CMP, LONG) => emit(Opcodes.LCMP)
- case (CMPL, FLOAT) => emit(Opcodes.FCMPL)
- case (CMPG, FLOAT) => emit(Opcodes.FCMPG)
- case (CMPL, DOUBLE) => emit(Opcodes.DCMPL)
- case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html
- }
-
- case Conversion(src, dst) =>
- debuglog("Converting from: " + src + " to: " + dst)
- if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) }
- else { emitT2T(src, dst) }
-
- case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH)
-
- case StartConcat =>
- jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
- jmethod.visitInsn(Opcodes.DUP)
- jcode.invokespecial(
- StringBuilderClassName,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid
- )
-
- case StringConcat(el) =>
- val jtype = el match {
- case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT
- case _ => javaType(el)
- }
- jcode.invokevirtual(
- StringBuilderClassName,
- "append",
- asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*)
- )
-
- case EndConcat =>
- jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString)
-
- case _ => abort("Unimplemented primitive " + primitive)
- }
- } // end of genCode()'s genPrimitive()
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 6 of genCode(): the executable part of genCode() starts here.
- // ------------------------------------------------------------------------------------------------------------
-
- genBlocks(linearization)
-
- jmethod.visitLabel(onePastLast)
-
- if(emitLines) {
- for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) }
- }
- if(emitVars) { genLocalVariableTable() }
-
- } // end of BytecodeGenerator.genCode()
-
-
- ////////////////////// local vars ///////////////////////
-
- // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe))
-
- def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1
-
- // def indexOf(m: IMethod, sym: Symbol): Int = {
- // val Some(local) = m lookupLocal sym
- // indexOf(local)
- // }
-
- @inline final def indexOf(local: Local): Int = {
- assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ")
- local.index
- }
-
- /**
- * Compute the indexes of each local variable of the given method.
- * *Does not assume the parameters come first!*
- */
- def computeLocalVarsIndex(m: IMethod) {
- var idx = if (m.symbol.isStaticMember) 0 else 1;
-
- for (l <- m.params) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
-
- for (l <- m.locals if !l.arg) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
- }
-
- } // end of class JPlainBuilder
-
-
- /** builder of mirror classes */
- class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) {
-
- 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.
- */
- def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) {
- assert(modsym.companionClass == NoSymbol, modsym)
- innerClassBuffer.clear()
- this.cunit = cunit
- val moduleName = javaName(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 = createJClass(flags,
- mirrorName,
- null /* no java-generic-signature */,
- JAVA_LANG_OBJECT.getInternalName,
- EMPTY_STRING_ARRAY)
-
- log("Dumping mirror class for '%s'".format(mirrorName))
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- 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)
-
- // typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)
-
- addInnerClasses(modsym, mirrorClass)
- mirrorClass.visitEnd()
- writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
- }
-
-
- } // end of class JMirrorBuilder
-
-
- /** builder of bean info classes */
- class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
-
- /**
- * Generate a bean info class that describes the given class.
- *
- * @author Ross Judson (ross.judson@soletta.com)
- */
- def genBeanInfoClass(clasz: IClass) {
-
- // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip")
- // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName")
- // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription")
- // val description = c.symbol getAnnotation BeanDescriptionAttr
- // informProgress(description.toString)
- innerClassBuffer.clear()
-
- val flags = mkFlags(
- javaFlags(clasz.symbol),
- if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val beanInfoName = (javaName(clasz.symbol) + "BeanInfo")
- val beanInfoClass = createJClass(
- flags,
- beanInfoName,
- null, // no java-generic-signature
- "scala/beans/ScalaBeanInfo",
- EMPTY_STRING_ARRAY
- )
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- beanInfoClass.visitSource(
- clasz.cunit.source.toString,
- null /* SourceDebugExtension */
- )
-
- var fieldList = List[String]()
-
- for (f <- clasz.fields if f.symbol.hasGetter;
- g = f.symbol.getter(clasz.symbol);
- s = f.symbol.setter(clasz.symbol);
- if g.isPublic && !(f.symbol.name startsWith "$")
- ) {
- // inserting $outer breaks the bean
- fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList
- }
-
- val methodList: List[String] =
- for (m <- clasz.methods
- if !m.symbol.isConstructor &&
- m.symbol.isPublic &&
- !(m.symbol.name startsWith "$") &&
- !m.symbol.isGetter &&
- !m.symbol.isSetter)
- yield javaName(m.symbol)
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- val constructor = beanInfoClass.visitMethod(
- asm.Opcodes.ACC_PUBLIC,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- EMPTY_STRING_ARRAY // no throwable exceptions
- )
-
- // constructor typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING)
- val conJType: asm.Type =
- asm.Type.getMethodType(
- asm.Type.VOID_TYPE,
- Array(javaType(ClassClass), 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 typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- constructor.visitCode()
-
- constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
- // push the class
- constructor.visitLdcInsn(javaType(clasz.symbol))
-
- // 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()
-
- addInnerClasses(clasz.symbol, beanInfoClass)
- beanInfoClass.visitEnd()
-
- writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
- }
-
- } // end of class JBeanInfoBuilder
-
- /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for.
- * In particualr, IMethod.normalize() doesn't collapseJumpChains().
- *
- * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them).
- */
- object newNormal {
-
- def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] }
-
- /** Prune from an exception handler those covered blocks which are jump-only. */
- private def coverWhatCountsOnly(m: IMethod): Boolean = {
- assert(m.hasCode, "code-less method")
-
- var wasReduced = false
- for(h <- m.exh) {
- val shouldntCover = (h.covered filter startsWithJump)
- if(shouldntCover.nonEmpty) {
- wasReduced = true
- h.covered --= shouldntCover // not removing any block on purpose.
- }
- }
-
- wasReduced
- }
-
- /** An exception handler is pruned provided any of the following holds:
- * (1) it covers nothing (for example, this may result after removing unreachable blocks)
- * (2) each block it covers is of the form: JUMP(_)
- * Return true iff one or more ExceptionHandlers were removed.
- *
- * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable)
- * won't be able to cause a class-loading-exception. As a result, behavior can be different.
- */
- private def elimNonCoveringExh(m: IMethod): Boolean = {
- assert(m.hasCode, "code-less method")
-
- def isRedundant(eh: ExceptionHandler): Boolean = {
- (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol`
- eh.covered.isEmpty
- || (eh.covered forall startsWithJump)
- )
- }
-
- var wasReduced = false
- val toPrune = (m.exh.toSet filter isRedundant)
- if(toPrune.nonEmpty) {
- wasReduced = true
- for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h)
- m.exh = (m.exh filterNot toPrune)
- }
-
- wasReduced
- }
-
- private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = {
- b.toList match {
- case JUMP(whereto) :: rest =>
- assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)")
- Some(whereto)
- case _ => None
- }
- }
-
- private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) }
-
- /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow.
- Those BBs in the argument are also included in the result */
- private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = {
- val result = new mutable.ListBuffer[BasicBlock]
- var toVisit: List[BasicBlock] = starters.toList.distinct
- while(toVisit.nonEmpty) {
- val h = toVisit.head
- toVisit = toVisit.tail
- result += h
- for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit }
- }
- result.toList
- }
-
- /** Returns:
- * for single-block self-loops, the pair (start, Nil)
- * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target)
- * otherwise a pair consisting of:
- * (a) the endpoint of a (single or multi-hop) chain of JUMPs
- * (such endpoint does not start with a JUMP and therefore is not part of the chain); and
- * (b) the chain (ie blocks to be removed when collapsing the chain of jumps).
- * Precondition: the BasicBlock given as argument starts with an unconditional JUMP.
- */
- private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = {
- assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.")
- var hops: List[BasicBlock] = Nil
- var prev = start
- var done = false
- do {
- done = isJumpOnly(prev) match {
- case Some(dest) =>
- if (dest == start) { return (start, hops) } // leave infinite-loops in place
- hops ::= prev
- if (hops.contains(dest)) {
- // leave infinite-loops in place
- return (dest, hops filterNot (dest eq))
- }
- prev = dest;
- false
- case None => true
- }
- } while(!done)
-
- (prev, hops)
- }
-
- /**
- * Collapse a chain of "jump-only" blocks such as:
- *
- * JUMP b1;
- * b1: JUMP b2;
- * b2: JUMP ... etc.
- *
- * by re-wiring predecessors to target directly the "final destination".
- * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed.
-
- * Returns true if any replacement was made, false otherwise.
- *
- * In more detail:
- * Starting at each of the entry points (m.startBlock, the start block of each exception handler)
- * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D.
- * The blocks thus skipped are also removed from IMethod.blocks.
- *
- * Rationale for this normalization:
- * test/files/run/private-inline.scala after -optimize is chock full of
- * BasicBlocks containing just JUMP(whereTo), where no exception handler straddles them.
- * They should be collapsed by IMethod.normalize() but aren't.
- * That was fine in FJBG times when by the time the exception table was emitted,
- * it already contained "anchored" labels (ie instruction offsets were known)
- * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question)
- * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range"
- * Now that visitTryCatchBlock() must be called before Labels are resolved,
- * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP).
- */
- private def collapseJumpOnlyBlocks(m: IMethod): Boolean = {
- assert(m.hasCode, "code-less method")
-
- /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */
- def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = {
- assert(startsWithJump(jumpStart), "not part of a jump-chain")
- val Pair(dest, redundants) = finalDestination(jumpStart)
- (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap
- }
-
- def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) {
- for(Pair(oldTarget, newTarget) <- detour.iterator) {
- if(m.startBlock == oldTarget) {
- m.code.startBlock = newTarget
- }
- for(eh <- m.exh; if eh.startBlock == oldTarget) {
- eh.setStartBlock(newTarget)
- }
- for(b <- m.blocks; if !detour.isDefinedAt(b)) {
- val idxLast = (b.size - 1)
- b.lastInstruction match {
- case JUMP(whereto) =>
- if (whereto == oldTarget) {
- b.replaceInstruction(idxLast, JUMP(newTarget))
- }
- case CJUMP(succ, fail, cond, kind) =>
- if ((succ == oldTarget) || (fail == oldTarget)) {
- b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ),
- detour.getOrElse(fail, fail),
- cond, kind))
- }
- case CZJUMP(succ, fail, cond, kind) =>
- if ((succ == oldTarget) || (fail == oldTarget)) {
- b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ),
- detour.getOrElse(fail, fail),
- cond, kind))
- }
- case SWITCH(tags, labels) =>
- if(labels exists (detour.isDefinedAt(_))) {
- val newLabels = (labels map { lab => detour.getOrElse(lab, lab) })
- b.replaceInstruction(idxLast, SWITCH(tags, newLabels))
- }
- case _ => ()
- }
- }
- }
- }
-
- /* remove from all containers that may contain a reference to */
- def elide(redu: BasicBlock) {
- assert(m.startBlock != redu, "startBlock should have been re-wired by now")
- m.code.removeBlock(redu);
- }
-
- var wasReduced = false
- val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock));
-
- var elided = mutable.Set.empty[BasicBlock] // debug
- var newTargets = mutable.Set.empty[BasicBlock] // debug
-
- for (ep <- entryPoints) {
- var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over
- while(reachable.nonEmpty) {
- val h = reachable.head
- reachable = reachable.tail
- if(startsWithJump(h)) {
- val detour = realTarget(h)
- if(detour.nonEmpty) {
- wasReduced = true
- reachable = (reachable filterNot (detour.keySet.contains(_)))
- rephraseGotos(detour)
- detour.keySet foreach elide
- elided ++= detour.keySet
- newTargets ++= detour.values
- }
- }
- }
- }
- assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain")
-
- wasReduced
- }
-
- def normalize(m: IMethod) {
- if(!m.hasCode) { return }
- collapseJumpOnlyBlocks(m)
- var wasReduced = false;
- do {
- wasReduced = false
- // Prune from an exception handler those covered blocks which are jump-only.
- wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place
- // Prune exception handlers covering nothing.
- wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m)
-
- // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?)
- } while (wasReduced)
-
- // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards.
- }
-
- }
-
-}
+/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import java.nio.ByteBuffer +import scala.collection.{ mutable, immutable } +import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } +import scala.tools.nsc.symtab._ +import scala.tools.nsc.io.AbstractFile + +import scala.tools.asm +import asm.Label + +/** + * @author Iulian Dragos (version 1.0, FJBG-based implementation) + * @author Miguel Garcia (version 2.0, ASM-based implementation) + * + * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf + */ +abstract class GenASM extends SubComponent with BytecodeWriters { + import global._ + import icodes._ + import icodes.opcodes._ + import definitions._ + + val phaseName = "jvm" + + /** Create a new phase */ + override def newPhase(p: Phase): Phase = new AsmPhase(p) + + private def outputDirectory(sym: Symbol): AbstractFile = + settings.outputDirs outputDirFor beforeFlatten(sym.sourceFile) + + private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + var dir = base + val pathParts = clsName.split("[./]").toList + for (part <- pathParts.init) { + dir = dir.subdirectoryNamed(part) + } + dir.fileNamed(pathParts.last + suffix) + } + private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = + getFile(outputDirectory(sym), clsName, suffix) + + /** JVM code generation phase + */ + class AsmPhase(prev: Phase) extends ICodePhase(prev) { + def name = phaseName + override def erasedTypes = true + def apply(cls: IClass) = sys.error("no implementation") + + val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") + + def isJavaEntryPoint(icls: IClass) = { + val sym = icls.symbol + def fail(msg: String, pos: Position = sym.pos) = { + icls.cunit.warning(sym.pos, + sym.name + " 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(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 == 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. + beforeErasure { + val companion = sym.linkedClassOfClass + val companionMain = companion.tpe.member(nme.main) + + if (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 { 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 + isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail("don't know what this is: " + tp, m.pos) + } + } + } + } + } + + private def initBytecodeWriter(entryPoints: List[IClass]): 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 (_.symbol fullName '.') match { + case Nil => + log("No Main-Class designated or discovered.") + case name :: Nil => + log("Unique entry point: setting Main-Class to " + name) + settings.mainClass.value = name + case names => + log("No Main-Class due to multiple entry points:\n " + names.mkString("\n ")) + } + } + else log("Main-Class was specified: " + settings.mainClass.value) + + new DirectToJarfileWriter(f.file) + + case _ => + if (settings.Ygenjavap.isDefault) { + if(settings.Ydumpclasses.isDefault) + new ClassBytecodeWriter { } + else + new ClassBytecodeWriter with DumpBytecodeWriter { } + } + else new ClassBytecodeWriter with JavapBytecodeWriter { } + + // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point. + // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are: + // (a) unreadable pickle; + // (b) two constant pools, while having identical contents, are displayed differently due to physical layout. + // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead. + } + } + + override def run() { + + if (settings.debug.value) + inform("[running phase " + name + " on icode]") + + if (settings.Xdce.value) + for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) + icodes.classes -= sym + + // For predictably ordered error messages. + var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName) + + debuglog("Created new bytecode generator for " + classes.size + " classes.") + val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) + val plainCodeGen = new JPlainBuilder(bytecodeWriter) + val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter) + val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter) + + while(!sortedClasses.isEmpty) { + val c = sortedClasses.head + + if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { + if (c.symbol.companionClass == NoSymbol) { + mirrorCodeGen.genMirrorClass(c.symbol, c.cunit) + } else { + log("No mirror class for module with linked class: " + c.symbol.fullName) + } + } + + plainCodeGen.genClass(c) + + if (c.symbol hasAnnotation BeanInfoAttr) { + beanInfoCodeGen.genBeanInfoClass(c) + } + + sortedClasses = sortedClasses.tail + classes -= c.symbol // GC opportunity + } + + bytecodeWriter.close() + classes.clear() + reverseJavaName.clear() + + /* don't javaNameCache.clear() because that causes the following tests to fail: + * test/files/run/macro-repl-dontexpand.scala + * test/files/jvm/interpreter.scala + * TODO but why? what use could javaNameCache possibly see once GenJVM is over? + */ + + /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification: + * + * (1) call the asm.util.CheckAdapter.verify() overload: + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) + * + * (2) passing a custom ClassLoader to verify inter-dependent classes. + * + * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). + */ + + } // end of AsmPhase.run() + + } // end of class AsmPhase + + var pickledBytes = 0 // statistics + + // Don't put this in per run caches. Contains entries for classes as well as members. + val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List( + NothingClass -> binarynme.RuntimeNothing, + RuntimeNothingClass -> binarynme.RuntimeNothing, + NullClass -> binarynme.RuntimeNull, + RuntimeNullClass -> binarynme.RuntimeNull + ) + + // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. + val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List( + binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. + binarynme.RuntimeNull.toString() -> RuntimeNullClass + ) + + private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + @inline final private def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + @inline final private def isRemote(s: Symbol) = (s hasAnnotation RemoteAttr) + + /** + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + */ + def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private + // PP: why are they only being marked private at this stage and not earlier? + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) + + // Final: the only fields which can receive ACC_FINAL are eager vals. + // Neither vars nor lazy vals can, because: + // + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + // Nested objects won't receive ACC_FINAL in order to allow for their overriding. + + val finalFlag = ( + (sym.hasFlag(Flags.FINAL) || isTopLevelModule(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isHidden) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 + ) + } + + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | mkFlags( + if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } + + def isTopLevelModule(sym: Symbol): Boolean = + afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + + def isStaticModule(sym: Symbol): Boolean = { + sym.isModuleClass && !sym.isImplClass && !sym.isLifted + } + + // ----------------------------------------------------------------------------------------- + // 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 + // ----------------------------------------------------------------------------------------- + + /** + * Given an internal name (eg "java/lang/Integer") returns the class symbol for it. + * + * Better not to need this method (an example where control flow arrives here is welcome). + * This method is invoked only upon both (1) and (2) below happening: + * (1) providing an asm.ClassWriter with an internal name by other means than javaName() + * (2) forgetting to track the corresponding class-symbol in reverseJavaName. + * + * (The first item is already unlikely because we rely on javaName() + * to do the bookkeeping for entries that should go in innerClassBuffer.) + * + * (We could do completely without this method at the expense of computing stack-map-frames ourselves and + * invoking visitFrame(), but that would require another pass over all instructions.) + * + * Right now I can't think of any invocation of visitSomething() on MethodVisitor + * where we hand an internal name not backed by a reverseJavaName. + * However, I'm leaving this note just in case any such oversight is discovered. + */ + def inameToSymbol(iname: String): Symbol = { + val name = global.newTypeName(iname) + val res0 = + if (nme.isModuleName(name)) rootMirror.getModule(nme.stripModuleSuffix(name)) + else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested). + assert(res0 != NoSymbol) + val res = jsymbol(res0) + res + } + + def jsymbol(sym: Symbol): Symbol = { + if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass + else if(sym.isModule) sym.moduleClass + else sym // we track only module-classes and plain-classes + } + + private def superClasses(s: Symbol): List[Symbol] = { + assert(!s.isInterface) + s.superClass match { + case NoSymbol => List(s) + case sc => s :: superClasses(sc) + } + } + + private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = { + assert(!(as contains NoSymbol)) + assert(!(bs contains NoSymbol)) + var chainA = as + var chainB = bs + var fcs: Symbol = NoSymbol + 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 == NoSymbol) + fcs + } + + @inline final private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = { + + assert(a.isClass) + assert(b.isClass) + + val res = Pair(a.isInterface, b.isInterface) match { + case (true, true) => + global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents + case (true, false) => + if(b isSubClass a) a else ObjectClass + case (false, true) => + if(a isSubClass b) b else ObjectClass + case _ => + firstCommonSuffix(superClasses(a), superClasses(b)) + } + assert(res != NoSymbol) + res + } + + /* 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 */ + def getCommonSuperClass(inameA: String, inameB: String): String = { + val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA)) + val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB)) + + // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString() + // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType + val lcaSym = jvmWiseLUB(a, b) + val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer. + val oldsym = reverseJavaName.put(lcaName, lcaSym) + assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption") + assert(lcaName != "scala/Any") + + lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching. + } + + class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { + override def getCommonSuperClass(iname1: String, iname2: String): String = { + GenASM.this.getCommonSuperClass(iname1, iname2) + } + } + + // ----------------------------------------------------------------------------------------- + // constants + // ----------------------------------------------------------------------------------------- + + private val classfileVersion: Int = settings.target.value match { + case "jvm-1.5" => asm.Opcodes.V1_5 + case "jvm-1.5-asm" => asm.Opcodes.V1_5 + case "jvm-1.6" => asm.Opcodes.V1_6 + case "jvm-1.7" => asm.Opcodes.V1_7 + } + + private val majorVersion: Int = (classfileVersion & 0xFF) + private val emitStackMapFrame = (majorVersion >= 50) + + private val extraProc: Int = mkFlags( + asm.ClassWriter.COMPUTE_MAXS, + if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 + ) + + val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object") + val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String") + + /** basic functionality for class file building */ + abstract class JBuilder(bytecodeWriter: BytecodeWriter) { + + val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type] + val EMPTY_STRING_ARRAY = Array.empty[String] + + val mdesc_arglessvoid = "()V" + + val CLASS_CONSTRUCTOR_NAME = "<clinit>" + val INSTANCE_CONSTRUCTOR_NAME = "<init>" + + val INNER_CLASSES_FLAGS = + (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT) + + // ----------------------------------------------------------------------------------------- + // factory methods + // ----------------------------------------------------------------------------------------- + + /** + * Returns a new ClassWriter for the class given by arguments. + * + * @param access the class's access flags. This parameter also indicates if the class is deprecated. + * + * @param name the internal name of the class. + * + * @param signature the signature of this class. May be <tt>null</tt> if + * the class is not a generic one, and does not extend or implement + * generic classes or interfaces. + * + * @param superName the internal of name of the super class. For interfaces, + * the super class is {@link Object}. May be <tt>null</tt>, but + * only for the {@link Object} class. + * + * @param interfaces the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + */ + def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = { + val cw = new CClassWriter(extraProc) + cw.visit(classfileVersion, + access, name, signature, + superName, interfaces) + + cw + } + + 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) + } + + // ----------------------------------------------------------------------------------------- + // utitilies useful when emitting plain, mirror, and beaninfo classes. + // ----------------------------------------------------------------------------------------- + + def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { + try { + val arr = jclass.toByteArray() + bytecodeWriter.writeClass(label, jclassName, arr, sym) + } catch { + case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => + // TODO check where ASM throws the equivalent of CodeSizeTooBigException + log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).") + } + } + + /** Specialized array conversion to prevent calling + * java.lang.reflect.Array.newInstance via TraversableOnce.toArray + */ + def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a } + def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a } + + // ----------------------------------------------------------------------------------------- + // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances). + // These getters track behind the scenes the inner classes referred to in the class being emitted, + // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()` + // (which also adds as member classes those inner classes that have been declared, + // thus also covering the case of inner classes declared but otherwise not referred). + // ----------------------------------------------------------------------------------------- + + val innerClassBuffer = mutable.LinkedHashSet[Symbol]() + + /** For given symbol return a symbol corresponding to a class that should be declared as inner class. + * + * For example: + * class A { + * class B + * object C + * } + * + * then method will return: + * NoSymbol for A, + * the same symbol for A.B (corresponding to A$B class), and + * A$C$ symbol for A.C. + */ + def innerClassSymbolFor(s: Symbol): Symbol = + if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol + + /** Return the a name of this symbol that can be used on the Java platform. It removes spaces from names. + * + * Special handling: + * scala.Nothing erases to scala.runtime.Nothing$ + * scala.Null erases to scala.runtime.Null$ + * + * This is needed because they are not real classes, and they mean + * 'abrupt termination upon evaluation of that expression' or null respectively. + * This handling is done already in GenICode, but here we need to remove + * references from method signatures to these types, because such classes + * cannot exist in the classpath: the type checker will be very confused. + */ + def javaName(sym: Symbol): String = { + + /** + * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer + * + * Note: This method is called recursively thus making sure that we add complete chain + * of inner class all until root class. + */ + def collectInnerClass(s: Symbol): Unit = { + // TODO: some beforeFlatten { ... } which accounts for + // being nested in parameterized classes (if we're going to selectively flatten.) + val x = innerClassSymbolFor(s) + if(x ne NoSymbol) { + assert(x.isClass, "not an inner-class symbol") + val isInner = !x.rawowner.isPackageClass + if (isInner) { + innerClassBuffer += x + collectInnerClass(x.rawowner) + } + } + } + + collectInnerClass(sym) + + var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod)) + val cachedJN = javaNameCache.getOrElseUpdate(sym, { + if (hasInternalName) { sym.javaBinaryName } + else { sym.javaSimpleName } + }) + + if(emitStackMapFrame && hasInternalName) { + val internalName = cachedJN.toString() + val trackedSym = jsymbol(sym) + reverseJavaName.get(internalName) match { + case None => + reverseJavaName.put(internalName, trackedSym) + case Some(oldsym) => + assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass), // In contrast, neither NothingClass nor NullClass show up bytecode-level. + "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.") + } + } + + cachedJN.toString + } + + def descriptor(t: Type): String = { javaType(t).getDescriptor } + def descriptor(k: TypeKind): String = { javaType(k).getDescriptor } + def descriptor(s: Symbol): String = { javaType(s).getDescriptor } + + def javaType(tk: TypeKind): asm.Type = { + if(tk.isValueType) { + if(tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL => asm.Type.BOOLEAN_TYPE + case BYTE => asm.Type.BYTE_TYPE + case SHORT => asm.Type.SHORT_TYPE + case CHAR => asm.Type.CHAR_TYPE + case INT => asm.Type.INT_TYPE + } + } else { + (tk: @unchecked) match { + case UNIT => asm.Type.VOID_TYPE + case LONG => asm.Type.LONG_TYPE + case FLOAT => asm.Type.FLOAT_TYPE + case DOUBLE => asm.Type.DOUBLE_TYPE + } + } + } else { + assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway) + (tk: @unchecked) match { + case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) + case ARRAY(elem) => javaArrayType(javaType(elem)) + } + } + } + + def javaType(t: Type): asm.Type = javaType(toTypeKind(t)) + + def javaType(s: Symbol): asm.Type = { + if (s.isMethod) { + val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType); + asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _* ) + } else { javaType(s.tpe) } + } + + def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) } + + def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } + + def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { + /** The outer name for this inner class. Note that it returns null + * when the inner class should not get an index in the constant pool. + * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. + */ + def outerName(innerSym: Symbol): String = { + if (innerSym.originalEnclosingMethod != NoSymbol) + null + else { + val outerName = javaName(innerSym.rawowner) + if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName)) + else outerName + } + } + + def innerName(innerSym: Symbol): String = + if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) + null + else + innerSym.rawname + innerSym.moduleSuffix + + // add inner classes which might not have been referenced yet + afterErasure { + for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) + innerClassBuffer += m + } + + val allInners: List[Symbol] = innerClassBuffer.toList + if (allInners.nonEmpty) { + debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.") + + // entries ready to be serialized into the classfile, used to detect duplicates. + val entries = mutable.Map.empty[String, String] + + // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler + for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? + val flags = mkFlags( + if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + javaFlags(innerSym), + if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag + ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) + val jname = javaName(innerSym) // never null + val oname = outerName(innerSym) // null when method-enclosed + val iname = innerName(innerSym) // null for anonymous inner class + + // Mimicking javap inner class output + debuglog( + if (oname == null || iname == null) "//class " + jname + else "//%s=class %s of class %s".format(iname, jname, oname) + ) + + assert(jname != null, "javaName is broken.") // documentation + val doAdd = entries.get(jname) 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 == oname, "duplicate") + false + case None => true + } + + if(doAdd) { + entries += (jname -> oname) + jclass.visitInnerClass(jname, oname, iname, flags) + } + + /* + * TODO assert (JVMS 4.7.6 The InnerClasses attribute) + * If a class file has a version number that is greater than or equal to 51.0, and + * has an InnerClasses attribute in its attributes table, then for all entries in the + * classes array of the InnerClasses attribute, the value of the + * outer_class_info_index item must be zero if the value of the + * inner_name_index item is zero. + */ + + } + } + } + + } // end of class JBuilder + + + /** functionality for building plain and mirror classes */ + abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + + def debugLevel = settings.debuginfo.indexOfChoice + + val emitSource = debugLevel >= 1 + val emitLines = debugLevel >= 2 + val emitVars = debugLevel >= 3 + + // ----------------------------------------------------------------------------------------- + // more constants + // ----------------------------------------------------------------------------------------- + + val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL + + val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + // ----------------------------------------------------------------------------------------- + // 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.) + // ----------------------------------------------------------------------------------------- + + 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 + } + + def pickleMarkerLocal = { + createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) + } + + 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. + * + */ + 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, List((nme.bytes, sigBytes))) + } + pickledBytes += pickle.writeIndex + currentRun.symData -= sym + currentRun.symData -= sym.companionSymbol + Some(scalaAnnot) + case _ => + None + } + } + + /** + * 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. + * + */ + def getExceptions(excs: List[AnnotationInfo]): List[String] = { + for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass) + yield { + val Literal(const) = exc + javaName(const.typeValue.typeSymbol) + } + } + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be un-initialized + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + annot.args.isEmpty && + !annot.matches(DeprecatedAttr) + + // @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.value + || sym.isHidden + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + + 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 + */ + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + + if (!needsGenericSignature(sym)) { return null } + + val memberTpe = beforeErasure(owner.thisType.memberInfo(sym)) + + 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 _ => false } + } + + if (settings.Xverify.value) { + // 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.SignatureChecker + if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { SignatureChecker checkFieldSignature sig } + else { SignatureChecker checkClassSignature sig } + } + + if(!isValidSignature) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created invalid generic signature for %s in %s + |signature: %s + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created generic signature for %s in %s that does not conform to its erasure + |signature: %s + |original type: %s + |normalized type: %s + |erasure type: %s + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) + return null + } + } + + sig + } + + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.size) + var idx = 0 + while(idx < bytes.size) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + // TODO this method isn't exercised during bootstrapping. Open question: is it bug free? + private 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.size) { + val newEncLength = encLength.toLong + (if(bSeven(offset) == 0) 2 else 1) + if(newEncLength > 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += 1 + 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 + } + + 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) + } + + def emitArgument(av: asm.AnnotationVisitor, + name: String, + arg: ClassfileAnnotArg) { + arg 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, javaType(const.typeValue)) + 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. + val assocValue = (if(sb.fitsInOneString) strEncode(sb) else arrEncode(sb)) + av.visit(name, assocValue) + // 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) + } + } + + def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { + for ((name, value) <- assocs) { + emitArgument(av, name.toString(), value) + } + av.visitEnd() + } + + 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) + } + } + + 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) + } + } + + 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) + } + } + + def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { + val annotationss = pannotss map (_ filter shouldEmitAnnotation) + if (annotationss forall (_.isEmpty)) return + for (Pair(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) + } + } + + /** Adds a @remote annotation, actual use unknown. + * + * Invoked from genMethod() and addForwarder(). + */ + def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { + val needsAnnotation = ( + ( isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !(meth.throwsAnnotations contains RemoteExceptionClass) + ) + if (needsAnnotation) { + val c = Constant(RemoteExceptionClass.tpe) + val arg = Literal(c) setType c.tpe + meth.addAnnotation(ThrowsClass, arg) + } + } + + // ----------------------------------------------------------------------------------------- + // Static forwarders (related to mirror classes but also present in + // a plain class lacking companion module, for details see `isCandidateForForwarders`). + // ----------------------------------------------------------------------------------------- + + val ExcludedForwarderFlags = { + import Flags._ + // Should include DEFERRED but this breaks findMember. + ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags ) + } + + /** Add a forwarder for method m. Used only from addForwarders(). */ + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + val moduleName = javaName(module) + val methodInfo = module.thisType.memberInfo(m) + val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType + // 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 = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) + val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) + val thrownExceptions: List[String] = getExceptions(throws) + + val jReturnType = javaType(methodInfo.resultType) + val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*) + val mirrorMethodName = javaName(m) + val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( + flags, + mirrorMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + // typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + emitAnnotations(mirrorMethod, others) + emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) + + // typestate: entering mode with valid call sequences: + // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + + 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.getSort() != asm.Type.METHOD, jparamType) + index += jparamType.getSize() + } + + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(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. + */ + def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { + assert(moduleClass.isModuleClass, moduleClass) + debuglog("Dumping mirror class for object: " + moduleClass) + + val linkedClass = moduleClass.companionClass + val linkedModule = linkedClass.companionSymbol + lazy val conflictingNames: Set[Name] = { + linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet + } + debuglog("Potentially conflicting names for forwarders: " + conflictingNames) + + for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) { + if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor) + debuglog("No forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) + else if (conflictingNames(m.name)) + log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name)) + else { + log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) + addForwarder(isRemoteClass, jclass, moduleClass, m) + } + } + } + + } // end of class JCommonBuilder + + + trait JAndroidBuilder { + self: JPlainBuilder => + + /** 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. + */ + private val androidFieldName = newTermName("CREATOR") + + private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") + private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") + + def isAndroidParcelableClass(sym: Symbol) = + (AndroidParcelableInterface != NoSymbol) && + (sym.parentSymbols contains AndroidParcelableInterface) + + /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */ + def addCreatorCode(block: BasicBlock) { + val fieldSymbol = ( + clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL) + setInfo AndroidCreatorClass.tpe + ) + val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName) + clasz addField new IField(fieldSymbol) + block emit CALL_METHOD(methodSymbol, Static(false)) + block emit STORE_FIELD(fieldSymbol, true) + } + + def legacyAddCreatorCode(clinit: asm.MethodVisitor) { + val creatorType: asm.Type = javaType(AndroidCreatorClass) + val tdesc_creator = creatorType.getDescriptor + + jclass.visitField( + PublicStaticFinal, + androidFieldName, + tdesc_creator, + null, // no java-generic-signature + null // no initial value + ).visitEnd() + + val moduleName = javaName(clasz.symbol)+"$" + + // GETSTATIC `moduleName`.MODULE$ : `moduleName`; + clinit.visitFieldInsn( + asm.Opcodes.GETSTATIC, + moduleName, + strMODULE_INSTANCE_FIELD, + asm.Type.getObjectType(moduleName).getDescriptor + ) + + // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; + clinit.visitMethodInsn( + asm.Opcodes.INVOKEVIRTUAL, + moduleName, + androidFieldName, + asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*) + ) + + // PUTSTATIC `thisName`.CREATOR; + clinit.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + androidFieldName, + tdesc_creator + ) + } + + } // end of trait JAndroidBuilder + + /** Map from type kinds to the Java reference types. + * It is used to push class literals onto the operand stack. + * @see Predef.classOf + * @see genConstant() + */ + private val classLiteral = immutable.Map[TypeKind, asm.Type]( + UNIT -> asm.Type.getObjectType("java/lang/Void"), + BOOL -> asm.Type.getObjectType("java/lang/Boolean"), + BYTE -> asm.Type.getObjectType("java/lang/Byte"), + SHORT -> asm.Type.getObjectType("java/lang/Short"), + CHAR -> asm.Type.getObjectType("java/lang/Character"), + INT -> asm.Type.getObjectType("java/lang/Integer"), + LONG -> asm.Type.getObjectType("java/lang/Long"), + FLOAT -> asm.Type.getObjectType("java/lang/Float"), + DOUBLE -> asm.Type.getObjectType("java/lang/Double") + ) + + def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT } + + case class MethodNameAndType(mname: String, mdesc: String) + + private val jBoxTo: Map[TypeKind, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , + BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , + CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , + SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , + INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , + LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , + FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , + DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) + ) + } + + private val jUnboxTo: Map[TypeKind, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , + BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , + CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , + SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , + INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , + LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , + FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , + DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") + ) + } + + case class BlockInteval(start: BasicBlock, end: BasicBlock) + + /** builder of plain classes */ + class JPlainBuilder(bytecodeWriter: BytecodeWriter) + extends JCommonBuilder(bytecodeWriter) + with JAndroidBuilder { + + val MIN_SWITCH_DENSITY = 0.7 + + val StringBuilderClassName = javaName(definitions.StringBuilderClass) + val BoxesRunTime = "scala/runtime/BoxesRunTime" + + val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName) + val mdesc_toString = "()Ljava/lang/String;" + val mdesc_arrayClone = "()Ljava/lang/Object;" + + val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J" + + def isParcelableClass = isAndroidParcelableClass(clasz.symbol) + + def serialVUID: Option[Long] = clasz.symbol getAnnotation SerialVersionUIDAttr collect { + case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue + } + + private def getSuperInterfaces(c: IClass): Array[String] = { + + // Additional interface parents based on annotations and other cues + def newParentForAttr(attr: Symbol): Option[Symbol] = attr match { + case SerializableAttr => Some(SerializableClass) + case CloneableAttr => Some(CloneableClass) + case RemoteAttr => Some(RemoteInterfaceClass) + case _ => None + } + + /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. + * This is important on Android because there is otherwise an interface explosion. + */ + def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while(!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if(!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + val ps = c.symbol.info.parents + val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses; + val superInterfaces = superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol)) distinct + + if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY + else mkArray(minimizeInterfaces(superInterfaces) map javaName) + } + + var clasz: IClass = _ // this var must be assigned only by genClass() + var jclass: asm.ClassWriter = _ // the classfile being emitted + var thisName: String = _ // the internal name of jclass + + def thisDescr: String = { + assert(thisName != null, "thisDescr invoked too soon.") + asm.Type.getObjectType(thisName).getDescriptor + } + + def getCurrentCUnit(): CompilationUnit = { clasz.cunit } + + def genClass(c: IClass) { + clasz = c + innerClassBuffer.clear() + + thisName = javaName(c.symbol) // the internal name of the class being emitted + + val ps = c.symbol.info.parents + val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol); + + val ifaces = getSuperInterfaces(c) + + val thisSignature = getGenericSignature(c.symbol, c.symbol.owner) + val flags = mkFlags( + javaFlags(c.symbol), + if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + jclass = createJClass(flags, + thisName, thisSignature, + superClass, ifaces) + + // typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + if(emitSource) { + jclass.visitSource(c.cunit.source.toString, + null /* SourceDebugExtension */) + } + + val enclM = getEnclosingMethodAttribute() + if(enclM != null) { + val EnclMethodEntry(className, methodName, methodType) = enclM + jclass.visitOuterClass(className, methodName, methodType.getDescriptor) + } + + // typestate: entering mode with valid call sequences: + // ( visitAnnotation | visitAttribute )* + + val ssa = getAnnotPickle(thisName, c.symbol) + jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(jclass, c.symbol.annotations ++ ssa) + + // typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + if (isStaticModule(c.symbol) || isParcelableClass) { + + if (isStaticModule(c.symbol)) { addModuleInstanceField() } + addStaticInit(c.lookupStaticCtor) + + } else { + + for (constructor <- c.lookupStaticCtor) { + addStaticInit(Some(constructor)) + } + val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value) + if (!skipStaticForwarders) { + val lmoc = c.symbol.companionModule + // add static forwarders if there are no name conflicts; see bugs #363 and #1735 + if (lmoc != NoSymbol) { + // it must be a top level class (name contains no $s) + val isCandidateForForwarders = { + afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + } + if (isCandidateForForwarders) { + log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc)) + addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass) + } + } + } + + } + + // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` + serialVUID foreach { value => + val fieldName = "serialVersionUID" + jclass.visitField( + PublicStaticFinal, + fieldName, + tdesc_long, + null, // no java-generic-signature + value + ).visitEnd() + } + + clasz.fields foreach genField + clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) } + + addInnerClasses(clasz.symbol, jclass) + jclass.visitEnd() + writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) + + } + + /** + * @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: asm.Type) + + /** + * @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. + * + */ + private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7 + var res: EnclMethodEntry = null + val clazz = clasz.symbol + val sym = clazz.originalEnclosingMethod + if (sym.isMethod) { + debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass)) + res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym)) + } else if (clazz.isAnonymousClass) { + val enclClass = clazz.rawowner + assert(enclClass.isClass, enclClass) + val sym = enclClass.primaryConstructor + if (sym == NoSymbol) { + log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz)) + } else { + debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) + res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) + } + } + + res + } + + def genField(f: IField) { + debuglog("Adding field: " + f.symbol.fullName) + + val javagensig = getGenericSignature(f.symbol, clasz.symbol) + + val flags = mkFlags( + javaFieldFlags(f.symbol), + if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val jfield: asm.FieldVisitor = jclass.visitField( + flags, + javaName(f.symbol), + javaType(f.symbol.tpe).getDescriptor(), + javagensig, + null // no initial value + ) + + emitAnnotations(jfield, f.symbol.annotations) + jfield.visitEnd() + } + + var method: IMethod = _ + var jmethod: asm.MethodVisitor = _ + var jMethodName: String = _ + + @inline final def emit(opc: Int) { jmethod.visitInsn(opc) } + + def genMethod(m: IMethod, isJInterface: Boolean) { + + def isClosureApply(sym: Symbol): Boolean = { + (sym.name == nme.apply) && + sym.owner.isSynthetic && + sym.owner.tpe.parents.exists { t => + val TypeRef(_, sym, _) = t + FunctionClass contains sym + } + } + + if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return + + debuglog("Generating method " + m.symbol.fullName) + method = m + computeLocalVarsIndex(m) + + var resTpe: asm.Type = javaType(m.symbol.tpe.resultType) + if (m.symbol.isClassConstructor) + resTpe = asm.Type.VOID_TYPE + + val flags = mkFlags( + javaFlags(m.symbol), + if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0, + if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, + if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes + if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } + val jgensig = getGenericSignature(m.symbol, clasz.symbol) + addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol) + val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass) + val thrownExceptions: List[String] = getExceptions(excs) + + jMethodName = javaName(m.symbol) + val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*) + jmethod = jclass.visitMethod( + flags, + jMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + // TODO param names: (m.params map (p => javaName(p.sym))) + + // typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + emitAnnotations(jmethod, others) + emitParamAnnotations(jmethod, m.params.map(_.sym.annotations)) + + // typestate: entering mode with valid call sequences: + // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code, + // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and + // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited. + + val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0) + val hasCodeAttribute = (!hasAbstractBitSet && !method.native) + if (hasCodeAttribute) { + + jmethod.visitCode() + + if (emitVars && isClosureApply(method.symbol)) { + // add a fake local for debugging purposes + val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL) + if (outerField != NoSymbol) { + log("Adding fake local to represent outer 'this' for closure " + clasz) + val _this = + new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS), + toTypeKind(outerField.tpe), + false) + m.locals = m.locals ::: List(_this) + computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes + jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) + jmethod.visitFieldInsn(asm.Opcodes.GETFIELD, + javaName(clasz.symbol), // field owner + javaName(outerField), // field name + descriptor(outerField) // field descriptor + ) + assert(_this.kind.isReferenceType, _this.kind) + jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this)) + } + } + + assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals ) + + val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) + genCode(m, emitVars, hasStaticBitSet) + + jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + } + + jmethod.visitEnd() + + } + + def addModuleInstanceField() { + val fv = + jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + strMODULE_INSTANCE_FIELD, + thisDescr, + null, // no java-generic-signature + null // no initial value + ) + + // typestate: entering mode with valid call sequences: + // ( visitAnnotation | visitAttribute )* visitEnd. + + fv.visitEnd() + } + + + /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */ + def addStaticInit(mopt: Option[IMethod]) { + + val clinitMethod: asm.MethodVisitor = jclass.visitMethod( + PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + CLASS_CONSTRUCTOR_NAME, + mdesc_arglessvoid, + null, // no java-generic-signature + null // no throwable exceptions + ) + + mopt match { + + case Some(m) => + + val oldLastBlock = m.lastBlock + val lastBlock = m.newBlock() + oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock)) + + if (isStaticModule(clasz.symbol)) { + // call object's private ctor from static ctor + lastBlock emit NEW(REFERENCE(m.symbol.enclClass)) + lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true)) + } + + if (isParcelableClass) { addCreatorCode(lastBlock) } + + lastBlock emit RETURN(UNIT) + lastBlock.close + + method = m + jmethod = clinitMethod + jMethodName = CLASS_CONSTRUCTOR_NAME + jmethod.visitCode() + genCode(m, false, true) + jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + jmethod.visitEnd() + + case None => + clinitMethod.visitCode() + legacyStaticInitializer(clinitMethod) + clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + clinitMethod.visitEnd() + + } + } + + /* used only from addStaticInit() */ + private def legacyStaticInitializer(clinit: asm.MethodVisitor) { + if (isStaticModule(clasz.symbol)) { + clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, + thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid) + } + + if (isParcelableClass) { legacyAddCreatorCode(clinit) } + + clinit.visitInsn(asm.Opcodes.RETURN) + } + + // ----------------------------------------------------------------------------------------- + // Emitting bytecode instructions. + // ----------------------------------------------------------------------------------------- + + private def genConstant(mv: asm.MethodVisitor, const: Constant) { + const.tag match { + + case BooleanTag => jcode.boolconst(const.booleanValue) + + case ByteTag => jcode.iconst(const.byteValue) + case ShortTag => jcode.iconst(const.shortValue) + case CharTag => jcode.iconst(const.charValue) + case IntTag => jcode.iconst(const.intValue) + + case LongTag => jcode.lconst(const.longValue) + case FloatTag => jcode.fconst(const.floatValue) + case DoubleTag => jcode.dconst(const.doubleValue) + + case UnitTag => () + + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag + + case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL) + + case ClazzTag => + val kind = toTypeKind(const.typeValue) + val toPush: asm.Type = + if (kind.isValueType) classLiteral(kind) + else javaType(kind); + mv.visitLdcInsn(toPush) + + case EnumTag => + val sym = const.symbolValue + mv.visitFieldInsn( + asm.Opcodes.GETSTATIC, + javaName(sym.owner), + javaName(sym), + javaType(sym.tpe.underlying).getDescriptor() + ) + + case _ => abort("Unknown constant value: " + const) + } + } + + /** Just a namespace for utilities that encapsulate MethodVisitor idioms. + * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, + * but the methods here allow choosing when to transition from ICode to ASM types + * (including not at all, e.g. for performance). + */ + object jcode { + + import asm.Opcodes; + + def aconst(cst: AnyRef) { + if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) } + else { jmethod.visitLdcInsn(cst) } + } + + @inline final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } + + def iconst(cst: Int) { + if (cst >= -1 && cst <= 5) { + jmethod.visitInsn(Opcodes.ICONST_0 + cst) + } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.BIPUSH, cst) + } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.SIPUSH, cst) + } else { + jmethod.visitLdcInsn(new Integer(cst)) + } + } + + def lconst(cst: Long) { + if (cst == 0L || cst == 1L) { + jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Long(cst)) + } + } + + def fconst(cst: Float) { + val bits: Int = java.lang.Float.floatToIntBits(cst) + if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 + jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Float(cst)) + } + } + + def dconst(cst: Double) { + val bits: Long = java.lang.Double.doubleToLongBits(cst) + if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d + jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Double(cst)) + } + } + + def newarray(elem: TypeKind) { + if(elem.isRefOrArrayType) { + jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName) + } else { + val rand = { + if(elem.isIntSizedType) { + (elem: @unchecked) match { + case BOOL => Opcodes.T_BOOLEAN + case BYTE => Opcodes.T_BYTE + case SHORT => Opcodes.T_SHORT + case CHAR => Opcodes.T_CHAR + case INT => Opcodes.T_INT + } + } else { + (elem: @unchecked) match { + case LONG => Opcodes.T_LONG + case FLOAT => Opcodes.T_FLOAT + case DOUBLE => Opcodes.T_DOUBLE + } + } + } + jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) + } + } + + + @inline def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) } + @inline def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) } + + @inline def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) } + @inline def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) } + + @inline def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) } + @inline def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) } + @inline def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) } + @inline def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) } + @inline def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) } + @inline def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) } + + @inline def invokespecial(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc) + } + @inline def invokestatic(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc) + } + @inline def invokeinterface(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc) + } + @inline def invokevirtual(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc) + } + + @inline def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } + @inline def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } + @inline def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } + @inline def emitIF_ACMP(cond: TestOp, label: asm.Label) { + assert((cond == EQ) || (cond == NE), cond) + val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) + jmethod.visitJumpInsn(opc, label) + } + @inline def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } + @inline def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } + + @inline def emitRETURN(tk: TypeKind) { + if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) } + else { emitTypeBased(returnOpcodes, tk) } + } + + /** Emits one of tableswitch or lookoupswitch. */ + def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { + assert(keys.length == branches.length) + + // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. + // Similar to what javac emits for a switch statement consisting only of a default case. + if (keys.length == 0) { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + return + } + + // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort + var i = 1 + while (i < keys.length) { + var j = 1 + while (j <= keys.length - i) { + if (keys(j) < keys(j - 1)) { + val tmp = keys(j) + keys(j) = keys(j - 1) + keys(j - 1) = tmp + val tmpL = branches(j) + branches(j) = branches(j - 1) + branches(j - 1) = tmpL + } + j += 1 + } + i += 1 + } + + // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) + i = 1 + while (i < keys.length) { + if(keys(i-1) == keys(i)) { + abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") + } + i += 1 + } + + val keyMin = keys(0) + val keyMax = keys(keys.length - 1) + + val isDenseEnough: Boolean = { + /** Calculate in long to guard against overflow. TODO what overflow??? */ + val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] + val klenD: Double = keys.length + val kdensity: Double = (klenD / keyRangeD) + + kdensity >= minDensity + } + + if (isDenseEnough) { + // use a table in which holes are filled with defaultBranch. + val keyRange = (keyMax - keyMin + 1) + val newBranches = new Array[asm.Label](keyRange) + var oldPos = 0; + var i = 0 + while(i < keyRange) { + val key = keyMin + i; + if (keys(oldPos) == key) { + newBranches(i) = branches(oldPos) + oldPos += 1 + } else { + newBranches(i) = defaultBranch + } + i += 1 + } + assert(oldPos == keys.length, "emitSWITCH") + jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) + } else { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + } + } + + // internal helpers -- not part of the public API of `jcode` + // don't make private otherwise inlining will suffer + + def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) { + assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) + jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx) + } + + // ---------------- array load and store ---------------- + + val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } + val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } + + val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } + + def emitTypeBased(opcs: Array[Int], tk: TypeKind) { + assert(tk != UNIT, tk) + val opc = { + if(tk.isRefOrArrayType) { opcs(0) } + else if(tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL | BYTE => opcs(1) + case SHORT => opcs(2) + case CHAR => opcs(3) + case INT => opcs(4) + } + } else { + (tk: @unchecked) match { + case LONG => opcs(5) + case FLOAT => opcs(6) + case DOUBLE => opcs(7) + } + } + } + jmethod.visitInsn(opc) + } + + // ---------------- primitive operations ---------------- + + val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) } + val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) } + val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) } + val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) } + val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) } + val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) } + + def emitPrimitive(opcs: Array[Int], tk: TypeKind) { + val opc = { + if(tk.isIntSizedType) { opcs(0) } + else { + (tk: @unchecked) match { + case LONG => opcs(1) + case FLOAT => opcs(2) + case DOUBLE => opcs(3) + } + } + } + jmethod.visitInsn(opc) + } + + } + + /** Invoked from genMethod() and addStaticInit() */ + def genCode(m: IMethod, + emitVars: Boolean, // this param name hides the instance-level var + isStatic: Boolean) { + + + newNormal.normalize(m) + + // ------------------------------------------------------------------------------------------------------------ + // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization` + // ------------------------------------------------------------------------------------------------------------ + + val linearization: List[BasicBlock] = linearizer.linearize(m) + if(linearization.isEmpty) { return } + + var isModuleInitialized = false + + val labels: collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*) + + val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted + + // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label. + val linNext: collection.Map[BasicBlock, asm.Label] = { + val result = mutable.HashMap.empty[BasicBlock, asm.Label] + var rest = linearization + var prev = rest.head + rest = rest.tail + while(!rest.isEmpty) { + result += (prev -> labels(rest.head)) + prev = rest.head + rest = rest.tail + } + assert(!result.contains(prev)) + result += (prev -> onePastLast) + + result + } + + // ------------------------------------------------------------------------------------------------------------ + // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock()) + // ------------------------------------------------------------------------------------------------------------ + + /**Generate exception handlers for the current method. + * + * Quoting from the JVMS 4.7.3 The Code Attribute + * The items of the Code_attribute structure are as follows: + * . . . + * exception_table[] + * Each entry in the exception_table array describes one + * exception handler in the code array. The order of the handlers in + * the exception_table array is significant. + * Each exception_table entry contains the following four items: + * start_pc, end_pc: + * ... The value of end_pc either must be a valid index into + * the code array of the opcode of an instruction or must be equal to code_length, + * the length of the code array. + * handler_pc: + * The value of the handler_pc item indicates the start of the exception handler + * catch_type: + * ... If the value of the catch_type item is zero, + * this exception handler is called for all exceptions. + * This is used to implement finally + */ + def genExceptionHandlers() { + + /** Return a list of pairs of intervals where the handler is active. + * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. + * Preconditions: + * - e.covered non-empty + * Postconditions for the result: + * - always non-empty + * - intervals are sorted as per `linearization` + * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, + * ie. between any two intervals in the result there is a non-empty gap. + * - each of the `covered` blocks in the argument is contained in some interval in the result + */ + def intervals(e: ExceptionHandler): List[BlockInteval] = { + assert(e.covered.nonEmpty, e) + var result: List[BlockInteval] = Nil + var rest = linearization + + // find intervals + while(!rest.isEmpty) { + // find interval start + var start: BasicBlock = null + while(!rest.isEmpty && (start eq null)) { + if(e.covered(rest.head)) { start = rest.head } + rest = rest.tail + } + if(start ne null) { + // find interval end + var end = start // for the time being + while(!rest.isEmpty && (e.covered(rest.head))) { + end = rest.head + rest = rest.tail + } + result = BlockInteval(start, end) :: result + } + } + + assert(result.nonEmpty, e) + + result + } + + /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains + * blocks not in the linearization (dead-code?). Is that well-formed or not? + * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does). + */ + for (e <- this.method.exh) { + val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } ) + // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)") + if(ignore.nonEmpty) { + e.covered = e.covered filterNot ignore + } + } + + // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table. + // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class? + for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) { + debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method + + " from: " + p.start + " to: " + p.end + " catching: " + e.cls); + val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null + else javaName(e.cls) + jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls) + } + } // end of genCode()'s genExceptionHandlers() + + if (m.exh.nonEmpty) { genExceptionHandlers() } + + // ------------------------------------------------------------------------------------------------------------ + // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute). + // ------------------------------------------------------------------------------------------------------------ + + case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive. + + case class Interval(lstart: asm.Label, lend: asm.Label) { + @inline final def start = lstart.getOffset + @inline final def end = lend.getOffset + + def precedes(that: Interval): Boolean = { this.end < that.start } + + def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) } + + def mergeWith(that: Interval): Interval = { + val newStart = if(this.start <= that.start) this.lstart else that.lstart; + val newEnd = if(this.end <= that.end) that.lend else this.lend; + Interval(newStart, newEnd) + } + + def repOK: Boolean = { start <= end } + + } + + /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */ + object scoping { + + private val pending = mutable.Map.empty[Local, mutable.Stack[Label]] + private var seen: List[LocVarEntry] = Nil + + private def fuse(ranges: List[Interval], added: Interval): List[Interval] = { + assert(added.repOK, added) + if(ranges.isEmpty) { return List(added) } + // precond: ranges is sorted by increasing start + var fused: List[Interval] = Nil + var done = false + var rest = ranges + while(!done && rest.nonEmpty) { + val current = rest.head + assert(current.repOK, current) + rest = rest.tail + if(added precedes current) { + fused = fused ::: ( added :: current :: rest ) + done = true + } else if(current overlaps added) { + fused = fused ::: ( added.mergeWith(current) :: rest ) + done = true + } + } + if(!done) { fused = fused ::: List(added) } + assert(repOK(fused), fused) + + fused + } + + def pushScope(lv: Local, start: Label) { + val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label]) + st.push(start) + } + def popScope(lv: Local, end: Label, iPos: Position) { + pending.get(lv) match { + case Some(st) if st.nonEmpty => + val start = st.pop() + seen ::= LocVarEntry(lv, start, end) + case _ => + // TODO SI-6049 + getCurrentCUnit().warning(iPos, "Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6049") + } + } + + def getMerged(): collection.Map[Local, List[Interval]] = { + // TODO should but isn't: unbalanced start(s) of scope(s) + val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty }; + + val merged = mutable.Map.empty[Local, List[Interval]] + + def addToMerged(lv: Local, start: Label, end: Label) { + val ranges = merged.getOrElseUpdate(lv, Nil) + val coalesced = fuse(ranges, Interval(start, end)) + merged.update(lv, coalesced) + } + + for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) } + + /* for each var with unbalanced start(s) of scope(s): + (a) take the earliest start (among unbalanced and balanced starts) + (b) take the latest end (onePastLast if none available) + (c) merge the thus made-up interval + */ + for(Pair(k, st) <- shouldBeEmpty) { + var start = st.toList.sortBy(_.getOffset).head + if(merged.isDefinedAt(k)) { + val balancedStart = merged(k).head.lstart + if(balancedStart.getOffset < start.getOffset) { + start = balancedStart; + } + } + val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend; + val end = endOpt.getOrElse(onePastLast) + addToMerged(k, start, end) + } + + merged + } + + private def repOK(fused: List[Interval]): Boolean = { + fused match { + case Nil => true + case h :: Nil => h.repOK + case h :: n :: rest => + h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest) + } + } + + } + + def genLocalVariableTable() { + // adding `this` and method params. + if (!isStatic) { + jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0) + } + for(lv <- m.params) { + jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv)) + } + // adding non-param locals + var anonCounter = 0 + var fltnd: List[Triple[String, Local, Interval]] = Nil + for(Pair(local, ranges) <- scoping.getMerged()) { + var name = javaName(local.sym) + if (name == null) { + anonCounter += 1; + name = "<anon" + anonCounter + ">" + } + for(intrvl <- ranges) { + fltnd ::= Triple(name, local, intrvl) + } + } + // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain). + val srtd = fltnd.sortBy { kr => + val Triple(name: String, local: Local, intrvl: Interval) = kr + + Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) + } + + for(Triple(name, local, Interval(start, end)) <- srtd) { + jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local)) + } + // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute" + } + + // ------------------------------------------------------------------------------------------------------------ + // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position. + // ------------------------------------------------------------------------------------------------------------ + + case class LineNumberEntry(line: Int, start: asm.Label) + var lastLineNr: Int = -1 + var lnEntries: List[LineNumberEntry] = Nil + + // ------------------------------------------------------------------------------------------------------------ + // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()). + // ------------------------------------------------------------------------------------------------------------ + + var nextBlock: BasicBlock = linearization.head + + def genBlocks(l: List[BasicBlock]): Unit = l match { + case Nil => () + case x :: Nil => nextBlock = null; genBlock(x) + case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys) + } + + def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { + target.isPublic || target.isProtected && { + (site.enclClass isSubClass target.enclClass) || + (site.enclosingPackage == target.privateWithin) + } + } // end of genCode()'s isAccessibleFrom() + + def genCallMethod(call: CALL_METHOD) { + val CALL_METHOD(method, style) = call + val siteSymbol = clasz.symbol + val hostSymbol = call.hostClass + val methodOwner = method.owner + // info calls so that types are up to date; erasure may add lateINTERFACE to traits + hostSymbol.info ; methodOwner.info + + def isInterfaceCall(sym: Symbol) = ( + sym.isInterface && methodOwner != ObjectClass + || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass) + ) + // whether to reference the type of the receiver or + // the type of the method owner (if not an interface!) + val useMethodOwner = ( + style != Dynamic + || !isInterfaceCall(hostSymbol) && isAccessibleFrom(methodOwner, siteSymbol) + || hostSymbol.isBottomClass + ) + val receiver = if (useMethodOwner) methodOwner else hostSymbol + val jowner = javaName(receiver) + val jname = javaName(method) + val jtype = javaType(method).getDescriptor() + + def dbg(invoke: String) { + debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype)) + } + + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (isStaticModule(siteSymbol) && !isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + jname == INSTANCE_CONSTRUCTOR_NAME) { + isModuleInitialized = true + jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) + jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr) + } + } + + style match { + case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype) + case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype) + case Dynamic if isInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype) + case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype) + case SuperCall(_) => + dbg("invokespecial") + jcode.invokespecial(jowner, jname, jtype) + initModule() + } + } // end of genCode()'s genCallMethod() + + def genBlock(b: BasicBlock) { + jmethod.visitLabel(labels(b)) + + import asm.Opcodes; + + debuglog("Generating code for block: " + b) + + // val lastInstr = b.lastInstruction + + for (instr <- b) { + + if(instr.pos.isDefined) { + val iPos = instr.pos + val currentLineNr = iPos.line + val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else + if(!skip) { + lastLineNr = currentLineNr + val lineLab = new asm.Label + jmethod.visitLabel(lineLab) + lnEntries ::= LineNumberEntry(currentLineNr, lineLab) + } + } + + (instr.category: @scala.annotation.switch) match { + + case icodes.localsCat => (instr: @unchecked) match { + case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0) + case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind) + case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind) + case STORE_THIS(_) => + // this only works for impl classes because the self parameter comes first + // in the method signature. If that changes, this code has to be revisited. + jmethod.visitVarInsn(Opcodes.ASTORE, 0) + + case SCOPE_ENTER(lv) => + // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored + val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) + if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars? + // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) + // similarly, these labels aren't tracked in the `labels` map. + val start = new asm.Label + jmethod.visitLabel(start) + scoping.pushScope(lv, start) + } + + case SCOPE_EXIT(lv) => + val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) + if(relevant) { + // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) + // similarly, these labels aren't tracked in the `labels` map. + val end = new asm.Label + jmethod.visitLabel(end) + scoping.popScope(lv, end, instr.pos) + } + } + + case icodes.stackCat => (instr: @unchecked) match { + + case LOAD_MODULE(module) => + // assert(module.isModule, "Expected module: " + module) + debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags)); + if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) { + jmethod.visitVarInsn(Opcodes.ALOAD, 0) + } else { + jmethod.visitFieldInsn( + Opcodes.GETSTATIC, + javaName(module) /* + "$" */ , + strMODULE_INSTANCE_FIELD, + descriptor(module) + ) + } + + case DROP(kind) => emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP) + + case DUP(kind) => emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP) + + case LOAD_EXCEPTION(_) => () + } + + case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) + + case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos) + + case icodes.castsCat => (instr: @unchecked) match { + + case IS_INSTANCE(tpe) => + val jtyp: asm.Type = + tpe match { + case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) + case ARRAY(elem) => javaArrayType(javaType(elem)) + case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) + } + jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName) + + case CHECK_CAST(tpe) => + tpe match { + + case REFERENCE(cls) => + if (cls != ObjectClass) { // No need to checkcast for Objects + jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls)) + } + + case ARRAY(elem) => + val iname = javaArrayType(javaType(elem)).getInternalName + jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname) + + case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) + } + + } + + case icodes.objsCat => (instr: @unchecked) match { + + case BOX(kind) => + val MethodNameAndType(mname, mdesc) = jBoxTo(kind) + jcode.invokestatic(BoxesRunTime, mname, mdesc) + + case UNBOX(kind) => + val MethodNameAndType(mname, mdesc) = jUnboxTo(kind) + jcode.invokestatic(BoxesRunTime, mname, mdesc) + + case NEW(REFERENCE(cls)) => + val className = javaName(cls) + jmethod.visitTypeInsn(Opcodes.NEW, className) + + case MONITOR_ENTER() => emit(Opcodes.MONITORENTER) + case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT) + } + + case icodes.fldsCat => (instr: @unchecked) match { + + case lf @ LOAD_FIELD(field, isStatic) => + var owner = javaName(lf.hostClass) + debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags)) + val fieldJName = javaName(field) + val fieldDescr = descriptor(field) + val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD + jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + case STORE_FIELD(field, isStatic) => + val owner = javaName(field.owner) + val fieldJName = javaName(field) + val fieldDescr = descriptor(field) + val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD + jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + } + + case icodes.mthdsCat => (instr: @unchecked) match { + + /** Special handling to access native Array.clone() */ + case call @ CALL_METHOD(definitions.Array_clone, Dynamic) => + val target: String = javaType(call.targetTypeKind).getInternalName + jcode.invokevirtual(target, "clone", mdesc_arrayClone) + + case call @ CALL_METHOD(method, style) => genCallMethod(call) + + } + + case icodes.arraysCat => (instr: @unchecked) match { + case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind) + case STORE_ARRAY_ITEM(kind) => jcode.astore(kind) + case CREATE_ARRAY(elem, 1) => jcode newarray elem + case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims) + } + + case icodes.jumpsCat => (instr: @unchecked) match { + + case sw @ SWITCH(tagss, branches) => + assert(branches.length == tagss.length + 1, sw) + val flatSize = sw.flatTagsCount + val flatKeys = new Array[Int](flatSize) + val flatBranches = new Array[asm.Label](flatSize) + + var restTagss = tagss + var restBranches = branches + var k = 0 // ranges over flatKeys and flatBranches + while(restTagss.nonEmpty) { + val currLabel = labels(restBranches.head) + for(cTag <- restTagss.head) { + flatKeys(k) = cTag; + flatBranches(k) = currLabel + k += 1 + } + restTagss = restTagss.tail + restBranches = restBranches.tail + } + val defaultLabel = labels(restBranches.head) + assert(restBranches.tail.isEmpty) + debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches) + jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY) + + case JUMP(whereto) => + if (nextBlock != whereto) { + jcode goTo labels(whereto) + } + + case CJUMP(success, failure, cond, kind) => + if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + if (nextBlock == success) { + jcode.emitIF_ICMP(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF_ICMP(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + if (nextBlock == success) { + jcode.emitIF_ACMP(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF_ACMP(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else { + (kind: @unchecked) match { + case LONG => emit(Opcodes.LCMP) + case FLOAT => + if (cond == LT || cond == LE) emit(Opcodes.FCMPG) + else emit(Opcodes.FCMPL) + case DOUBLE => + if (cond == LT || cond == LE) emit(Opcodes.DCMPG) + else emit(Opcodes.DCMPL) + } + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + // .. and fall through to success label + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } + + case CZJUMP(success, failure, cond, kind) => + if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + val Success = success + val Failure = failure + // @unchecked because references aren't compared with GT, GE, LT, LE. + ((cond, nextBlock) : @unchecked) match { + case (EQ, Success) => jcode emitIFNONNULL labels(failure) + case (NE, Failure) => jcode emitIFNONNULL labels(success) + case (EQ, Failure) => jcode emitIFNULL labels(success) + case (NE, Success) => jcode emitIFNULL labels(failure) + case (EQ, _) => + jcode emitIFNULL labels(success) + jcode goTo labels(failure) + case (NE, _) => + jcode emitIFNONNULL labels(success) + jcode goTo labels(failure) + } + } else { + (kind: @unchecked) match { + case LONG => + emit(Opcodes.LCONST_0) + emit(Opcodes.LCMP) + case FLOAT => + emit(Opcodes.FCONST_0) + if (cond == LT || cond == LE) emit(Opcodes.FCMPG) + else emit(Opcodes.FCMPL) + case DOUBLE => + emit(Opcodes.DCONST_0) + if (cond == LT || cond == LE) emit(Opcodes.DCMPG) + else emit(Opcodes.DCMPL) + } + if (nextBlock == success) { + jcode.emitIF(cond.negate, labels(failure)) + } else { + jcode.emitIF(cond, labels(success)) + if (nextBlock != failure) { jcode goTo labels(failure) } + } + } + + } + + case icodes.retCat => (instr: @unchecked) match { + case RETURN(kind) => jcode emitRETURN kind + case THROW(_) => emit(Opcodes.ATHROW) + } + + } + + } + + } // end of genCode()'s genBlock() + + /** + * Emits one or more conversion instructions based on the types given as arguments. + * + * @param from The type of the value to be converted into another type. + * @param to The type the value will be converted into. + */ + def emitT2T(from: TypeKind, to: TypeKind) { + assert(isNonUnitValueTK(from), from) + assert(isNonUnitValueTK(to), to) + + def pickOne(opcs: Array[Int]) { + val chosen = (to: @unchecked) match { + case BYTE => opcs(0) + case SHORT => opcs(1) + case CHAR => opcs(2) + case INT => opcs(3) + case LONG => opcs(4) + case FLOAT => opcs(5) + case DOUBLE => opcs(6) + } + if(chosen != -1) { emit(chosen) } + } + + if(from == to) { return } + if((from == BOOL) || (to == BOOL)) { + // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) + throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString()) + } + + if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already) + + val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) + val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing + val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing + val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } + + (from: @unchecked) match { + case BYTE => pickOne(fromByte) + case SHORT => pickOne(fromShort) + case CHAR => pickOne(fromChar) + case INT => pickOne(fromInt) + } + + } else { // FLOAT, LONG, DOUBLE + + (from: @unchecked) match { + case FLOAT => + import asm.Opcodes.{ F2L, F2D, F2I } + (to: @unchecked) match { + case LONG => emit(F2L) + case DOUBLE => emit(F2D) + case _ => emit(F2I); emitT2T(INT, to) + } + + case LONG => + import asm.Opcodes.{ L2F, L2D, L2I } + (to: @unchecked) match { + case FLOAT => emit(L2F) + case DOUBLE => emit(L2D) + case _ => emit(L2I); emitT2T(INT, to) + } + + case DOUBLE => + import asm.Opcodes.{ D2L, D2F, D2I } + (to: @unchecked) match { + case FLOAT => emit(D2F) + case LONG => emit(D2L) + case _ => emit(D2I); emitT2T(INT, to) + } + } + } + } // end of genCode()'s emitT2T() + + def genPrimitive(primitive: Primitive, pos: Position) { + + import asm.Opcodes; + + primitive match { + + case Negation(kind) => jcode.neg(kind) + + case Arithmetic(op, kind) => + op match { + + case ADD => jcode.add(kind) + case SUB => jcode.sub(kind) + case MUL => jcode.mul(kind) + case DIV => jcode.div(kind) + case REM => jcode.rem(kind) + + case NOT => + if(kind.isIntSizedType) { + emit(Opcodes.ICONST_M1) + emit(Opcodes.IXOR) + } else if(kind == LONG) { + jmethod.visitLdcInsn(new java.lang.Long(-1)) + jmethod.visitInsn(Opcodes.LXOR) + } else { + abort("Impossible to negate an " + kind) + } + + case _ => + abort("Unknown arithmetic primitive " + primitive) + } + + // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey) + // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead. + // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed). + case Logical(op, kind) => ((op, kind): @unchecked) match { + case (AND, LONG) => emit(Opcodes.LAND) + case (AND, INT) => emit(Opcodes.IAND) + case (AND, _) => + emit(Opcodes.IAND) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (OR, LONG) => emit(Opcodes.LOR) + case (OR, INT) => emit(Opcodes.IOR) + case (OR, _) => + emit(Opcodes.IOR) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (XOR, LONG) => emit(Opcodes.LXOR) + case (XOR, INT) => emit(Opcodes.IXOR) + case (XOR, _) => + emit(Opcodes.IXOR) + if (kind != BOOL) { emitT2T(INT, kind) } + } + + case Shift(op, kind) => ((op, kind): @unchecked) match { + case (LSL, LONG) => emit(Opcodes.LSHL) + case (LSL, INT) => emit(Opcodes.ISHL) + case (LSL, _) => + emit(Opcodes.ISHL) + emitT2T(INT, kind) + + case (ASR, LONG) => emit(Opcodes.LSHR) + case (ASR, INT) => emit(Opcodes.ISHR) + case (ASR, _) => + emit(Opcodes.ISHR) + emitT2T(INT, kind) + + case (LSR, LONG) => emit(Opcodes.LUSHR) + case (LSR, INT) => emit(Opcodes.IUSHR) + case (LSR, _) => + emit(Opcodes.IUSHR) + emitT2T(INT, kind) + } + + case Comparison(op, kind) => ((op, kind): @unchecked) match { + case (CMP, LONG) => emit(Opcodes.LCMP) + case (CMPL, FLOAT) => emit(Opcodes.FCMPL) + case (CMPG, FLOAT) => emit(Opcodes.FCMPG) + case (CMPL, DOUBLE) => emit(Opcodes.DCMPL) + case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html + } + + case Conversion(src, dst) => + debuglog("Converting from: " + src + " to: " + dst) + if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) } + else { emitT2T(src, dst) } + + case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH) + + case StartConcat => + jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) + jmethod.visitInsn(Opcodes.DUP) + jcode.invokespecial( + StringBuilderClassName, + INSTANCE_CONSTRUCTOR_NAME, + mdesc_arglessvoid + ) + + case StringConcat(el) => + val jtype = el match { + case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT + case _ => javaType(el) + } + jcode.invokevirtual( + StringBuilderClassName, + "append", + asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*) + ) + + case EndConcat => + jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString) + + case _ => abort("Unimplemented primitive " + primitive) + } + } // end of genCode()'s genPrimitive() + + // ------------------------------------------------------------------------------------------------------------ + // Part 6 of genCode(): the executable part of genCode() starts here. + // ------------------------------------------------------------------------------------------------------------ + + genBlocks(linearization) + + jmethod.visitLabel(onePastLast) + + if(emitLines) { + for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) } + } + if(emitVars) { genLocalVariableTable() } + + } // end of BytecodeGenerator.genCode() + + + ////////////////////// local vars /////////////////////// + + // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe)) + + def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1 + + // def indexOf(m: IMethod, sym: Symbol): Int = { + // val Some(local) = m lookupLocal sym + // indexOf(local) + // } + + @inline final def indexOf(local: Local): Int = { + assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ") + local.index + } + + /** + * Compute the indexes of each local variable of the given method. + * *Does not assume the parameters come first!* + */ + def computeLocalVarsIndex(m: IMethod) { + var idx = if (m.symbol.isStaticMember) 0 else 1; + + for (l <- m.params) { + debuglog("Index value for " + l + "{" + l.## + "}: " + idx) + l.index = idx + idx += sizeOf(l.kind) + } + + for (l <- m.locals if !l.arg) { + debuglog("Index value for " + l + "{" + l.## + "}: " + idx) + l.index = idx + idx += sizeOf(l.kind) + } + } + + } // end of class JPlainBuilder + + + /** builder of mirror classes */ + class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { + + 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. + */ + def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) { + assert(modsym.companionClass == NoSymbol, modsym) + innerClassBuffer.clear() + this.cunit = cunit + val moduleName = javaName(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 = createJClass(flags, + mirrorName, + null /* no java-generic-signature */, + JAVA_LANG_OBJECT.getInternalName, + EMPTY_STRING_ARRAY) + + log("Dumping mirror class for '%s'".format(mirrorName)) + + // typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + 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) + + // typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + + addInnerClasses(modsym, mirrorClass) + mirrorClass.visitEnd() + writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) + } + + + } // end of class JMirrorBuilder + + + /** builder of bean info classes */ + class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + + /** + * Generate a bean info class that describes the given class. + * + * @author Ross Judson (ross.judson@soletta.com) + */ + def genBeanInfoClass(clasz: IClass) { + + // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip") + // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName") + // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription") + // val description = c.symbol getAnnotation BeanDescriptionAttr + // informProgress(description.toString) + innerClassBuffer.clear() + + val flags = mkFlags( + javaFlags(clasz.symbol), + if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val beanInfoName = (javaName(clasz.symbol) + "BeanInfo") + val beanInfoClass = createJClass( + flags, + beanInfoName, + null, // no java-generic-signature + "scala/beans/ScalaBeanInfo", + EMPTY_STRING_ARRAY + ) + + // beanInfoClass typestate: entering mode with valid call sequences: + // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* + + beanInfoClass.visitSource( + clasz.cunit.source.toString, + null /* SourceDebugExtension */ + ) + + var fieldList = List[String]() + + for (f <- clasz.fields if f.symbol.hasGetter; + g = f.symbol.getter(clasz.symbol); + s = f.symbol.setter(clasz.symbol); + if g.isPublic && !(f.symbol.name startsWith "$") + ) { + // inserting $outer breaks the bean + fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList + } + + val methodList: List[String] = + for (m <- clasz.methods + if !m.symbol.isConstructor && + m.symbol.isPublic && + !(m.symbol.name startsWith "$") && + !m.symbol.isGetter && + !m.symbol.isSetter) + yield javaName(m.symbol) + + // beanInfoClass typestate: entering mode with valid call sequences: + // ( visitInnerClass | visitField | visitMethod )* visitEnd + + val constructor = beanInfoClass.visitMethod( + asm.Opcodes.ACC_PUBLIC, + INSTANCE_CONSTRUCTOR_NAME, + mdesc_arglessvoid, + null, // no java-generic-signature + EMPTY_STRING_ARRAY // no throwable exceptions + ) + + // constructor typestate: entering mode with valid call sequences: + // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* + + val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING) + val conJType: asm.Type = + asm.Type.getMethodType( + asm.Type.VOID_TYPE, + Array(javaType(ClassClass), 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 typestate: entering mode with valid call sequences: + // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd + + constructor.visitCode() + + constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) + // push the class + constructor.visitLdcInsn(javaType(clasz.symbol)) + + // 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() + + addInnerClasses(clasz.symbol, beanInfoClass) + beanInfoClass.visitEnd() + + writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) + } + + } // end of class JBeanInfoBuilder + + /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for. + * In particualr, IMethod.normalize() doesn't collapseJumpChains(). + * + * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). + */ + object newNormal { + + def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] } + + /** Prune from an exception handler those covered blocks which are jump-only. */ + private def coverWhatCountsOnly(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + var wasReduced = false + for(h <- m.exh) { + val shouldntCover = (h.covered filter startsWithJump) + if(shouldntCover.nonEmpty) { + wasReduced = true + h.covered --= shouldntCover // not removing any block on purpose. + } + } + + wasReduced + } + + /** An exception handler is pruned provided any of the following holds: + * (1) it covers nothing (for example, this may result after removing unreachable blocks) + * (2) each block it covers is of the form: JUMP(_) + * Return true iff one or more ExceptionHandlers were removed. + * + * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable) + * won't be able to cause a class-loading-exception. As a result, behavior can be different. + */ + private def elimNonCoveringExh(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + def isRedundant(eh: ExceptionHandler): Boolean = { + (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol` + eh.covered.isEmpty + || (eh.covered forall startsWithJump) + ) + } + + var wasReduced = false + val toPrune = (m.exh.toSet filter isRedundant) + if(toPrune.nonEmpty) { + wasReduced = true + for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h) + m.exh = (m.exh filterNot toPrune) + } + + wasReduced + } + + private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = { + b.toList match { + case JUMP(whereto) :: rest => + assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") + Some(whereto) + case _ => None + } + } + + private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) } + + /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow. + Those BBs in the argument are also included in the result */ + private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = { + val result = new mutable.ListBuffer[BasicBlock] + var toVisit: List[BasicBlock] = starters.toList.distinct + while(toVisit.nonEmpty) { + val h = toVisit.head + toVisit = toVisit.tail + result += h + for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit } + } + result.toList + } + + /** Returns: + * for single-block self-loops, the pair (start, Nil) + * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target) + * otherwise a pair consisting of: + * (a) the endpoint of a (single or multi-hop) chain of JUMPs + * (such endpoint does not start with a JUMP and therefore is not part of the chain); and + * (b) the chain (ie blocks to be removed when collapsing the chain of jumps). + * Precondition: the BasicBlock given as argument starts with an unconditional JUMP. + */ + private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = { + assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.") + var hops: List[BasicBlock] = Nil + var prev = start + var done = false + do { + done = isJumpOnly(prev) match { + case Some(dest) => + if (dest == start) { return (start, hops) } // leave infinite-loops in place + hops ::= prev + if (hops.contains(dest)) { + // leave infinite-loops in place + return (dest, hops filterNot (dest eq)) + } + prev = dest; + false + case None => true + } + } while(!done) + + (prev, hops) + } + + /** + * Collapse a chain of "jump-only" blocks such as: + * + * JUMP b1; + * b1: JUMP b2; + * b2: JUMP ... etc. + * + * by re-wiring predecessors to target directly the "final destination". + * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed. + + * Returns true if any replacement was made, false otherwise. + * + * In more detail: + * Starting at each of the entry points (m.startBlock, the start block of each exception handler) + * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D. + * The blocks thus skipped are also removed from IMethod.blocks. + * + * Rationale for this normalization: + * test/files/run/private-inline.scala after -optimize is chock full of + * BasicBlocks containing just JUMP(whereTo), where no exception handler straddles them. + * They should be collapsed by IMethod.normalize() but aren't. + * That was fine in FJBG times when by the time the exception table was emitted, + * it already contained "anchored" labels (ie instruction offsets were known) + * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question) + * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range" + * Now that visitTryCatchBlock() must be called before Labels are resolved, + * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP). + */ + private def collapseJumpOnlyBlocks(m: IMethod): Boolean = { + assert(m.hasCode, "code-less method") + + /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */ + def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = { + assert(startsWithJump(jumpStart), "not part of a jump-chain") + val Pair(dest, redundants) = finalDestination(jumpStart) + (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap + } + + def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) { + for(Pair(oldTarget, newTarget) <- detour.iterator) { + if(m.startBlock == oldTarget) { + m.code.startBlock = newTarget + } + for(eh <- m.exh; if eh.startBlock == oldTarget) { + eh.setStartBlock(newTarget) + } + for(b <- m.blocks; if !detour.isDefinedAt(b)) { + val idxLast = (b.size - 1) + b.lastInstruction match { + case JUMP(whereto) => + if (whereto == oldTarget) { + b.replaceInstruction(idxLast, JUMP(newTarget)) + } + case CJUMP(succ, fail, cond, kind) => + if ((succ == oldTarget) || (fail == oldTarget)) { + b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ), + detour.getOrElse(fail, fail), + cond, kind)) + } + case CZJUMP(succ, fail, cond, kind) => + if ((succ == oldTarget) || (fail == oldTarget)) { + b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ), + detour.getOrElse(fail, fail), + cond, kind)) + } + case SWITCH(tags, labels) => + if(labels exists (detour.isDefinedAt(_))) { + val newLabels = (labels map { lab => detour.getOrElse(lab, lab) }) + b.replaceInstruction(idxLast, SWITCH(tags, newLabels)) + } + case _ => () + } + } + } + } + + /* remove from all containers that may contain a reference to */ + def elide(redu: BasicBlock) { + assert(m.startBlock != redu, "startBlock should have been re-wired by now") + m.code.removeBlock(redu); + } + + var wasReduced = false + val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock)); + + var elided = mutable.Set.empty[BasicBlock] // debug + var newTargets = mutable.Set.empty[BasicBlock] // debug + + for (ep <- entryPoints) { + var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over + while(reachable.nonEmpty) { + val h = reachable.head + reachable = reachable.tail + if(startsWithJump(h)) { + val detour = realTarget(h) + if(detour.nonEmpty) { + wasReduced = true + reachable = (reachable filterNot (detour.keySet.contains(_))) + rephraseGotos(detour) + detour.keySet foreach elide + elided ++= detour.keySet + newTargets ++= detour.values + } + } + } + } + assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain") + + wasReduced + } + + def normalize(m: IMethod) { + if(!m.hasCode) { return } + collapseJumpOnlyBlocks(m) + var wasReduced = false; + do { + wasReduced = false + // Prune from an exception handler those covered blocks which are jump-only. + wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place + // Prune exception handlers covering nothing. + wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m) + + // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?) + } while (wasReduced) + + // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards. + } + + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 72985d58af..763a567828 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -727,7 +727,8 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with // without it. This is particularly bad because the availability of // generic information could disappear as a consequence of a seemingly // unrelated change. - sym.isHidden + settings.Ynogenericsig.value + || sym.isHidden || sym.isLiftedMethod || sym.isBridge || (sym.ownerChain exists (_.isImplClass)) diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index fad41ae98d..bee5aa5f4f 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -56,6 +56,31 @@ abstract class LambdaLift extends InfoTransform { /** The set of symbols that need to be renamed. */ private val renamable = newSymSet + /** + * The new names for free variables proxies. If we simply renamed the + * free variables, we would transform: + * {{{ + * def closure(x: Int) = { () => x } + * }}} + * + * To: + * {{{ + * def closure(x$1: Int) = new anonFun$1(this, x$1) + * class anonFun$1(outer$: Outer, x$1: Int) { def apply() => x$1 } + * }}} + * + * This is fatally bad for named arguments (0e170e4b), extremely impolite to tools + * reflecting on the method parameter names in the generated bytecode (SI-6028), + * and needlessly bothersome to anyone using a debugger. + * + * Instead, we transform to: + * {{{ + * def closure(x: Int) = new anonFun$1(this, x) + * class anonFun$1(outer$: Outer, x$1: Int) { def apply() => x$1 } + * }}} + */ + private val proxyNames = mutable.HashMap[Symbol, Name]() + // (trait, name) -> owner private val localTraits = mutable.HashMap[(Symbol, Name), Symbol]() // (owner, name) -> implClass @@ -117,15 +142,6 @@ abstract class LambdaLift extends InfoTransform { if (!ss(sym)) { ss addEntry sym renamable addEntry sym - beforePickler { - // The param symbol in the MethodType should not be renamed, only the symbol in scope. This way, - // parameter names for named arguments are not changed. Example: without cloning the MethodType, - // def closure(x: Int) = { () => x } - // would have the signature - // closure: (x$1: Int)() => Int - if (sym.isParameter && sym.owner.info.paramss.exists(_ contains sym)) - sym.owner modifyInfo (_ cloneInfo sym.owner) - } changedFreeVars = true debuglog("" + sym + " is free in " + enclosure); if (sym.isVariable) sym setFlag CAPTURED @@ -215,24 +231,26 @@ abstract class LambdaLift extends InfoTransform { def renameSym(sym: Symbol) { val originalName = sym.name + sym setName newName(sym) + debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) + } + + def newName(sym: Symbol): Name = { + val originalName = sym.name def freshen(prefix: String): Name = if (originalName.isTypeName) unit.freshTypeName(prefix) else unit.freshTermName(prefix) - val newName: Name = ( - if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) - } else { - // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) - // Generating a a unique name, mangled with the enclosing class name, avoids a VerifyError - // in the case that a sub-class happens to lifts out a method with the *same* name. - val name = freshen(sym.name + nme.NAME_JOIN_STRING) - if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name, sym.enclClass) - else name - } - ) - sym setName newName - debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) + if (sym.isAnonymousFunction && sym.owner.isMethod) { + freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) + } else { + // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) + // Generating a a unique name, mangled with the enclosing class name, avoids a VerifyError + // in the case that a sub-class happens to lifts out a method with the *same* name. + val name = freshen(sym.name + nme.NAME_JOIN_STRING) + if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name, sym.enclClass) + else name + } } /** Rename a trait's interface and implementation class in coordinated fashion. @@ -245,6 +263,8 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming impl class in step with %s: %s => %s".format(traitSym, originalImplName, implSym.name)) } + val allFree: Set[Symbol] = free.values.flatMap(_.iterator).toSet + for (sym <- renamable) { // If we renamed a trait from Foo to Foo$1, we must rename the implementation // class from Foo$class to Foo$1$class. (Without special consideration it would @@ -252,7 +272,9 @@ abstract class LambdaLift extends InfoTransform { // under us, and there's no reliable link between trait symbol and impl symbol, // we have maps from ((trait, name)) -> owner and ((owner, name)) -> impl. localTraits remove ((sym, sym.name)) match { - case None => renameSym(sym) + case None => + if (allFree(sym)) proxyNames(sym) = newName(sym) + else renameSym(sym) case Some(owner) => localImplClasses remove ((owner, sym.name)) match { case Some(implSym) => renameTrait(sym, implSym) @@ -267,7 +289,8 @@ abstract class LambdaLift extends InfoTransform { debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", "))) proxies(owner) = for (fv <- freeValues.toList) yield { - val proxy = owner.newValue(fv.name, owner.pos, newFlags) setInfo fv.info + val proxyName = proxyNames.getOrElse(fv, fv.name) + val proxy = owner.newValue(proxyName, owner.pos, newFlags) setInfo fv.info if (owner.isClass) owner.info.decls enter proxy proxy } @@ -280,9 +303,9 @@ abstract class LambdaLift extends InfoTransform { if (enclosure eq NoSymbol) throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )") debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + enclosure.logicallyEnclosingMember) - val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten filter (_.name == sym.name) - if (ps.isEmpty) searchIn(enclosure.skipConstructor.owner) - else ps.head + val proxyName = proxyNames.getOrElse(sym, sym.name) + val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten find (_.name == proxyName) + ps getOrElse searchIn(enclosure.skipConstructor.owner) } debuglog("proxy %s from %s has logical enclosure %s".format( sym.debugLocationString, @@ -501,8 +524,10 @@ abstract class LambdaLift extends InfoTransform { } override def transformUnit(unit: CompilationUnit) { - computeFreeVars - afterOwnPhase(super.transformUnit(unit)) + computeFreeVars() + afterOwnPhase { + super.transformUnit(unit) + } assert(liftedDefs.isEmpty, liftedDefs.keys mkString ", ") } } // class LambdaLifter diff --git a/src/compiler/scala/tools/nsc/transform/PostErasure.scala b/src/compiler/scala/tools/nsc/transform/PostErasure.scala index 999d00520d..151bc66a79 100644 --- a/src/compiler/scala/tools/nsc/transform/PostErasure.scala +++ b/src/compiler/scala/tools/nsc/transform/PostErasure.scala @@ -21,6 +21,8 @@ trait PostErasure extends InfoTransform with TypingTransformers { object elimErasedValueType extends TypeMap { def apply(tp: Type) = tp match { + case ConstantType(Constant(tp: Type)) => + ConstantType(Constant(apply(tp))) case ErasedValueType(tref) => atPhase(currentRun.erasurePhase)(erasure.erasedValueClassArg(tref)) case _ => mapOver(tp) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ff38227294..0e1a341da7 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -564,12 +564,6 @@ abstract class UnCurry extends InfoTransform } val sym = tree.symbol - // Take a pass looking for @specialize annotations and set all - // their SPECIALIZE flags for cheaper recognition. - if ((sym ne null) && (sym.isClass || sym.isMethod)) { - for (tp <- sym.typeParams ; if tp hasAnnotation SpecializedClass) - tp setFlag SPECIALIZED - } val result = ( // TODO - settings.noassertions.value temporarily retained to avoid // breakage until a reasonable interface is settled upon. diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 322b9ebb25..b7043e58de 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -26,7 +26,7 @@ import java.lang.reflect.{Array => jArray, Method => jMethod} * def fooBar[T: c.TypeTag] * (c: scala.reflect.makro.Context) * (xs: c.Expr[List[T]]) - * : c.Tree = { + * : c.Expr[T] = { * ... * } * @@ -601,7 +601,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { if (settings.XmacroPrimaryClasspath.value != "") { macroLogVerbose("primary macro classloader: initializing from -Xmacro-primary-classpath: %s".format(settings.XmacroPrimaryClasspath.value)) - val classpath = toURLs(settings.XmacroFallbackClasspath.value) + val classpath = toURLs(settings.XmacroPrimaryClasspath.value) ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) } else { macroLogVerbose("primary macro classloader: initializing from -cp: %s".format(global.classPath.asURLs)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 9580cd5676..48fd6ba928 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -835,13 +835,15 @@ trait Namers extends MethodSynthesis { // add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because // the namer phase must traverse this copy method to create default getters for its parameters. - // here, clazz is the ClassSymbol of the case class (not the module). - if (clazz.isClass && !clazz.hasModuleFlag) { + // here, clazz is the ClassSymbol of the case class (not the module). (!clazz.hasModuleFlag) excludes + // the moduleClass symbol of the companion object when the companion is a "case object". + if (clazz.isCaseClass && !clazz.hasModuleFlag) { val modClass = companionSymbolOf(clazz, context).moduleClass modClass.attachments.get[ClassForCaseCompanionAttachment] foreach { cma => val cdef = cma.caseClass def hasCopy(decls: Scope) = (decls lookup nme.copy) != NoSymbol - if (cdef.mods.isCase && !hasCopy(decls) && + // SI-5956 needs (cdef.symbol == clazz): there can be multiple class symbols with the same name + if (cdef.symbol == clazz && !hasCopy(decls) && !parents.exists(p => hasCopy(p.typeSymbol.info.decls)) && !parents.flatMap(_.baseClasses).distinct.exists(bc => hasCopy(bc.info.decls))) addCopyMethod(cdef, templateNamer) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index 0dd4f9fbf9..5c94ff63a6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -27,14 +27,13 @@ import reflect.internal.util.Statistics * Cases are combined into a pattern match using the `orElse` combinator (the implicit failure case is expressed using the monad's `zero`). * * TODO: - * - use TypeTags for type testing * - DCE (on irrefutable patterns) * - update spec and double check it's implemented correctly (see TODO's) * * (longer-term) TODO: * - user-defined unapplyProd * - recover GADT typing by locally inserting implicit witnesses to type equalities derived from the current case, and considering these witnesses during subtyping (?) - * - recover exhaustivity and unreachability checking using a variation on the type-safe builder pattern + * - recover exhaustivity/unreachability of user-defined extractors by partitioning the types they match on using an HList or similar type-level structure */ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL { // self: Analyzer => import Statistics._ @@ -48,8 +47,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val phaseName: String = "patmat" // TODO: the inliner fails to inline the closures to patmatDebug - // private val printPatmat = settings.Ypatmatdebug.value - // @inline final def patmatDebug(s: => String) = if (printPatmat) println(s) + object debugging { + val printPatmat = settings.Ypatmatdebug.value + @inline final def patmatDebug(s: => String) = if (printPatmat) println(s) + } + import debugging.patmatDebug def newTransformer(unit: CompilationUnit): Transformer = if (!settings.XoldPatmat.value) new MatchTransformer(unit) @@ -186,7 +188,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // (that would require more sophistication when generating trees, // and the only place that emits Matches after typers is for exception handling anyway) if(phase.id >= currentRun.uncurryPhase.id) debugwarn("running translateMatch at "+ phase +" on "+ selector +" match "+ cases) - // patmatDebug ("translating "+ cases.mkString("{", "\n", "}")) + patmatDebug("translating "+ cases.mkString("{", "\n", "}")) def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { case TypeRef(_, RepeatedParamClass, arg :: Nil) => seqType(arg) @@ -311,14 +313,14 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (!extractor.isTyped) ErrorUtils.issueNormalTypeError(patTree, "Could not typecheck extractor call: "+ extractor)(context) // if (extractor.resultInMonad == ErrorType) throw new TypeError(pos, "Unsupported extractor type: "+ extractor.tpe) - // patmatDebug ("translateExtractorPattern checking parameter type: "+ (patBinder, patBinder.info.widen, extractor.paramType, patBinder.info.widen <:< extractor.paramType)) + patmatDebug("translateExtractorPattern checking parameter type: "+ (patBinder, patBinder.info.widen, extractor.paramType, patBinder.info.widen <:< extractor.paramType)) // must use type `tp`, which is provided by extractor's result, not the type expected by binder, // as b.info may be based on a Typed type ascription, which has not been taken into account yet by the translation // (it will later result in a type test when `tp` is not a subtype of `b.info`) // TODO: can we simplify this, together with the Bound case? (extractor.subPatBinders, extractor.subPatTypes).zipped foreach { case (b, tp) => - // patmatDebug ("changing "+ b +" : "+ b.info +" -> "+ tp) + patmatDebug("changing "+ b +" : "+ b.info +" -> "+ tp) b setInfo tp } @@ -425,7 +427,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL */ case Bind(n, p) => // this happens in certain ill-formed programs, there'll be an error later - // patmatDebug ("WARNING: Bind tree with unbound symbol "+ patTree) + patmatDebug("WARNING: Bind tree with unbound symbol "+ patTree) noFurtherSubPats() // there's no symbol -- something's wrong... don't fail here though (or should we?) // case Star(_) | ArrayValue | This => error("stone age pattern relics encountered!") @@ -635,7 +637,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // binder has type paramType def treeMaker(binder: Symbol, pos: Position): TreeMaker = { // checks binder ne null before chaining to the next extractor - ProductExtractorTreeMaker(binder, lengthGuard(binder), Substitution(subPatBinders, subPatRefs(binder))) + ProductExtractorTreeMaker(binder, lengthGuard(binder))(Substitution(subPatBinders, subPatRefs(binder))) } // reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component @@ -679,7 +681,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // the extractor call (applied to the binder bound by the flatMap corresponding to the previous (i.e., enclosing/outer) pattern) val extractorApply = atPos(pos)(spliceApply(patBinderOrCasted)) val binder = freshSym(pos, pureType(resultInMonad)) // can't simplify this when subPatBinders.isEmpty, since UnitClass.tpe is definitely wrong when isSeq, and resultInMonad should always be correct since it comes directly from the extractor's result type - ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder, Substitution(subPatBinders, subPatRefs(binder)))(resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(Substitution(subPatBinders, subPatRefs(binder)), resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) } override protected def seqTree(binder: Symbol): Tree = @@ -828,7 +830,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL private[TreeMakers] def incorporateOuterSubstitution(outerSubst: Substitution): Unit = { if (currSub ne null) { - // patmatDebug ("BUG: incorporateOuterSubstitution called more than once for "+ (this, currSub, outerSubst)) + patmatDebug("BUG: incorporateOuterSubstitution called more than once for "+ (this, currSub, outerSubst)) Thread.dumpStack() } else currSub = outerSubst >> substitution @@ -890,7 +892,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * the function's body is determined by the next TreeMaker * in this function's body, and all the subsequent ones, references to the symbols in `from` will be replaced by the corresponding tree in `to` */ - case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol, localSubstitution: Substitution)(extractorReturnsBoolean: Boolean, val checkedLength: Option[Int], val prevBinder: Symbol) extends FunTreeMaker { + case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)(val localSubstitution: Substitution, extractorReturnsBoolean: Boolean, val checkedLength: Option[Int], val prevBinder: Symbol) extends FunTreeMaker { def chainBefore(next: Tree)(casegen: Casegen): Tree = { val condAndNext = extraCond map (casegen.ifThenElseZero(_, next)) getOrElse next atPos(extractor.pos)( @@ -903,7 +905,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } // TODO: allow user-defined unapplyProduct - case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree], localSubstitution: Substitution) extends FunTreeMaker { import CODE._ + case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])(val localSubstitution: Substitution) extends FunTreeMaker { import CODE._ val nextBinder = prevBinder // just passing through def chainBefore(next: Tree)(casegen: Casegen): Tree = { val nullCheck = REF(prevBinder) OBJ_NE NULL @@ -994,7 +996,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL **/ case class TypeTestTreeMaker(prevBinder: Symbol, testedBinder: Symbol, expectedTp: Type, nextBinderTp: Type)(override val pos: Position, extractorArgTypeTest: Boolean = false) extends CondTreeMaker { import TypeTestTreeMaker._ - // patmatDebug ("TTTM"+(prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)) + patmatDebug("TTTM"+(prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)) lazy val outerTestNeeded = ( !((expectedTp.prefix eq NoPrefix) || expectedTp.prefix.typeSymbol.isPackageClass) @@ -1122,7 +1124,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Tree => Tree]): Tree = fixerUpper(owner, scrut.pos){ def matchFailGen = (matchFailGenOverride orElse Some(CODE.MATCHERROR(_: Tree))) - // patmatDebug ("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) + patmatDebug("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (unchecked, requireSwitch) = if (settings.XnoPatmatAnalysis.value) (true, false) @@ -1176,12 +1178,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL t match { case Function(_, _) if t.symbol == NoSymbol => t.symbol = currentOwner.newAnonymousFunctionValue(t.pos) - // patmatDebug ("new symbol for "+ (t, t.symbol.ownerChain)) + patmatDebug("new symbol for "+ (t, t.symbol.ownerChain)) case Function(_, _) if (t.symbol.owner == NoSymbol) || (t.symbol.owner == origOwner) => - // patmatDebug ("fundef: "+ (t, t.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("fundef: "+ (t, t.symbol.ownerChain, currentOwner.ownerChain)) t.symbol.owner = currentOwner case d : DefTree if (d.symbol != NoSymbol) && ((d.symbol.owner == NoSymbol) || (d.symbol.owner == origOwner)) => // don't indiscriminately change existing owners! (see e.g., pos/t3440, pos/t3534, pos/unapplyContexts2) - // patmatDebug ("def: "+ (d, d.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("def: "+ (d, d.symbol.ownerChain, currentOwner.ownerChain)) if(d.symbol.isLazy) { // for lazy val's accessor -- is there no tree?? assert(d.symbol.lazyAccessor != NoSymbol && d.symbol.lazyAccessor.owner == d.symbol.owner, d.symbol.lazyAccessor) d.symbol.lazyAccessor.owner = currentOwner @@ -1191,7 +1193,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL d.symbol.owner = currentOwner // case _ if (t.symbol != NoSymbol) && (t.symbol ne null) => - // patmatDebug ("untouched "+ (t, t.getClass, t.symbol.ownerChain, currentOwner.ownerChain)) + patmatDebug("untouched "+ (t, t.getClass, t.symbol.ownerChain, currentOwner.ownerChain)) case _ => } super.traverse(t) @@ -1248,6 +1250,24 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def typesConform(tp: Type, pt: Type) = ((tp eq pt) || (tp <:< pt)) + // we use subtyping as a model for implication between instanceof tests + // i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T] + // unfortunately this is not true in general: + // SI-6022 expects instanceOfTpImplies(ProductClass.tpe, AnyRefClass.tpe) + def instanceOfTpImplies(tp: Type, tpImplied: Type) = { + val tpValue = tp.typeSymbol.isPrimitiveValueClass + + // pretend we're comparing to Any when we're actually comparing to AnyVal or AnyRef + // (and the subtype is respectively a value type or not a value type) + // this allows us to reuse subtyping as a model for implication between instanceOf tests + // the latter don't see a difference between AnyRef, Object or Any when comparing non-value types -- SI-6022 + val tpImpliedNormalizedToAny = + if (tpImplied =:= (if (tpValue) AnyValClass.tpe else AnyRefClass.tpe)) AnyClass.tpe + else tpImplied + + tp <:< tpImpliedNormalizedToAny + } + abstract class CommonCodegen extends AbsCodegen { import CODE._ def fun(arg: Symbol, body: Tree): Tree = Function(List(ValDef(arg)), body) def genTypeApply(tfun: Tree, args: Type*): Tree = if(args contains NoType) tfun else TypeApply(tfun, args.toList map TypeTree) @@ -1369,7 +1389,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case object FalseCond extends Cond {override def toString = "F"} case class AndCond(a: Cond, b: Cond) extends Cond {override def toString = a +"/\\"+ b} - case class OrCond(a: Cond, b: Cond) extends Cond {override def toString = "("+a+") \\/ ("+ b +")"} + case class OrCond(a: Cond, b: Cond) extends Cond {override def toString = "("+a+") \\/ ("+ b +")"} object EqualityCond { private val uniques = new collection.mutable.HashMap[(Tree, Tree), EqualityCond] @@ -1428,9 +1448,9 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case _ => false } - def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol, Substitution)] = xtm match { - case ExtractorTreeMaker(extractor, None, nextBinder, subst) if irrefutableExtractorType(extractor.tpe) => - Some(extractor, nextBinder, subst) + def unapply(xtm: ExtractorTreeMaker): Option[(Tree, Symbol)] = xtm match { + case ExtractorTreeMaker(extractor, None, nextBinder) if irrefutableExtractorType(extractor.tpe) => + Some(extractor, nextBinder) case _ => None } @@ -1438,18 +1458,13 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // returns (tree, tests), where `tree` will be used to refer to `root` in `tests` class TreeMakersToConds(val root: Symbol) { - def discard() = { - pointsToBound.clear() - trees.clear() - normalize = EmptySubstitution - accumSubst = EmptySubstitution - } // a variable in this set should never be replaced by a tree that "does not consist of a selection on a variable in this set" (intuitively) private val pointsToBound = collection.mutable.HashSet(root) private val trees = collection.mutable.HashSet.empty[Tree] // the substitution that renames variables to variables in pointsToBound private var normalize: Substitution = EmptySubstitution + private var substitutionComputed = false // replaces a variable (in pointsToBound) by a selection on another variable in pointsToBound // in the end, instead of having x1, x1.hd, x2, x2.hd, ... flying around, @@ -1459,29 +1474,6 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // pointsToBound -- accumSubst.from == Set(root) && (accumSubst.from.toSet -- pointsToBound) isEmpty private var accumSubst: Substitution = EmptySubstitution - private def updateSubstitution(subst: Substitution) = { - // find part of substitution that replaces bound symbols by new symbols, and reverse that part - // so that we don't introduce new aliases for existing symbols, thus keeping the set of bound symbols minimal - val (boundSubst, unboundSubst) = (subst.from zip subst.to) partition { - case (f, t) => - t.isInstanceOf[Ident] && (t.symbol ne NoSymbol) && pointsToBound(f) - } - val (boundFrom, boundTo) = boundSubst.unzip - val (unboundFrom, unboundTo) = unboundSubst.unzip - - // reverse substitution that would otherwise replace a variable we already encountered by a new variable - // NOTE: this forgets the more precise type we have for these later variables, but that's probably okay - normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_))) - // patmatDebug ("normalize subst: "+ normalize) - - val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway - pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1 - // patmatDebug ("pointsToBound: "+ pointsToBound) - - accumSubst >>= okSubst - // patmatDebug ("accumSubst: "+ accumSubst) - } - // hashconsing trees (modulo value-equality) def unique(t: Tree, tpOverride: Type = NoType): Tree = trees find (a => a.correspondsStructure(t)(sameValue)) match { @@ -1512,74 +1504,120 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // note that the sequencing of operations is important: must visit in same order as match execution // binderToUniqueTree uses the type of the first symbol that was encountered as the type for all future binders - final def treeMakerToCond(tm: TreeMaker, handleUnknown: TreeMaker => Cond, updateSubst: Boolean, rewriteNil: Boolean = false): Cond = { - if (updateSubst) updateSubstitution(tm.substitution) - - tm match { - case ttm@TypeTestTreeMaker(prevBinder, testedBinder, pt, _) => - object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy { - type Result = Cond - def and(a: Result, b: Result) = AndCond(a, b) - def outerTest(testedBinder: Symbol, expectedTp: Type) = TrueCond // TODO OuterEqCond(testedBinder, expectedType) - def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T) - val p = binderToUniqueTree(b); AndCond(NonNullCond(p), TypeCond(p, uniqueTp(pt))) + abstract class TreeMakerToCond extends (TreeMaker => Cond) { + // requires(if (!substitutionComputed)) + def updateSubstitution(subst: Substitution): Unit = { + // find part of substitution that replaces bound symbols by new symbols, and reverse that part + // so that we don't introduce new aliases for existing symbols, thus keeping the set of bound symbols minimal + val (boundSubst, unboundSubst) = (subst.from zip subst.to) partition { + case (f, t) => + t.isInstanceOf[Ident] && (t.symbol ne NoSymbol) && pointsToBound(f) + } + val (boundFrom, boundTo) = boundSubst.unzip + val (unboundFrom, unboundTo) = unboundSubst.unzip + + // reverse substitution that would otherwise replace a variable we already encountered by a new variable + // NOTE: this forgets the more precise type we have for these later variables, but that's probably okay + normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_))) + // patmatDebug ("normalize subst: "+ normalize) + + val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway + pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1 + // patmatDebug("pointsToBound: "+ pointsToBound) + + accumSubst >>= okSubst + // patmatDebug("accumSubst: "+ accumSubst) + } + + def handleUnknown(tm: TreeMaker): Cond + + /** apply itself must render a faithful representation of the TreeMaker + * + * Concretely, TrueCond must only be used to represent a TreeMaker that is sure to match and that does not do any computation at all + * e.g., doCSE relies on apply itself being sound in this sense (since it drops TreeMakers that are approximated to TrueCond -- SI-6077) + * + * handleUnknown may be customized by the caller to approximate further + * + * TODO: don't ignore outer-checks + */ + def apply(tm: TreeMaker): Cond = { + if (!substitutionComputed) updateSubstitution(tm.substitution) + + tm match { + case ttm@TypeTestTreeMaker(prevBinder, testedBinder, pt, _) => + object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy { + type Result = Cond + def and(a: Result, b: Result) = AndCond(a, b) + def outerTest(testedBinder: Symbol, expectedTp: Type) = TrueCond // TODO OuterEqCond(testedBinder, expectedType) + def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T) + val p = binderToUniqueTree(b); AndCond(NonNullCond(p), TypeCond(p, uniqueTp(pt))) + } + def nonNullTest(testedBinder: Symbol) = NonNullCond(binderToUniqueTree(testedBinder)) + def equalsTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) + def eqTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) // TODO: eq, not == } - def nonNullTest(testedBinder: Symbol) = NonNullCond(binderToUniqueTree(testedBinder)) - def equalsTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) - def eqTest(pat: Tree, testedBinder: Symbol) = EqualityCond(binderToUniqueTree(testedBinder), unique(pat)) // TODO: eq, not == - } - ttm.renderCondition(condStrategy) - case EqualityTestTreeMaker(prevBinder, patTree, _) => EqualityCond(binderToUniqueTree(prevBinder), unique(patTree)) - case AlternativesTreeMaker(_, altss, _) => \/(altss map (alts => /\(alts map (treeMakerToCond(_, handleUnknown, updateSubst))))) - case ProductExtractorTreeMaker(testedBinder, None, subst) => NonNullCond(binderToUniqueTree(testedBinder)) - case IrrefutableExtractorTreeMaker(_, _, _) => - // the extra condition is None, the extractor's result indicates it always succeeds, - // and the potential type-test for the argument is represented by a separate TypeTestTreeMaker - TrueCond - case GuardTreeMaker(guard) => - guard.tpe match { - case ConstantType(Constant(true)) => TrueCond - case ConstantType(Constant(false)) => FalseCond - case _ => handleUnknown(tm) - } - case p @ ExtractorTreeMaker(extractor, Some(lenCheck), testedBinder, _) => - p.checkedLength match { - // special-case: interpret pattern `List()` as `Nil` - // TODO: make it more general List(1, 2) => 1 :: 2 :: Nil -- not sure this is a good idea... - case Some(0) if rewriteNil && testedBinder.tpe.typeSymbol == ListClass => // extractor.symbol.owner == SeqFactory - EqualityCond(binderToUniqueTree(p.prevBinder), unique(Ident(NilModule) setType NilModule.tpe)) - case _ => handleUnknown(tm) - } - case SubstOnlyTreeMaker(_, _) => TrueCond - case ProductExtractorTreeMaker(_, Some(_), _) | - ExtractorTreeMaker(_, _, _, _) | BodyTreeMaker(_, _) => handleUnknown(tm) + ttm.renderCondition(condStrategy) + case EqualityTestTreeMaker(prevBinder, patTree, _) => EqualityCond(binderToUniqueTree(prevBinder), unique(patTree)) + case AlternativesTreeMaker(_, altss, _) => \/(altss map (alts => /\(alts map this))) + case ProductExtractorTreeMaker(testedBinder, None) => NonNullCond(binderToUniqueTree(testedBinder)) + case SubstOnlyTreeMaker(_, _) => TrueCond + case GuardTreeMaker(guard) => + guard.tpe match { + case ConstantType(Constant(true)) => TrueCond + case ConstantType(Constant(false)) => FalseCond + case _ => handleUnknown(tm) + } + case ExtractorTreeMaker(_, _, _) | + ProductExtractorTreeMaker(_, _) | + BodyTreeMaker(_, _) => handleUnknown(tm) + } } } - val constFalse = (_: TreeMaker) => FalseCond - val constTrue = (_: TreeMaker) => TrueCond - final def approximateMatch(cases: List[List[TreeMaker]], handleUnknown: TreeMaker => Cond = constFalse, rewriteNil: Boolean = false): List[List[Test]] = - cases.map { _ map (tm => Test(treeMakerToCond(tm, handleUnknown, updateSubst = true, rewriteNil), tm)) } + private val irrefutableExtractor: PartialFunction[TreeMaker, Cond] = { + // the extra condition is None, the extractor's result indicates it always succeeds, + // (the potential type-test for the argument is represented by a separate TypeTestTreeMaker) + case IrrefutableExtractorTreeMaker(_, _) => TrueCond + } - final def approximateMatchAgain(cases: List[List[TreeMaker]], handleUnknown: TreeMaker => Cond = constFalse, rewriteNil: Boolean = false): List[List[Test]] = - cases.map { _ map (tm => Test(treeMakerToCond(tm, handleUnknown, updateSubst = false, rewriteNil), tm)) } - } + // special-case: interpret pattern `List()` as `Nil` + // TODO: make it more general List(1, 2) => 1 :: 2 :: Nil -- not sure this is a good idea... + private val rewriteListPattern: PartialFunction[TreeMaker, Cond] = { + case p @ ExtractorTreeMaker(_, _, testedBinder) + if testedBinder.tpe.typeSymbol == ListClass && p.checkedLength == Some(0) => + EqualityCond(binderToUniqueTree(p.prevBinder), unique(Ident(NilModule) setType NilModule.tpe)) + } + val fullRewrite = (irrefutableExtractor orElse rewriteListPattern) + val refutableRewrite = irrefutableExtractor + @inline def onUnknown(handler: TreeMaker => Cond) = new TreeMakerToCond { + def handleUnknown(tm: TreeMaker) = handler(tm) + } - def approximateMatch(root: Symbol, cases: List[List[TreeMaker]]): List[List[Test]] = { - object approximator extends TreeMakersToConds(root) - approximator.approximateMatch(cases) + // used for CSE -- rewrite all unknowns to False (the most conserative option) + object conservative extends TreeMakerToCond { + def handleUnknown(tm: TreeMaker) = FalseCond + } + + final def approximateMatch(cases: List[List[TreeMaker]], treeMakerToCond: TreeMakerToCond = conservative) ={ + val testss = cases.map { _ map (tm => Test(treeMakerToCond(tm), tm)) } + substitutionComputed = true // a second call to approximateMatch should not re-compute the substitution (would be wrong) + testss + } } + def approximateMatchConservative(root: Symbol, cases: List[List[TreeMaker]]): List[List[Test]] = + (new TreeMakersToConds(root)).approximateMatch(cases) + def showTreeMakers(cases: List[List[TreeMaker]]) = { - // patmatDebug ("treeMakers:") - // patmatDebug (alignAcrossRows(cases, ">>")) + patmatDebug("treeMakers:") + patmatDebug(alignAcrossRows(cases, ">>")) } def showTests(testss: List[List[Test]]) = { - // patmatDebug ("tests: ") - // patmatDebug (alignAcrossRows(testss, "&")) + patmatDebug("tests: ") + patmatDebug(alignAcrossRows(testss, "&")) } } @@ -1771,7 +1809,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL override def hashCode = a.hashCode ^ b.hashCode } - // patmatDebug ("removeVarEq vars: "+ vars) + patmatDebug("removeVarEq vars: "+ vars) vars.foreach { v => val excludedPair = new collection.mutable.HashSet[ExcludedPair] @@ -1790,16 +1828,17 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } val syms = v.equalitySyms - // patmatDebug ("eqSyms "+(v, syms)) + patmatDebug("eqSyms "+(v, syms)) syms foreach { sym => // if we've already excluded the pair at some point (-A \/ -B), then don't exclude the symmetric one (-B \/ -A) // (nor the positive implications -B \/ A, or -A \/ B, which would entail the equality axioms falsifying the whole formula) val todo = syms filterNot (b => (b.const == sym.const) || excludedPair(ExcludedPair(b.const, sym.const))) val (excluded, notExcluded) = todo partition (b => sym.const.excludes(b.const)) val implied = notExcluded filter (b => sym.const.implies(b.const)) - // patmatDebug ("eq axioms for: "+ sym.const) - // patmatDebug ("excluded: "+ excluded) - // patmatDebug ("implied: "+ implied) + + patmatDebug("eq axioms for: "+ sym.const) + patmatDebug("excluded: "+ excluded) + patmatDebug("implied: "+ implied) // when this symbol is true, what must hold... implied foreach (impliedSym => addAxiom(Or(Not(sym), impliedSym))) @@ -1812,8 +1851,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } } - // patmatDebug ("eqAxioms:\n"+ cnfString(eqFreePropToSolvable(eqAxioms))) - // patmatDebug ("pure:"+ pure.map(p => cnfString(eqFreePropToSolvable(p))).mkString("\n")) + patmatDebug("eqAxioms:\n"+ cnfString(eqFreePropToSolvable(eqAxioms))) + patmatDebug("pure:"+ pure.map(p => cnfString(eqFreePropToSolvable(p))).mkString("\n")) Statistics.stopTimer(patmatAnaVarEq, start) @@ -1955,12 +1994,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def findAllModels(f: Formula, models: List[Model], recursionDepthAllowed: Int = 10): List[Model]= if (recursionDepthAllowed == 0) models else { - // patmatDebug ("find all models for\n"+ cnfString(f)) + patmatDebug("find all models for\n"+ cnfString(f)) val model = findModelFor(f) // if we found a solution, conjunct the formula with the model's negation and recurse if (model ne NoModel) { val unassigned = (vars -- model.keySet).toList - // patmatDebug ("unassigned "+ unassigned +" in "+ model) + patmatDebug("unassigned "+ unassigned +" in "+ model) def force(lit: Lit) = { val model = withLit(findModelFor(dropUnit(f, lit)), lit) if (model ne NoModel) List(model) @@ -1969,7 +2008,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val forced = unassigned flatMap { s => force(Lit(s, true)) ++ force(Lit(s, false)) } - // patmatDebug ("forced "+ forced) + patmatDebug("forced "+ forced) val negated = negateModel(model) findAllModels(f :+ negated, model :: (forced ++ models), recursionDepthAllowed - 1) } @@ -1992,7 +2031,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def findModelFor(f: Formula): Model = { @inline def orElse(a: Model, b: => Model) = if (a ne NoModel) a else b - // patmatDebug ("DPLL\n"+ cnfString(f)) + patmatDebug("DPLL\n"+ cnfString(f)) val start = Statistics.startTimer(patmatAnaDPLL) @@ -2136,11 +2175,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL uniques.get(tp).getOrElse( uniques.find {case (oldTp, oldC) => oldTp =:= tp} match { case Some((_, c)) => - // patmatDebug ("unique const: "+ (tp, c)) + patmatDebug("unique const: "+ (tp, c)) c case _ => val fresh = mkFresh - // patmatDebug ("uniqued const: "+ (tp, fresh)) + patmatDebug("uniqued const: "+ (tp, fresh)) uniques(tp) = fresh fresh }) @@ -2156,12 +2195,12 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (!t.symbol.isStable) t.tpe.narrow else trees find (a => a.correspondsStructure(t)(sameValue)) match { case Some(orig) => - // patmatDebug ("unique tp for tree: "+ (orig, orig.tpe)) + patmatDebug("unique tp for tree: "+ (orig, orig.tpe)) orig.tpe case _ => // duplicate, don't mutate old tree (TODO: use a map tree -> type instead?) val treeWithNarrowedType = t.duplicate setType t.tpe.narrow - // patmatDebug ("uniqued: "+ (t, t.tpe, treeWithNarrowedType.tpe)) + patmatDebug("uniqued: "+ (t, t.tpe, treeWithNarrowedType.tpe)) trees += treeWithNarrowedType treeWithNarrowedType.tpe } @@ -2173,25 +2212,6 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def isAny = wideTp.typeSymbol == AnyClass - // we use subtyping as a model for implication between instanceof tests - // i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T] - // unfortunately this is not true in general: - // SI-6022 expects instanceOfTpImplies(ProductClass.tpe, AnyRefClass.tpe) - private def instanceOfTpImplies(tp: Type, tpImplied: Type) = { - val tpValue = tp.typeSymbol.isPrimitiveValueClass - - // pretend we're comparing to Any when we're actually comparing to AnyVal or AnyRef - // (and the subtype is respectively a value type or not a value type) - // this allows us to reuse subtyping as a model for implication between instanceOf tests - // the latter don't see a difference between AnyRef, Object or Any when comparing non-value types -- SI-6022 - val tpImpliedNormalizedToAny = - if ((tpValue && tpImplied =:= AnyValClass.tpe) || - (!tpValue && tpImplied =:= AnyRefClass.tpe)) AnyClass.tpe - else tpImplied - - tp <:< tpImpliedNormalizedToAny - } - final def implies(other: Const): Boolean = { val r = (this, other) match { case (_: ValueConst, _: ValueConst) => this == other // hashconsed @@ -2351,11 +2371,15 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // use the same approximator so we share variables, // but need different conditions depending on whether we're conservatively looking for failure or success - val reachabilityApproximation = new TreeMakersToConds(prevBinder) - val testCasesOk = reachabilityApproximation.approximateMatch(cases, reachabilityApproximation.constTrue) - val testCasesFail = reachabilityApproximation.approximateMatchAgain(cases, reachabilityApproximation.constFalse) + // don't rewrite List-like patterns, as List() and Nil need to distinguished for unreachability + val approx = new TreeMakersToConds(prevBinder) + def approximate(default: Cond) = approx.approximateMatch(cases, approx.onUnknown { tm => + approx.refutableRewrite.applyOrElse(tm, (_: TreeMaker) => default ) + }) + + val testCasesOk = approximate(TrueCond) + val testCasesFail = approximate(FalseCond) - reachabilityApproximation.discard() prepareNewAnalysis() val propsCasesOk = testCasesOk map (t => symbolicCase(t, modelNull = true)) @@ -2375,8 +2399,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL var reachable = true var caseIndex = 0 - // patmatDebug ("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables) map (_.describe) mkString ("\n"))) - // patmatDebug ("equality axioms:\n"+ cnfString(eqAxiomsCNF)) + patmatDebug("reachability, vars:\n"+ ((propsCasesFail flatMap gatherVariables) map (_.describe) mkString ("\n"))) + patmatDebug("equality axioms:\n"+ cnfString(eqAxiomsCNF)) // invariant (prefixRest.length == current.length) && (prefix.reverse ++ prefixRest == symbolicCasesFail) // termination: prefixRest.length decreases by 1 @@ -2423,7 +2447,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL Some(List(tp)) // make sure it's not a primitive, else (5: Byte) match { case 5 => ... } sees no Byte case sym if !sym.isSealed || isPrimitiveValueClass(sym) => - // patmatDebug ("enum unsealed "+ (tp, sym, sym.isSealed, isPrimitiveValueClass(sym))) + patmatDebug("enum unsealed "+ (tp, sym, sym.isSealed, isPrimitiveValueClass(sym))) None case sym => val subclasses = ( @@ -2431,7 +2455,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // symbols which are both sealed and abstract need not be covered themselves, because // all of their children must be and they cannot otherwise be created. filterNot (x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))) - // patmatDebug ("enum sealed -- subclasses: "+ (sym, subclasses)) + patmatDebug("enum sealed -- subclasses: "+ (sym, subclasses)) val tpApprox = typer.infer.approximateAbstracts(tp) val pre = tpApprox.prefix @@ -2447,7 +2471,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (subTpApprox <:< tpApprox) Some(checkableType(subTp)) else None }) - // patmatDebug ("enum sealed "+ (tp, tpApprox) + " as "+ validSubTypes) + patmatDebug("enum sealed "+ (tp, tpApprox) + " as "+ validSubTypes) Some(validSubTypes) } @@ -2467,7 +2491,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } } val res = toCheckable(tp) - // patmatDebug ("checkable "+(tp, res)) + patmatDebug("checkable "+(tp, res)) res } @@ -2492,19 +2516,19 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val start = Statistics.startTimer(patmatAnaExhaust) var backoff = false - val exhaustivityApproximation = new TreeMakersToConds(prevBinder) - val tests = exhaustivityApproximation.approximateMatch(cases, { - case BodyTreeMaker(_, _) => TrueCond // will be discarded by symbolCase later - case tm => - // patmatDebug("backing off due to "+ tm) + val approx = new TreeMakersToConds(prevBinder) + val tests = approx.approximateMatch(cases, approx.onUnknown { tm => + approx.fullRewrite.applyOrElse[TreeMaker, Cond](tm, { + case BodyTreeMaker(_, _) => TrueCond // irrelevant -- will be discarded by symbolCase later + case _ => // patmatDebug("backing off due to "+ tm) backoff = true FalseCond - }, rewriteNil = true) + }) + }) if (backoff) Nil else { - val prevBinderTree = exhaustivityApproximation.binderToUniqueTree(prevBinder) + val prevBinderTree = approx.binderToUniqueTree(prevBinder) - exhaustivityApproximation.discard() prepareNewAnalysis() val symbolicCases = tests map (symbolicCase(_, modelNull = false)) @@ -2525,7 +2549,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val vars = gatherVariables(matchFails) // debug output: - // patmatDebug ("analysing:") + patmatDebug("analysing:") showTreeMakers(cases) showTests(tests) @@ -2545,7 +2569,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL pruned } catch { case e : CNFBudgetExceeded => - // patmatDebug (util.Position.formatMessage(prevBinder.pos, "Cannot check match for exhaustivity", false)) + patmatDebug(util.Position.formatMessage(prevBinder.pos, "Cannot check match for exhaustivity", false)) // e.printStackTrace() Nil // CNF budget exceeded } @@ -2635,7 +2659,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // ... val varAssignment = modelToVarAssignment(model) - // patmatDebug ("var assignment for model "+ model +":\n"+ varAssignmentString(varAssignment)) + patmatDebug("var assignment for model "+ model +":\n"+ varAssignmentString(varAssignment)) // chop a path into a list of symbols def chop(path: Tree): List[Symbol] = path match { @@ -2702,7 +2726,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def toCounterExample(beBrief: Boolean = false): CounterExample = if (!allFieldAssignmentsLegal) NoExample else { - // patmatDebug ("describing "+ (variable, equalTo, notEqualTo, fields, cls, allFieldAssignmentsLegal)) + patmatDebug("describing "+ (variable, equalTo, notEqualTo, fields, cls, allFieldAssignmentsLegal)) val res = prunedEqualTo match { // a definite assignment to a value case List(eq: ValueConst) if fields.isEmpty => ValueExample(eq) @@ -2743,7 +2767,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive case _ => NoExample } - // patmatDebug ("described as: "+ res) + patmatDebug("described as: "+ res) res } @@ -2768,7 +2792,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * we generalize sharing to implication, where b reuses a if a => b and priors(a) => priors(b) (the priors of a sub expression form the path through the decision tree) */ def doCSE(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): List[List[TreeMaker]] = { - val testss = approximateMatch(prevBinder, cases) + patmatDebug("before CSE:") + showTreeMakers(cases) + + val testss = approximateMatchConservative(prevBinder, cases) // interpret: val dependencies = new collection.mutable.LinkedHashMap[Test, Set[Cond]] @@ -2778,10 +2805,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL val cond = test.cond def simplify(c: Cond): Set[Cond] = c match { - case AndCond(a, b) => simplify(a) ++ simplify(b) + case AndCond(a, b) => simplify(a) ++ simplify(b) case OrCond(_, _) => Set(FalseCond) // TODO: make more precise case NonNullCond(_) => Set(TrueCond) // not worth remembering - case _ => Set(c) + case _ => Set(c) } val conds = simplify(cond) @@ -2796,7 +2823,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case (priorTest, deps) => ((simplify(priorTest.cond) == nonTrivial) || // our conditions are implied by priorTest if it checks the same thing directly (nonTrivial subsetOf deps) // or if it depends on a superset of our conditions - ) && (deps subsetOf tested) // the conditions we've tested when we are here in the match satisfy the prior test, and hence what it tested + ) && (deps subsetOf tested) // the conditions we've tested when we are here in the match satisfy the prior test, and hence what it tested } foreach { case (priorTest, _) => // if so, note the dependency in both tests @@ -2814,7 +2841,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL tested.clear() tests dropWhile storeDependencies } - // patmatDebug ("dependencies: "+ dependencies) + patmatDebug("dependencies: "+ dependencies) // find longest prefix of tests that reuse a prior test, and whose dependent conditions monotonically increase // then, collapse these contiguous sequences of reusing tests @@ -2848,7 +2875,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case _ => } - // patmatDebug("sharedPrefix: "+ sharedPrefix) + patmatDebug("sharedPrefix: "+ sharedPrefix) + patmatDebug("suffix: "+ sharedPrefix) // if the shared prefix contains interesting conditions (!= TrueCond) // and the last of such interesting shared conditions reuses another treemaker's test // replace the whole sharedPrefix by a ReusingCondTreeMaker @@ -2864,7 +2892,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // replace original treemakers that are reused (as determined when computing collapsed), // by ReusedCondTreeMakers val reusedMakers = collapsed mapConserve (_ mapConserve reusedOrOrig) - // patmatDebug ("after CSE:") + patmatDebug("after CSE:") showTreeMakers(reusedMakers) reusedMakers } @@ -2942,6 +2970,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL //// SWITCHES -- TODO: operate on Tests rather than TreeMakers trait SwitchEmission extends TreeMakers with OptimizedMatchMonadInterface { self: CodegenCore => + import treeInfo.isGuardedCase + abstract class SwitchMaker { abstract class SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] } val SwitchableTreeMaker: SwitchableTreeMakerExtractor @@ -2978,91 +3008,170 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL /** Collapse guarded cases that switch on the same constant (the last case may be unguarded). * - * `{case C if(G_i) => B_i | case C'_j if G'_j => B'_j}*` is rewritten to - * `case C => {if(G_i) B_i}*` ++ rewrite({case C'_j if G'_j => B'_j}*) + * Cases with patterns A and B switch on the same constant iff for all values x that match A also match B and vice versa. + * (This roughly corresponds to equality on trees modulo alpha renaming and reordering of alternatives.) * + * The rewrite only applies if some of the cases are guarded (this must be checked before invoking this method). + * + * The rewrite goes through the switch top-down and merges each case with the subsequent cases it is implied by + * (i.e. it matches if they match, not taking guards into account) + * + * If there are no unreachable cases, all cases can be uniquely assigned to a partition of such 'overlapping' cases, + * save for the default case (thus we jump to it rather than copying it several times). + * (The cases in a partition are implied by the principal element of the partition.) + * + * The overlapping cases are merged into one case with their guards pushed into the body as follows + * (with P the principal element of the overlapping patterns Pi): + * + * `{case Pi if(G_i) => B_i }*` is rewritten to `case P => {if(G_i) B_i}*` + * + * The rewrite fails (and returns Nil) when: + * (1) there is a subsequence of overlapping cases that has an unguarded case in the middle; + * only the last case of each subsequence of overlapping cases may be unguarded (this is implied by unreachability) + * + * (2) there are overlapping cases that differ (tested by `caseImpliedBy`) + * cases with patterns A and B are overlapping if for SOME value x, A matches x implies B matches y OR vice versa <-- note the difference with case equality defined above + * for example `case 'a' | 'b' =>` and `case 'b' =>` are different and overlapping (overlapping and equality disregard guards) + * + * The second component of the returned tuple indicates whether we'll need to emit a labeldef to jump to the default case. */ - private def collapseGuardedCases(cases: List[CaseDef]) = { - // requires(switchesOnSameConst.forall(caseChecksSameConst(switchesOnSameConst.head))) - def collapse(switchesOnSameConst: List[CaseDef]): List[CaseDef] = - if (switchesOnSameConst.tail.isEmpty && (switchesOnSameConst.head.guard == EmptyTree)) switchesOnSameConst - else { - val commonPattern = switchesOnSameConst.head.pat - - // jump to default case (either the user-supplied one or the synthetic one) - // unless we're collapsing the default case, then re-use the same body as the synthetic catchall (throwing a matcherror, rethrowing the exception) - val jumpToDefault: Tree = - if (!canJump || isDefault(CaseDef(commonPattern, EmptyTree, EmptyTree))) defaultBody - else Apply(Ident(defaultLabel), Nil) - - val guardedBody = switchesOnSameConst.foldRight(jumpToDefault){ - // the last case may be un-guarded (we know it's the last one since fold's accum == jumpToDefault) - // --> replace jumpToDefault by the un-guarded case's body - case (CaseDef(_, EmptyTree, b), `jumpToDefault`) => b - case (CaseDef(_, g, b), els) if g != EmptyTree => If(g, b, els) - // error: the un-guarded case did not come last - case _ => - return switchesOnSameConst - } + private def collapseGuardedCases(cases: List[CaseDef]): (List[CaseDef], Boolean) = { + // requires(same.forall(caseEquals(same.head))) + // requires(same.nonEmpty, same) + def collapse(same: List[CaseDef], isDefault: Boolean): CaseDef = { + val commonPattern = same.head.pat + // jump to default case (either the user-supplied one or the synthetic one) + // unless we're collapsing the default case: then we re-use the same body as the synthetic catchall (throwing a matcherror, rethrowing the exception) + val jumpToDefault: Tree = + if (isDefault || !canJump) defaultBody + else Apply(Ident(defaultLabel), Nil) + + val guardedBody = same.foldRight(jumpToDefault){ + // the last case may be un-guarded (we know it's the last one since fold's accum == jumpToDefault) + // --> replace jumpToDefault by the un-guarded case's body + case (CaseDef(_, EmptyTree, b), `jumpToDefault`) => b + case (cd@CaseDef(_, g, b), els) if isGuardedCase(cd) => If(g, b, els) + } - // if the cases that we're going to collapse bind variables, - // must replace them by the single binder introduced by the collapsed case - val binders = switchesOnSameConst.collect{case CaseDef(x@Bind(_, _), _, _) if x.symbol != NoSymbol => x.symbol} - val (pat, guardedBodySubst) = - if (binders.isEmpty) (commonPattern, guardedBody) - else { - // create a single fresh binder to subsume the old binders (and their types) - // TODO: I don't think the binder's types can actually be different (due to checks in caseChecksSameConst) - // if they do somehow manage to diverge, the lub might not be precise enough and we could get a type error - val binder = freshSym(binders.head.pos, lub(binders.map(_.tpe))) - - // the patterns in switchesOnSameConst are equal (according to caseChecksSameConst) and modulo variable-binding - // we can thus safely pick the first one arbitrarily, provided we correct binding - val origPatWithoutBind = commonPattern match { - case Bind(b, orig) => orig - case o => o - } - // need to replace `defaultSym` as well -- it's used in `defaultBody` (see `jumpToDefault` above) - val unifiedBody = guardedBody.substituteSymbols(defaultSym :: binders, binder :: binders.map(_ => binder)) - (Bind(binder, origPatWithoutBind), unifiedBody) + // if the cases that we're going to collapse bind variables, + // must replace them by the single binder introduced by the collapsed case + val binders = same.collect{case CaseDef(x@Bind(_, _), _, _) if x.symbol != NoSymbol => x.symbol} + val (pat, guardedBodySubst) = + if (binders.isEmpty) (commonPattern, guardedBody) + else { + // create a single fresh binder to subsume the old binders (and their types) + // TODO: I don't think the binder's types can actually be different (due to checks in caseEquals) + // if they do somehow manage to diverge, the lub might not be precise enough and we could get a type error + // TODO: reuse name exactly if there's only one binder in binders + val binder = freshSym(binders.head.pos, lub(binders.map(_.tpe)), binders.head.name.toString) + + // the patterns in same are equal (according to caseEquals) + // we can thus safely pick the first one arbitrarily, provided we correct binding + val origPatWithoutBind = commonPattern match { + case Bind(b, orig) => orig + case o => o } + // need to replace `defaultSym` as well -- it's used in `defaultBody` (see `jumpToDefault` above) + val unifiedBody = guardedBody.substituteSymbols(defaultSym :: binders, binder :: binders.map(_ => binder)) + (Bind(binder, origPatWithoutBind), unifiedBody) + } - List(CaseDef(pat, EmptyTree, guardedBodySubst)) - } + atPos(commonPattern.pos)(CaseDef(pat, EmptyTree, guardedBodySubst)) + } - @annotation.tailrec def partitionAndCollapse(cases: List[CaseDef], accum: List[CaseDef] = Nil): List[CaseDef] = - if (cases.isEmpty) accum - else { - val (same, others) = cases.tail partition (caseChecksSameConst(cases.head)) - partitionAndCollapse(others, accum ++ collapse(cases.head :: same)) + // requires cases.exists(isGuardedCase) (otherwise the rewrite is pointless) + var remainingCases = cases + val collapsed = collection.mutable.ListBuffer.empty[CaseDef] + + // when some of collapsed cases (except for the default case itself) did not include an un-guarded case + // we'll need to emit a labeldef for the default case + var needDefault = false + + while (remainingCases.nonEmpty) { + val currCase = remainingCases.head + val currIsDefault = isDefault(CaseDef(currCase.pat, EmptyTree, EmptyTree)) + val (impliesCurr, others) = + // the default case is implied by all cases, no need to partition (and remainingCases better all be default cases as well) + if (currIsDefault) (remainingCases.tail, Nil) + else remainingCases.tail partition (caseImplies(currCase)) + + val unguardedComesLastOrAbsent = + (!isGuardedCase(currCase) && impliesCurr.isEmpty) || { val LastImpliesCurr = impliesCurr.length - 1 + impliesCurr.indexWhere(oc => !isGuardedCase(oc)) match { + // if all cases are guarded we will have to jump to the default case in the final else + // (except if we're collapsing the default case itself) + case -1 => + if (!currIsDefault) needDefault = true + true + + // last case is not guarded, no need to jump to the default here + // note: must come after case -1 => (since LastImpliesCurr may be -1) + case LastImpliesCurr => true + + case _ => false + }} + + if (unguardedComesLastOrAbsent /*(1)*/ && impliesCurr.forall(caseEquals(currCase)) /*(2)*/) { + collapsed += ( + if (impliesCurr.isEmpty && !isGuardedCase(currCase)) currCase + else collapse(currCase :: impliesCurr, currIsDefault) + ) + + remainingCases = others + } else { // fail + collapsed.clear() + remainingCases = Nil } + } - // common case: no rewrite needed when there are no guards - if (cases.forall(c => c.guard == EmptyTree)) cases - else partitionAndCollapse(cases) + (collapsed.toList, needDefault) } - private def caseChecksSameConst(x: CaseDef)(y: CaseDef) = (x, y) match { + private def caseEquals(x: CaseDef)(y: CaseDef) = patternEquals(x.pat)(y.pat) + private def patternEquals(x: Tree)(y: Tree): Boolean = (x, y) match { + case (Alternative(xs), Alternative(ys)) => + xs.forall(x => ys.exists(patternEquals(x))) && + ys.forall(y => xs.exists(patternEquals(y))) + case (Alternative(pats), _) => pats.forall(p => patternEquals(p)(y)) + case (_, Alternative(pats)) => pats.forall(q => patternEquals(x)(q)) // regular switch - case (CaseDef(Literal(Constant(cx)), _, _), CaseDef(Literal(Constant(cy)), _, _)) => cx == cy - case (CaseDef(Ident(nme.WILDCARD), _, _), CaseDef(Ident(nme.WILDCARD), _, _)) => true + case (Literal(Constant(cx)), Literal(Constant(cy))) => cx == cy + case (Ident(nme.WILDCARD), Ident(nme.WILDCARD)) => true // type-switch for catch - case (CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpX)), _, _), CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpY)), _, _)) => tpX.tpe =:= tpY.tpe + case (Bind(_, Typed(Ident(nme.WILDCARD), tpX)), Bind(_, Typed(Ident(nme.WILDCARD), tpY))) => tpX.tpe =:= tpY.tpe case _ => false } - private def checkNoGuards(cs: List[CaseDef]) = - if (cs.exists{case CaseDef(_, g, _) => g != EmptyTree case _ => false}) None - else Some(cs) + // if y matches then x matches for sure (thus, if x comes before y, y is unreachable) + private def caseImplies(x: CaseDef)(y: CaseDef) = patternImplies(x.pat)(y.pat) + private def patternImplies(x: Tree)(y: Tree): Boolean = (x, y) match { + // since alternatives are flattened, must treat them as separate cases + case (Alternative(pats), _) => pats.exists(p => patternImplies(p)(y)) + case (_, Alternative(pats)) => pats.exists(q => patternImplies(x)(q)) + // regular switch + case (Literal(Constant(cx)), Literal(Constant(cy))) => cx == cy + case (Ident(nme.WILDCARD), _) => true + // type-switch for catch + case (Bind(_, Typed(Ident(nme.WILDCARD), tpX)), + Bind(_, Typed(Ident(nme.WILDCARD), tpY))) => instanceOfTpImplies(tpY.tpe, tpX.tpe) + case _ => false + } - // requires(cs.forall(_.guard == EmptyTree)) + private def noGuards(cs: List[CaseDef]): Boolean = !cs.exists(isGuardedCase) + + // must do this before removing guards from cases and collapsing (SI-6011, SI-6048) private def unreachableCase(cs: List[CaseDef]): Option[CaseDef] = { var cases = cs var unreachable: Option[CaseDef] = None while (cases.nonEmpty && unreachable.isEmpty) { - if (isDefault(cases.head) && cases.tail.nonEmpty) unreachable = Some(cases.tail.head) - else unreachable = cases.tail.find(caseChecksSameConst(cases.head)) + val currCase = cases.head + if (isDefault(currCase) && cases.tail.nonEmpty) // subsumed by the `else if` that follows, but faster + unreachable = Some(cases.tail.head) + else if (!isGuardedCase(currCase) || currCase.guard.tpe =:= ConstantType(Constant(true))) + unreachable = cases.tail.find(caseImplies(currCase)) + else if (currCase.guard.tpe =:= ConstantType(Constant(false))) + unreachable = Some(currCase) cases = cases.tail } @@ -3071,68 +3180,80 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } // empty list ==> failure - def apply(cases: List[(Symbol, List[TreeMaker])], pt: Type): List[CaseDef] = { - val caseDefs = cases map { case (scrutSym, makers) => - makers match { - // default case - case GuardAndBodyTreeMakers(guard, body) => - Some(defaultCase(scrutSym, guard, body)) - // constant (or typetest for typeSwitch) - case SwitchableTreeMaker(pattern) :: GuardAndBodyTreeMakers(guard, body) => - Some(CaseDef(pattern, guard, body)) - // alternatives - case AlternativesTreeMaker(_, altss, _) :: GuardAndBodyTreeMakers(guard, body) if alternativesSupported => - val switchableAlts = altss map { - case SwitchableTreeMaker(pattern) :: Nil => - Some(pattern) - case _ => - None - } + def apply(cases: List[(Symbol, List[TreeMaker])], pt: Type): List[CaseDef] = + // generate if-then-else for 1 case switch (avoids verify error... can't imagine a one-case switch being faster than if-then-else anyway) + if (cases.isEmpty || cases.tail.isEmpty) Nil + else { + val caseDefs = cases map { case (scrutSym, makers) => + makers match { + // default case + case GuardAndBodyTreeMakers(guard, body) => + Some(defaultCase(scrutSym, guard, body)) + // constant (or typetest for typeSwitch) + case SwitchableTreeMaker(pattern) :: GuardAndBodyTreeMakers(guard, body) => + Some(CaseDef(pattern, guard, body)) + // alternatives + case AlternativesTreeMaker(_, altss, _) :: GuardAndBodyTreeMakers(guard, body) if alternativesSupported => + val switchableAlts = altss map { + case SwitchableTreeMaker(pattern) :: Nil => + Some(pattern) + case _ => + None + } - // succeed if they were all switchable - sequence(switchableAlts) map { switchableAlts => - CaseDef(Alternative(switchableAlts), guard, body) - } - case _ => - // patmatDebug("can't emit switch for "+ makers) - None //failure (can't translate pattern to a switch) + // succeed if they were all switchable + sequence(switchableAlts) map { switchableAlts => + CaseDef(Alternative(switchableAlts), guard, body) + } + case _ => + // patmatDebug("can't emit switch for "+ makers) + None //failure (can't translate pattern to a switch) + } + } + + val caseDefsWithGuards = sequence(caseDefs) match { + case None => return Nil + case Some(cds) => cds } - } - (for( - caseDefsWithGuards <- sequence(caseDefs); - collapsed = collapseGuardedCases(caseDefsWithGuards); - caseDefs <- checkNoGuards(collapsed)) yield { - if (!unchecked) - unreachableCase(caseDefs) foreach (cd => reportUnreachable(cd.body.pos)) - - // if we rewrote, we may need the default label to jump there (but then again, we may not) - // TODO: make more precise; we don't need the default label if - // - all collapsed cases included an un-guarded case (some of the guards of each case will always be true) - // - or: there was no default case (if all the guards of a case fail, it's a matcherror for sure) - val needDefaultLabel = (collapsed != caseDefsWithGuards) - - def wrapInDefaultLabelDef(cd: CaseDef): CaseDef = - if (needDefaultLabel && canJump) deriveCaseDef(cd){ b => - // TODO: can b.tpe ever be null? can't really use pt, see e.g. pos/t2683 or cps/match1.scala - defaultLabel setInfo MethodType(Nil, if (b.tpe != null) b.tpe else pt) - LabelDef(defaultLabel, Nil, b) - } else cd - - (caseDefs partition isDefault) match { - case (Nil, caseDefs) => caseDefs :+ wrapInDefaultLabelDef(defaultCase()) - case (default :: Nil, caseDefs) if canJump || !needDefaultLabel => - // we either didn't collapse (and thus definitely didn't have to emit a jump), - // or we canJump (and then the potential jumps in collapsed are ok) - caseDefs :+ wrapInDefaultLabelDef(default) - case _ => Nil - // TODO: if (canJump) error message (but multiple defaults should be caught by unreachability) - // if (!canJump) we got ourselves in the situation where we might need to emit a jump when we can't (in exception handler) - // --> TODO: refine the condition to detect whether we actually really needed to jump, but this seems relatively rare + val allReachable = + if (unchecked) true + else { + val unreachables = unreachableCase(caseDefsWithGuards) + unreachables foreach {cd => reportUnreachable(cd.body.pos)} + // a switch with duplicate cases yields a verify error, + // and a switch with duplicate cases and guards cannot soundly be rewritten to an unguarded switch + // (even though the verify error would disappear, the behaviour would change) + unreachables.isEmpty + } + + if (!allReachable) Nil + else if (noGuards(caseDefsWithGuards)) { + if (isDefault(caseDefsWithGuards.last)) caseDefsWithGuards + else caseDefsWithGuards :+ defaultCase() + } else { + // collapse identical cases with different guards, push guards into body for all guarded cases + // this translation is only sound if there are no unreachable (duplicate) cases + // it should only be run if there are guarded cases, and on failure it returns Nil + val (collapsed, needDefaultLabel) = collapseGuardedCases(caseDefsWithGuards) + + if (collapsed.isEmpty || (needDefaultLabel && !canJump)) Nil + else { + def wrapInDefaultLabelDef(cd: CaseDef): CaseDef = + if (needDefaultLabel) deriveCaseDef(cd){ b => + // TODO: can b.tpe ever be null? can't really use pt, see e.g. pos/t2683 or cps/match1.scala + defaultLabel setInfo MethodType(Nil, if (b.tpe != null) b.tpe else pt) + LabelDef(defaultLabel, Nil, b) + } else cd + + val last = collapsed.last + if (isDefault(last)) { + if (!needDefaultLabel) collapsed + else collapsed.init :+ wrapInDefaultLabelDef(last) + } else collapsed :+ wrapInDefaultLabelDef(defaultCase()) } } - ) getOrElse Nil - } + } } class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree], val unchecked: Boolean) extends SwitchMaker { diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index f67cec730b..b544407286 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -19,6 +19,10 @@ import symtab.Flags._ * of class-members which are private up to an enclosing non-package * class, in order to avoid overriding conflicts. * + * This phase also sets SPECIALIZED flag on type parameters with + * `@specialized` annotation. We put this logic here because the + * flag must be set before pickling. + * * @author Martin Odersky * @version 1.0 */ @@ -208,6 +212,15 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT case TypeApply(sel @ Select(This(_), name), args) => mayNeedProtectedAccessor(sel, args, false) + // set a flag for all type parameters with `@specialized` annotation so it can be pickled + case typeDef: TypeDef if typeDef.symbol.deSkolemize.hasAnnotation(definitions.SpecializedClass) => + debuglog("setting SPECIALIZED flag on typeDef.symbol.deSkolemize where typeDef = " + typeDef) + // we need to deSkolemize symbol so we get the same symbol as others would get when + // inspecting type parameter from "outside"; see the discussion of skolems here: + // https://groups.google.com/d/topic/scala-internals/0j8laVNTQsI/discussion + typeDef.symbol.deSkolemize.setFlag(SPECIALIZED) + typeDef + case sel @ Select(qual @ This(_), name) => // warn if they are selecting a private[this] member which // also exists in a superclass, because they may be surprised diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 8ea66979bc..63ecfa32b2 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -43,7 +43,7 @@ trait FastTrack { ApiUniverseReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExpr(c.prefix.tree, EmptyTree, expr) } MacroContextReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExprForMacroContext(c.prefix.tree, expr) } ReflectRuntimeCurrentMirror bindTo { case (c, _) => scala.reflect.runtime.Macros.currentMirror(c).tree } - StringContext_f bindTo { case (c, Apply(Select(Apply(_, parts), _), args)) => c.macro_StringInterpolation_f(parts, args) } + StringContext_f bindTo { case (c, app@Apply(Select(Apply(_, parts), _), args)) => c.macro_StringInterpolation_f(parts, args, app.pos) } registry } }
\ No newline at end of file diff --git a/src/compiler/scala/tools/reflect/MacroImplementations.scala b/src/compiler/scala/tools/reflect/MacroImplementations.scala index a5f7928f55..604bd7cd1a 100644 --- a/src/compiler/scala/tools/reflect/MacroImplementations.scala +++ b/src/compiler/scala/tools/reflect/MacroImplementations.scala @@ -8,9 +8,9 @@ import scala.collection.mutable.Stack abstract class MacroImplementations { val c: Context - import c.universe._ + import c.universe.{Position => SPosition, _} - def macro_StringInterpolation_f(parts: List[Tree], args: List[Tree]): Tree = { + def macro_StringInterpolation_f(parts: List[Tree], args: List[Tree], origApplyPos: SPosition): Tree = { // the parts all have the same position information (as the expression is generated by the compiler) // the args have correct position information @@ -25,7 +25,6 @@ abstract class MacroImplementations { c.abort(args(parts.length-1).pos, "too many arguments for interpolated string") } - val stringParts = parts map { case Literal(Constant(s: String)) => s; case _ => throw new IllegalArgumentException("argument parts must be a list of string literals") @@ -39,7 +38,7 @@ abstract class MacroImplementations { def defval(value: Tree, tpe: Type): Unit = { val freshName = newTermName(c.fresh("arg$")) - evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe) setPos value.pos.focus, value) setPos value.pos ids += Ident(freshName) } @@ -141,7 +140,7 @@ abstract class MacroImplementations { List(ids: _* ) ); - Block(evals.toList, expr) + Block(evals.toList, atPos(origApplyPos.focus)(expr)) setPos origApplyPos.makeTransparent } }
\ No newline at end of file diff --git a/src/eclipse/partest/.classpath b/src/eclipse/partest/.classpath new file mode 100644 index 0000000000..b14e465aa6 --- /dev/null +++ b/src/eclipse/partest/.classpath @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="partest"/> + <classpathentry combineaccessrules="false" kind="src" path="/reflect"/> + <classpathentry combineaccessrules="false" kind="src" path="/scala-library"/> + <classpathentry combineaccessrules="false" kind="src" path="/scala-compiler"/> + <classpathentry combineaccessrules="false" kind="src" path="/scalap"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="lib" path="lib/ant/ant.jar"/> + <classpathentry kind="lib" path="lib/jline.jar"/> + <classpathentry kind="lib" path="lib/msil.jar"/> + <classpathentry combineaccessrules="false" kind="src" path="/asm"/> + <classpathentry kind="output" path="build-quick-partest"/> +</classpath> diff --git a/src/eclipse/partest/.project b/src/eclipse/partest/.project new file mode 100644 index 0000000000..45c24332ba --- /dev/null +++ b/src/eclipse/partest/.project @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>partest</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.scala-ide.sdt.core.scalabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.scala-ide.sdt.core.scalanature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> + <linkedResources> + <link> + <name>build-quick-partest</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/build/quick/classes/partest</locationURI> + </link> + <link> + <name>lib</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/lib</locationURI> + </link> + <link> + <name>partest</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/src/partest</locationURI> + </link> + </linkedResources> +</projectDescription> diff --git a/src/eclipse/reflect/.classpath b/src/eclipse/reflect/.classpath index 3fb1d08d4d..57a3928dc3 100644 --- a/src/eclipse/reflect/.classpath +++ b/src/eclipse/reflect/.classpath @@ -2,7 +2,6 @@ <classpath> <classpathentry kind="src" path="reflect"/> <classpathentry combineaccessrules="false" kind="src" path="/scala-library"/> - <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <classpathentry kind="output" path="build-quick-reflect"/> </classpath> diff --git a/src/eclipse/scala-compiler/.classpath b/src/eclipse/scala-compiler/.classpath index e0264b9856..40a4ed9996 100644 --- a/src/eclipse/scala-compiler/.classpath +++ b/src/eclipse/scala-compiler/.classpath @@ -5,7 +5,6 @@ <classpathentry combineaccessrules="false" kind="src" path="/scala-library"/> <classpathentry combineaccessrules="false" kind="src" path="/fjbg"/> <classpathentry combineaccessrules="false" kind="src" path="/asm"/> - <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="lib" path="lib/ant/ant.jar"/> <classpathentry kind="lib" path="lib/jline.jar"/> diff --git a/src/eclipse/scalap/.classpath b/src/eclipse/scalap/.classpath new file mode 100644 index 0000000000..2b44ad19b2 --- /dev/null +++ b/src/eclipse/scalap/.classpath @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="scalap"/> + <classpathentry combineaccessrules="false" kind="src" path="/reflect"/> + <classpathentry combineaccessrules="false" kind="src" path="/scala-library"/> + <classpathentry combineaccessrules="false" kind="src" path="/scala-compiler"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="lib" path="lib/ant/ant.jar"/> + <classpathentry kind="lib" path="lib/jline.jar"/> + <classpathentry kind="lib" path="lib/msil.jar"/> + <classpathentry kind="output" path="build-quick-scalap"/> +</classpath> diff --git a/src/eclipse/scalap/.project b/src/eclipse/scalap/.project new file mode 100644 index 0000000000..3599168e32 --- /dev/null +++ b/src/eclipse/scalap/.project @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>scalap</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.scala-ide.sdt.core.scalabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.scala-ide.sdt.core.scalanature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> + <linkedResources> + <link> + <name>build-quick-scalap</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/build/quick/classes/scalap</locationURI> + </link> + <link> + <name>lib</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/lib</locationURI> + </link> + <link> + <name>scalap</name> + <type>2</type> + <locationURI>SCALA_BASEDIR/src/scalap</locationURI> + </link> + </linkedResources> +</projectDescription> diff --git a/src/intellij/README b/src/intellij/README index f24d700889..9ef612bd0a 100644 --- a/src/intellij/README +++ b/src/intellij/README @@ -1,12 +1,13 @@ -Use IntelliJ IDEA X EAP (http://confluence.jetbrains.net/display/IDEADEV/IDEA+X+EAP) -a Scala Plugin nightly build (http://confluence.jetbrains.net/display/SCA/Scala+Plugin+Nightly+Builds+for+IDEA+X) +Use the latest IntelliJ IDEA release and install the Scala plugin from within the IDE. The following steps are required to use IntelliJ IDEA on Scala trunk + - compile "locker" using "ant locker.done" - Copy the *.iml.SAMPLE / *.ipr.SAMPLE files to *.iml / *.ipr - In IDEA, create a global library named "ant" which contains "ant.jar" - - In the Scala Facet of the "library" module, update the path in the command-line - argument for "-sourcepath" + - Also create an SDK entry named "1.6" containing the java 1.6 SDK + - In the Scala Facet of the "library" and "reflect" modules, update the path in the + command-line argument for "-sourcepath" - In the Project Settings, update the "Version Control" to match your checkout Known problems - - Currently, it's not possible to build the "actors" module in IDEA + - Due to SI-4365, the "library" module has to be built using "-Yno-generic-signatures" diff --git a/src/intellij/actors.iml.SAMPLE b/src/intellij/actors.iml.SAMPLE index b095d29d7b..896c4966ff 100644 --- a/src/intellij/actors.iml.SAMPLE +++ b/src/intellij/actors.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -18,7 +18,7 @@ <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module" module-name="library" /> - <orderEntry type="library" name="lib" level="project" /> + <orderEntry type="module" module-name="forkjoin" /> </component> </module> diff --git a/src/intellij/asm.iml.SAMPLE b/src/intellij/asm.iml.SAMPLE new file mode 100644 index 0000000000..ba9e7e899f --- /dev/null +++ b/src/intellij/asm.iml.SAMPLE @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/../asm"> + <sourceFolder url="file://$MODULE_DIR$/../asm" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/src/intellij/compiler.iml.SAMPLE b/src/intellij/compiler.iml.SAMPLE index e3278d0311..696c347b7b 100644 --- a/src/intellij/compiler.iml.SAMPLE +++ b/src/intellij/compiler.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -18,8 +18,12 @@ <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module" module-name="library" /> - <orderEntry type="library" name="lib" level="project" /> <orderEntry type="module" module-name="reflect" /> + <orderEntry type="module" module-name="asm" /> + <orderEntry type="module" module-name="fjbg" /> + <orderEntry type="module" module-name="msil" /> <orderEntry type="library" name="ant" level="application" /> + <orderEntry type="library" name="jline" level="project" /> </component> </module> + diff --git a/src/intellij/fjbg.iml.SAMPLE b/src/intellij/fjbg.iml.SAMPLE new file mode 100644 index 0000000000..03eca69246 --- /dev/null +++ b/src/intellij/fjbg.iml.SAMPLE @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/../fjbg"> + <sourceFolder url="file://$MODULE_DIR$/../fjbg" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/src/intellij/forkjoin.iml.SAMPLE b/src/intellij/forkjoin.iml.SAMPLE new file mode 100644 index 0000000000..be807cc019 --- /dev/null +++ b/src/intellij/forkjoin.iml.SAMPLE @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/../forkjoin"> + <sourceFolder url="file://$MODULE_DIR$/../forkjoin" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/src/intellij/library.iml.SAMPLE b/src/intellij/library.iml.SAMPLE index a86c0a005c..9c1b7ec185 100644 --- a/src/intellij/library.iml.SAMPLE +++ b/src/intellij/library.iml.SAMPLE @@ -5,9 +5,9 @@ <configuration> <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> - <option name="compilerOptions" value="-sourcepath /Users/luc/scala/git/src/library" /> + <option name="compilerOptions" value="-sourcepath /Users/luc/scala/scala/src/library -Yno-generic-signatures" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -18,7 +18,7 @@ </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" name="lib" level="project" /> + <orderEntry type="module" module-name="forkjoin" /> </component> </module> diff --git a/src/intellij/manual.iml.SAMPLE b/src/intellij/manual.iml.SAMPLE index 10de797fc4..62810e0cba 100644 --- a/src/intellij/manual.iml.SAMPLE +++ b/src/intellij/manual.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> diff --git a/src/intellij/msil.iml.SAMPLE b/src/intellij/msil.iml.SAMPLE new file mode 100644 index 0000000000..56f794785f --- /dev/null +++ b/src/intellij/msil.iml.SAMPLE @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="scala" name="Scala"> + <configuration> + <option name="compilerLibraryLevel" value="Project" /> + <option name="compilerLibraryName" value="compiler-locker" /> + <option name="maximumHeapSize" value="1536" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/../msil"> + <sourceFolder url="file://$MODULE_DIR$/../msil" isTestSource="false" /> + <excludeFolder url="file://$MODULE_DIR$/../msil/ch/epfl/lamp/compiler/msil/tests" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="library" /> + </component> +</module> + diff --git a/src/intellij/partest.iml.SAMPLE b/src/intellij/partest.iml.SAMPLE index 7dcd868fe4..ab4a32a9b3 100644 --- a/src/intellij/partest.iml.SAMPLE +++ b/src/intellij/partest.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -17,11 +17,11 @@ </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="module" module-name="compiler" /> <orderEntry type="module" module-name="library" /> + <orderEntry type="module" module-name="reflect" /> <orderEntry type="module" module-name="actors" /> <orderEntry type="module" module-name="scalap" /> - <orderEntry type="library" name="lib" level="project" /> + <orderEntry type="module" module-name="compiler" /> <orderEntry type="library" name="ant" level="application" /> </component> </module> diff --git a/src/intellij/reflect.iml.SAMPLE b/src/intellij/reflect.iml.SAMPLE index 7f14a00699..10973c503f 100644 --- a/src/intellij/reflect.iml.SAMPLE +++ b/src/intellij/reflect.iml.SAMPLE @@ -5,9 +5,9 @@ <configuration> <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> - <option name="compilerOptions" value="-sourcepath /Users/luc/scala/git/src/reflect" /> + <option name="compilerOptions" value="-sourcepath /Users/luc/scala/scala/src/reflect" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -18,8 +18,8 @@ </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" name="lib" level="project" /> <orderEntry type="module" module-name="library" /> + <orderEntry type="library" name="jline" level="project" /> </component> </module> diff --git a/src/intellij/scala-lang.ipr.SAMPLE b/src/intellij/scala-lang.ipr.SAMPLE index c5f7b904c8..37307c2029 100644 --- a/src/intellij/scala-lang.ipr.SAMPLE +++ b/src/intellij/scala-lang.ipr.SAMPLE @@ -32,7 +32,7 @@ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> <component name="EntryPointsManager"> <entry_points version="2.0" /> - </component> + </component> <component name="InspectionProjectProfileManager"> <profiles> <profile version="1.0" is_locked="false"> @@ -196,9 +196,13 @@ <component name="ProjectModuleManager"> <modules> <module fileurl="file://$PROJECT_DIR$/actors.iml" filepath="$PROJECT_DIR$/actors.iml" /> + <module fileurl="file://$PROJECT_DIR$/asm.iml" filepath="$PROJECT_DIR$/asm.iml" /> <module fileurl="file://$PROJECT_DIR$/compiler.iml" filepath="$PROJECT_DIR$/compiler.iml" /> + <module fileurl="file://$PROJECT_DIR$/fjbg.iml" filepath="$PROJECT_DIR$/fjbg.iml" /> + <module fileurl="file://$PROJECT_DIR$/forkjoin.iml" filepath="$PROJECT_DIR$/forkjoin.iml" /> <module fileurl="file://$PROJECT_DIR$/library.iml" filepath="$PROJECT_DIR$/library.iml" /> <module fileurl="file://$PROJECT_DIR$/manual.iml" filepath="$PROJECT_DIR$/manual.iml" /> + <module fileurl="file://$PROJECT_DIR$/msil.iml" filepath="$PROJECT_DIR$/msil.iml" /> <module fileurl="file://$PROJECT_DIR$/partest.iml" filepath="$PROJECT_DIR$/partest.iml" /> <module fileurl="file://$PROJECT_DIR$/reflect.iml" filepath="$PROJECT_DIR$/reflect.iml" /> <module fileurl="file://$PROJECT_DIR$/scala.iml" filepath="$PROJECT_DIR$/scala.iml" /> @@ -213,6 +217,10 @@ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/../../out" /> </component> + <component name="ScalacSettings"> + <option name="COMPILER_LIBRARY_NAME" value="compiler-locker" /> + <option name="COMPILER_LIBRARY_LEVEL" value="Project" /> + </component> <component name="VcsDirectoryMappings"> <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> </component> @@ -221,32 +229,20 @@ <CLASSES> <root url="file://$PROJECT_DIR$/../../build/locker/classes/library" /> <root url="file://$PROJECT_DIR$/../../build/locker/classes/compiler" /> - <root url="jar://$PROJECT_DIR$/../../lib/forkjoin.jar!/" /> - <root url="jar://$PROJECT_DIR$/../../lib/fjbg.jar!/" /> - <root url="jar://$PROJECT_DIR$/../../lib/msil.jar!/" /> + <root url="file://$PROJECT_DIR$/../../build/locker/classes/reflect" /> + <root url="file://$PROJECT_DIR$/../../build/libs/classes/fjbg" /> + <root url="file://$PROJECT_DIR$/../../build/asm/classes" /> </CLASSES> <JAVADOC /> <SOURCES /> </library> - <library name="lib"> + <library name="jline"> <CLASSES> <root url="jar://$PROJECT_DIR$/../../lib/jline.jar!/" /> - <root url="jar://$PROJECT_DIR$/../../lib/forkjoin.jar!/" /> - <root url="jar://$PROJECT_DIR$/../../lib/fjbg.jar!/" /> - <root url="jar://$PROJECT_DIR$/../../lib/msil.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES /> </library> - <library name="quicklib"> - <CLASSES> - <root url="file://$PROJECT_DIR$/../../build/quick/classes/library" /> - </CLASSES> - <JAVADOC /> - <SOURCES> - <root url="file://$PROJECT_DIR$/../library" /> - </SOURCES> - </library> </component> </project> diff --git a/src/intellij/scalap.iml.SAMPLE b/src/intellij/scalap.iml.SAMPLE index 9473864fbd..77eea7c38f 100644 --- a/src/intellij/scalap.iml.SAMPLE +++ b/src/intellij/scalap.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> @@ -18,6 +18,7 @@ <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module" module-name="library" /> + <orderEntry type="module" module-name="reflect" /> <orderEntry type="module" module-name="compiler" /> </component> </module> diff --git a/src/intellij/swing.iml.SAMPLE b/src/intellij/swing.iml.SAMPLE index e9c4d13b91..c97bfdf91f 100644 --- a/src/intellij/swing.iml.SAMPLE +++ b/src/intellij/swing.iml.SAMPLE @@ -6,7 +6,7 @@ <option name="compilerLibraryLevel" value="Project" /> <option name="compilerLibraryName" value="compiler-locker" /> <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> + <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseCompressedOops -XX:+UseParallelGC" /> </configuration> </facet> </component> diff --git a/src/intellij/test.iml.SAMPLE b/src/intellij/test.iml.SAMPLE index e6729ae362..112fec428f 100644 --- a/src/intellij/test.iml.SAMPLE +++ b/src/intellij/test.iml.SAMPLE @@ -5,12 +5,16 @@ <content url="file://$MODULE_DIR$/../../test" /> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="module" module-name="actors" /> - <orderEntry type="module" module-name="compiler" /> - <orderEntry type="module" module-name="continuations" /> <orderEntry type="module" module-name="library" /> + <orderEntry type="module" module-name="reflect" /> + <orderEntry type="module" module-name="compiler" /> + <orderEntry type="module" module-name="actors" /> <orderEntry type="module" module-name="swing" /> <orderEntry type="module" module-name="partest" /> + <orderEntry type="module" module-name="asm" /> + <orderEntry type="module" module-name="fjbg" /> + <orderEntry type="module" module-name="forkjoin" /> + <orderEntry type="module" module-name="msil" /> </component> </module> diff --git a/src/library/scala/Enumeration.scala b/src/library/scala/Enumeration.scala index 2b658ee4f7..1151b04ca0 100644 --- a/src/library/scala/Enumeration.scala +++ b/src/library/scala/Enumeration.scala @@ -194,7 +194,10 @@ abstract class Enumeration (initial: Int) extends Serializable { /** a marker so we can tell whose values belong to whom come reflective-naming time */ private[Enumeration] val outerEnum = thisenum - override def compare(that: Value): Int = this.id - that.id + override def compare(that: Value): Int = + if (this.id < that.id) -1 + else if (this.id == that.id) 0 + else 1 override def equals(other: Any) = other match { case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) case _ => false @@ -236,7 +239,7 @@ abstract class Enumeration (initial: Int) extends Serializable { /** An ordering by id for values of this set */ object ValueOrdering extends Ordering[Value] { - def compare(x: Value, y: Value): Int = x.id - y.id + def compare(x: Value, y: Value): Int = x compare y } /** A class for sets of values. diff --git a/src/library/scala/Option.scala b/src/library/scala/Option.scala index c461b413d6..f651461fe6 100644 --- a/src/library/scala/Option.scala +++ b/src/library/scala/Option.scala @@ -268,9 +268,9 @@ sealed abstract class Option[+A] extends Product with Serializable { def toList: List[A] = if (isEmpty) List() else List(this.get) - /** Returns a [[scala.Left]] containing the given + /** Returns a [[scala.util.Left]] containing the given * argument `left` if this $option is empty, or - * a [[scala.Right]] containing this $option's value if + * a [[scala.util.Right]] containing this $option's value if * this is nonempty. * * @param left the expression to evaluate and return if this is empty @@ -279,9 +279,9 @@ sealed abstract class Option[+A] extends Product with Serializable { @inline final def toRight[X](left: => X) = if (isEmpty) Left(left) else Right(this.get) - /** Returns a [[scala.Right]] containing the given + /** Returns a [[scala.util.Right]] containing the given * argument `right` if this is empty, or - * a [[scala.Left]] containing this $option's value + * a [[scala.util.Left]] containing this $option's value * if this $option is nonempty. * * @param right the expression to evaluate and return if this is empty diff --git a/src/library/scala/concurrent/ConcurrentPackageObject.scala b/src/library/scala/concurrent/ConcurrentPackageObject.scala deleted file mode 100644 index 86a86966ef..0000000000 --- a/src/library/scala/concurrent/ConcurrentPackageObject.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.concurrent - -import java.util.concurrent.{ Executors, Executor, ThreadFactory } -import scala.concurrent.forkjoin.{ ForkJoinPool, ForkJoinWorkerThread } -import scala.concurrent.util.Duration -import language.implicitConversions - - -/** This package object contains primitives for concurrent and parallel programming. - */ -abstract class ConcurrentPackageObject { - - /* concurrency constructs */ - - /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. - * - * The result becomes available once the asynchronous computation is completed. - * - * @tparam T the type of the result - * @param body the asychronous computation - * @param execctx the execution context on which the future is run - * @return the `Future` holding the result of the computation - */ - def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body) - - /** Creates a promise object which can be completed with a value. - * - * @tparam T the type of the value in the promise - * @param execctx the execution context on which the promise is created on - * @return the newly created `Promise` object - */ - def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() - - /** Used to block on a piece of code which potentially blocks. - * - * @param body A piece of code which contains potentially blocking or long running calls. - * - * Calling this method may throw the following exceptions: - * - CancellationException - if the computation was cancelled - * - InterruptedException - in the case that a wait within the blockable object was interrupted - * - TimeoutException - in the case that the blockable object timed out - */ - def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) - - /** Blocks on an awaitable object. - * - * @param awaitable An object with a `block` method which runs potentially blocking or long running calls. - * - * Calling this method may throw the following exceptions: - * - CancellationException - if the computation was cancelled - * - InterruptedException - in the case that a wait within the blockable object was interrupted - * - TimeoutException - in the case that the blockable object timed out - */ - def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = - BlockContext.current.internalBlockingCall(awaitable, atMost) - - @inline implicit final def int2durationops(x: Int): DurationOps = new DurationOps(x) -} diff --git a/src/library/scala/concurrent/DelayedLazyVal.scala b/src/library/scala/concurrent/DelayedLazyVal.scala index 91e41748f5..6d262ea9a2 100644 --- a/src/library/scala/concurrent/DelayedLazyVal.scala +++ b/src/library/scala/concurrent/DelayedLazyVal.scala @@ -23,7 +23,7 @@ package scala.concurrent * @author Paul Phillips * @version 2.8 */ -class DelayedLazyVal[T](f: () => T, body: => Unit){ +class DelayedLazyVal[T](f: () => T, body: => Unit)(implicit exec: ExecutionContext){ @volatile private[this] var _isDone = false private[this] lazy val complete = f() @@ -39,10 +39,5 @@ class DelayedLazyVal[T](f: () => T, body: => Unit){ */ def apply(): T = if (isDone) complete else f() - // FIXME need to take ExecutionContext in constructor - import ExecutionContext.Implicits.global - future { - body - _isDone = true - } + exec.execute(new Runnable { def run = { body; _isDone = true } }) } diff --git a/src/library/scala/concurrent/ExecutionContext.scala b/src/library/scala/concurrent/ExecutionContext.scala index b486e5269e..8081bb32da 100644 --- a/src/library/scala/concurrent/ExecutionContext.scala +++ b/src/library/scala/concurrent/ExecutionContext.scala @@ -44,11 +44,6 @@ trait ExecutionContextExecutorService extends ExecutionContextExecutor with Exec */ object ExecutionContext { /** - * The `ExecutionContext` associated with the current `Thread` - */ - val currentExecutionContext: ThreadLocal[ExecutionContext] = new ThreadLocal //FIXME might want to set the initial value to an executionContext that throws an exception on execute and warns that it's not set - - /** * This is the explicit global ExecutionContext, * call this when you want to provide the global ExecutionContext explicitly */ @@ -82,7 +77,7 @@ object ExecutionContext { /** The default reporter simply prints the stack trace of the `Throwable` to System.err. */ - def defaultReporter: Throwable => Unit = { case t => t.printStackTrace() } + def defaultReporter: Throwable => Unit = (t: Throwable) => t.printStackTrace() } diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 75a83d6ef8..f82b79cb18 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -14,7 +14,6 @@ import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable } import java.util.concurrent.TimeUnit.{ NANOSECONDS => NANOS, MILLISECONDS ⇒ MILLIS } import java.lang.{ Iterable => JIterable } import java.util.{ LinkedList => JLinkedList } -import java.{ lang => jl } import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicBoolean } import scala.concurrent.util.Duration @@ -136,7 +135,7 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Left(t) if (impl.Future.isFutureThrowable(t) && callback.isDefinedAt(t)) => callback(t) + case Left(t) if NonFatal(t) && callback.isDefinedAt(t) => callback(t) case _ => }(executor) @@ -565,17 +564,15 @@ trait Future[+T] extends Awaitable[T] { */ object Future { - import java.{ lang => jl } - private[concurrent] val toBoxed = Map[Class[_], Class[_]]( - classOf[Boolean] -> classOf[jl.Boolean], - classOf[Byte] -> classOf[jl.Byte], - classOf[Char] -> classOf[jl.Character], - classOf[Short] -> classOf[jl.Short], - classOf[Int] -> classOf[jl.Integer], - classOf[Long] -> classOf[jl.Long], - classOf[Float] -> classOf[jl.Float], - classOf[Double] -> classOf[jl.Double], + classOf[Boolean] -> classOf[java.lang.Boolean], + classOf[Byte] -> classOf[java.lang.Byte], + classOf[Char] -> classOf[java.lang.Character], + classOf[Short] -> classOf[java.lang.Short], + classOf[Int] -> classOf[java.lang.Integer], + classOf[Long] -> classOf[java.lang.Long], + classOf[Float] -> classOf[java.lang.Float], + classOf[Double] -> classOf[java.lang.Double], classOf[Unit] -> classOf[scala.runtime.BoxedUnit] ) @@ -604,9 +601,6 @@ object Future { */ def apply[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = impl.Future(body) - import scala.collection.mutable.Builder - import scala.collection.generic.CanBuildFrom - /** Simple version of `Futures.traverse`. Transforms a `TraversableOnce[Future[A]]` into a `Future[TraversableOnce[A]]`. * Useful for reducing many `Future`s into a single `Future`. */ diff --git a/src/library/scala/concurrent/FutureTaskRunner.scala b/src/library/scala/concurrent/FutureTaskRunner.scala index 9d6f8a7a88..d7f1e1c2f9 100644 --- a/src/library/scala/concurrent/FutureTaskRunner.scala +++ b/src/library/scala/concurrent/FutureTaskRunner.scala @@ -15,7 +15,7 @@ import language.{implicitConversions, higherKinds} * * @author Philipp Haller */ -@deprecated("Use `ExecutionContext`s instead.", "2.10.0") +@deprecated("Use `ExecutionContext` instead.", "2.10.0") trait FutureTaskRunner extends TaskRunner { /** The type of the futures that the underlying task runner supports. @@ -33,6 +33,7 @@ trait FutureTaskRunner extends TaskRunner { /* Possibly blocks the current thread, for example, waiting for * a lock or condition. */ + @deprecated("Use `blocking` instead.", "2.10.0") def managedBlock(blocker: ManagedBlocker): Unit } diff --git a/src/library/scala/concurrent/JavaConversions.scala b/src/library/scala/concurrent/JavaConversions.scala index 9b5e741549..ffb9926fef 100644 --- a/src/library/scala/concurrent/JavaConversions.scala +++ b/src/library/scala/concurrent/JavaConversions.scala @@ -50,8 +50,16 @@ object JavaConversions { } } - implicit def asExecutionContext(exec: ExecutorService): ExecutionContext = null // TODO + /** + * Creates a new `ExecutionContext` which uses the provided `ExecutorService`. + */ + implicit def asExecutionContext(exec: ExecutorService): ExecutionContextExecutorService = + ExecutionContext.fromExecutorService(exec) - implicit def asExecutionContext(exec: Executor): ExecutionContext = null // TODO + /** + * Creates a new `ExecutionContext` which uses the provided `Executor`. + */ + implicit def asExecutionContext(exec: Executor): ExecutionContextExecutor = + ExecutionContext.fromExecutor(exec) } diff --git a/src/library/scala/concurrent/ManagedBlocker.scala b/src/library/scala/concurrent/ManagedBlocker.scala index 9c6f4d51d6..47bbb91f6f 100644 --- a/src/library/scala/concurrent/ManagedBlocker.scala +++ b/src/library/scala/concurrent/ManagedBlocker.scala @@ -12,6 +12,7 @@ package scala.concurrent * * @author Philipp Haller */ +@deprecated("Use `blocking` instead.", "2.10.0") trait ManagedBlocker { /** diff --git a/src/library/scala/concurrent/Scheduler.scala b/src/library/scala/concurrent/Scheduler.scala deleted file mode 100644 index 6645abcc4e..0000000000 --- a/src/library/scala/concurrent/Scheduler.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.concurrent - -import scala.concurrent.util.Duration - -/** A service for scheduling tasks and thunks for one-time, or periodic execution. - */ -trait Scheduler { - - /** Schedules a thunk for repeated execution with an initial delay and a frequency. - * - * @param delay the initial delay after which the thunk should be executed - * the first time - * @param frequency the frequency with which the thunk should be executed, - * as a time period between subsequent executions - */ - def schedule(delay: Duration, frequency: Duration)(thunk: => Unit): Cancellable - - /** Schedules a task for execution after a given delay. - * - * @param delay the duration after which the task should be executed - * @param task the task that is scheduled for execution - * @return a `Cancellable` that may be used to cancel the execution - * of the task - */ - def scheduleOnce(delay: Duration, task: Runnable): Cancellable - - /** Schedules a thunk for execution after a given delay. - * - * @param delay the duration after which the thunk should be executed - * @param task the thunk that is scheduled for execution - * @return a `Cancellable` that may be used to cancel the execution - * of the thunk - */ - def scheduleOnce(delay: Duration)(task: => Unit): Cancellable - -} - - - -trait Cancellable { - - /** Cancels the underlying task. - */ - def cancel(): Unit - -} diff --git a/src/library/scala/concurrent/Task.scala b/src/library/scala/concurrent/Task.scala deleted file mode 100644 index eb3efbb422..0000000000 --- a/src/library/scala/concurrent/Task.scala +++ /dev/null @@ -1,13 +0,0 @@ -package scala.concurrent - - - -trait Task[+T] { - - def start(): Unit - - def future: Future[T] - -} - - diff --git a/src/library/scala/concurrent/TaskRunner.scala b/src/library/scala/concurrent/TaskRunner.scala index 3180e9ce8a..2e11ac42b0 100644 --- a/src/library/scala/concurrent/TaskRunner.scala +++ b/src/library/scala/concurrent/TaskRunner.scala @@ -14,7 +14,7 @@ import language.{higherKinds, implicitConversions} * * @author Philipp Haller */ -@deprecated("Use `ExecutionContext`s instead.", "2.10.0") +@deprecated("Use `ExecutionContext` instead.", "2.10.0") trait TaskRunner { type Task[T] diff --git a/src/library/scala/concurrent/TaskRunners.scala b/src/library/scala/concurrent/TaskRunners.scala index 7994255b25..8f7d952ed8 100644 --- a/src/library/scala/concurrent/TaskRunners.scala +++ b/src/library/scala/concurrent/TaskRunners.scala @@ -14,7 +14,7 @@ import java.util.concurrent.{ThreadPoolExecutor, LinkedBlockingQueue, TimeUnit} * * @author Philipp Haller */ -@deprecated("Use `ExecutionContext`s instead.", "2.10.0") +@deprecated("Use `ExecutionContext` instead.", "2.10.0") object TaskRunners { implicit val threadRunner: FutureTaskRunner = diff --git a/src/library/scala/concurrent/ThreadPoolRunner.scala b/src/library/scala/concurrent/ThreadPoolRunner.scala index fd6882348a..594555d49b 100644 --- a/src/library/scala/concurrent/ThreadPoolRunner.scala +++ b/src/library/scala/concurrent/ThreadPoolRunner.scala @@ -16,7 +16,7 @@ import language.implicitConversions * * @author Philipp Haller */ -@deprecated("Use `ExecutionContext`s instead.", "2.10.0") +@deprecated("Use `ExecutionContext` instead.", "2.10.0") trait ThreadPoolRunner extends FutureTaskRunner { type Task[T] = Callable[T] with Runnable @@ -43,6 +43,7 @@ trait ThreadPoolRunner extends FutureTaskRunner { executor execute task } + @deprecated("Use `blocking` instead.", "2.10.0") def managedBlock(blocker: ManagedBlocker) { blocker.block() } diff --git a/src/library/scala/concurrent/ThreadRunner.scala b/src/library/scala/concurrent/ThreadRunner.scala index 76be94aa6b..ab709e0210 100644 --- a/src/library/scala/concurrent/ThreadRunner.scala +++ b/src/library/scala/concurrent/ThreadRunner.scala @@ -15,6 +15,7 @@ import language.implicitConversions * * @author Philipp Haller */ +@deprecated("Use `ExecutionContext` instead.", "2.10.0") class ThreadRunner extends FutureTaskRunner { type Task[T] = () => T @@ -47,6 +48,7 @@ class ThreadRunner extends FutureTaskRunner { () => result.get.fold[S](throw _, identity _) } + @deprecated("Use `blocking` instead.", "2.10.0") def managedBlock(blocker: ManagedBlocker) { blocker.block() } diff --git a/src/library/scala/concurrent/default/TaskImpl.scala.disabled b/src/library/scala/concurrent/default/TaskImpl.scala.disabled index 50753a7154..8b4eb12d4f 100644 --- a/src/library/scala/concurrent/default/TaskImpl.scala.disabled +++ b/src/library/scala/concurrent/default/TaskImpl.scala.disabled @@ -9,7 +9,7 @@ import scala.util.Try import scala.util import scala.concurrent.util.Duration import scala.annotation.tailrec - +import scala.util.control.NonFatal private[concurrent] trait Completable[T] { @@ -167,7 +167,7 @@ extends RecursiveAction with Task[T] with Future[T] with Completable[T] { val res = body processCallbacks(tryCompleteState(Success(res)), util.Success(res)) } catch { - case t if isFutureThrowable(t) => + case t if NonFatal(t) => processCallbacks(tryCompleteState(Failure(t)), util.Failure(t)) case t => val ee = new ExecutionException(t) diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 551a444425..98f821652f 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -10,7 +10,7 @@ package scala.concurrent.impl -import java.util.concurrent.{ Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit } +import java.util.concurrent.{ LinkedBlockingQueue, Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit, ThreadPoolExecutor } import java.util.Collection import scala.concurrent.forkjoin._ import scala.concurrent.{ BlockContext, ExecutionContext, Awaitable, ExecutionContextExecutor, ExecutionContextExecutorService } @@ -27,48 +27,70 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: } // Implement BlockContext on FJP threads - def forkJoinPoolThreadFactory = new ForkJoinPool.ForkJoinWorkerThreadFactory { - def newThread(fjp: ForkJoinPool) = new ForkJoinWorkerThread(fjp) with BlockContext { + class DefaultThreadFactory(daemonic: Boolean) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { + def wire[T <: Thread](thread: T): T = { + thread.setDaemon(daemonic) + //Potentially set things like uncaught exception handler, name etc + thread + } + + def newThread(runnable: Runnable): Thread = wire(new Thread(runnable)) + + def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = wire(new ForkJoinWorkerThread(fjp) with BlockContext { override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { var result: T = null.asInstanceOf[T] ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { @volatile var isdone = false def block(): Boolean = { - result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) // FIXME what happens if there's an exception thrown here? - isdone = true + result = try awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) finally { isdone = true } true } def isReleasable = isdone }) result } - } + }) } - def createExecutorService: ExecutorService = try { + def createExecutorService: ExecutorService = { + def getInt(name: String, f: String => Int): Int = - try f(System.getProperty(name)) catch { case e: Exception => Runtime.getRuntime.availableProcessors } + try f(System.getProperty(name)) catch { case e: Exception => Runtime.getRuntime.availableProcessors } def range(floor: Int, desired: Int, ceiling: Int): Int = if (ceiling < floor) range(ceiling, desired, floor) else scala.math.min(scala.math.max(desired, floor), ceiling) + + val desiredParallelism = range( + getInt("scala.concurrent.context.minThreads", _.toInt), + getInt("scala.concurrent.context.numThreads", { + case null | "" => Runtime.getRuntime.availableProcessors + case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt + case other => other.toInt + }), + getInt("scala.concurrent.context.maxThreads", _.toInt)) + + val threadFactory = new DefaultThreadFactory(daemonic = true) - new ForkJoinPool( - range( - getInt("scala.concurrent.ec.minThreads", _.toInt), - getInt("scala.concurrent.ec.numThreads", { - case null | "" => Runtime.getRuntime.availableProcessors - case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt - case other => other.toInt - }), - getInt("scala.concurrent.ec.maxThreads", _.toInt) - ), - forkJoinPoolThreadFactory, - null, //FIXME we should have an UncaughtExceptionHandler, see what Akka does - true) //FIXME I really think this should be async... - } catch { - case NonFatal(t) => - System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to Executors.newCachedThreadPool") - t.printStackTrace(System.err) - Executors.newCachedThreadPool() //FIXME use the same desired parallelism here too? + try { + new ForkJoinPool( + desiredParallelism, + threadFactory, + null, //FIXME we should have an UncaughtExceptionHandler, see what Akka does + true) // Async all the way baby + } catch { + case NonFatal(t) => + System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to ThreadPoolExecutor") + t.printStackTrace(System.err) + val exec = new ThreadPoolExecutor( + desiredParallelism, + desiredParallelism, + 5L, + TimeUnit.MINUTES, + new LinkedBlockingQueue[Runnable], + threadFactory + ) + exec.allowCoreThreadTimeOut(true) + exec + } } def execute(runnable: Runnable): Unit = executor match { diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 073e6c4c9f..132e1d79e7 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -21,19 +21,6 @@ private[concurrent] trait Future[+T] extends scala.concurrent.Future[T] with Awa } private[concurrent] object Future { - import java.{ lang => jl } - - private val toBoxed = Map[Class[_], Class[_]]( - classOf[Boolean] -> classOf[jl.Boolean], - classOf[Byte] -> classOf[jl.Byte], - classOf[Char] -> classOf[jl.Character], - classOf[Short] -> classOf[jl.Short], - classOf[Int] -> classOf[jl.Integer], - classOf[Long] -> classOf[jl.Long], - classOf[Float] -> classOf[jl.Float], - classOf[Double] -> classOf[jl.Double], - classOf[Unit] -> classOf[scala.runtime.BoxedUnit] - ) /** Wraps a block of code into an awaitable object. */ private[concurrent] def body2awaitable[T](body: =>T) = new Awaitable[T] { @@ -44,15 +31,7 @@ private[concurrent] object Future { def result(atMost: Duration)(implicit permit: CanAwait) = body } - def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) toBoxed(c) else c - - // TODO rename appropriately and make public - private[concurrent] def isFutureThrowable(t: Throwable) = t match { - case e: Error => false - case t: scala.util.control.ControlThrowable => false - case i: InterruptedException => false - case _ => true - } + def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) scala.concurrent.Future.toBoxed(c) else c private[impl] class PromiseCompletingRunnable[T](body: => T) extends Runnable { @@ -60,12 +39,7 @@ private[concurrent] object Future { override def run() = { promise complete { - try Right(body) catch { - case NonFatal(e) => - // Commenting out reporting for now, since it produces too much output in the tests - //executor.reportFailure(e) - Left(e) - } + try Right(body) catch { case NonFatal(e) => Left(e) } } } } diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 3ac34bef8a..84638586cf 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -10,14 +10,11 @@ package scala.concurrent.impl -import java.util.concurrent.TimeUnit.{ NANOSECONDS, MILLISECONDS } -import scala.concurrent.{ Awaitable, ExecutionContext, blocking, CanAwait, OnCompleteRunnable, TimeoutException, ExecutionException } -//import scala.util.continuations._ +import java.util.concurrent.TimeUnit.NANOSECONDS +import scala.concurrent.{ ExecutionContext, CanAwait, OnCompleteRunnable, TimeoutException, ExecutionException } import scala.concurrent.util.Duration -import scala.util import scala.annotation.tailrec import scala.util.control.NonFatal -//import scala.concurrent.NonDeterministic @@ -41,7 +38,7 @@ private class CallbackRunnable[T](val executor: ExecutionContext, val onComplete } } -object Promise { +private[concurrent] object Promise { private def resolveEither[T](source: Either[Throwable, T]): Either[Throwable, T] = source match { case Left(t) => resolver(t) @@ -59,7 +56,7 @@ object Promise { /** Default promise implementation. */ class DefaultPromise[T] extends AbstractPromise with Promise[T] { self => - updateState(null, Nil) // Start at "No callbacks" //FIXME switch to Unsafe instead of ARFU + updateState(null, Nil) // Start at "No callbacks" protected final def tryAwait(atMost: Duration): Boolean = { @tailrec @@ -80,7 +77,6 @@ object Promise { } else isCompleted } - //FIXME do not do this if there'll be no waiting awaitUnsafe(if (atMost.isFinite) atMost.toNanos else Long.MaxValue) } diff --git a/src/library/scala/concurrent/ops.scala b/src/library/scala/concurrent/ops.scala index 2cea29aefe..4de8f6cba3 100644 --- a/src/library/scala/concurrent/ops.scala +++ b/src/library/scala/concurrent/ops.scala @@ -15,7 +15,7 @@ import scala.util.control.Exception.allCatch * * @author Martin Odersky, Stepan Koltsov, Philipp Haller */ -@deprecated("Use `future` instead.", "2.10.0") +@deprecated("Use `Future` instead.", "2.10.0") object ops { val defaultRunner: FutureTaskRunner = TaskRunners.threadRunner diff --git a/src/library/scala/concurrent/package.scala b/src/library/scala/concurrent/package.scala index e8921ef531..76703bf081 100644 --- a/src/library/scala/concurrent/package.scala +++ b/src/library/scala/concurrent/package.scala @@ -8,17 +8,59 @@ package scala -import scala.util.{ Try, Success, Failure } import scala.concurrent.util.Duration /** This package object contains primitives for concurrent and parallel programming. */ -package object concurrent extends scala.concurrent.ConcurrentPackageObject { +package object concurrent { type ExecutionException = java.util.concurrent.ExecutionException type CancellationException = java.util.concurrent.CancellationException type TimeoutException = java.util.concurrent.TimeoutException + + /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. + * + * The result becomes available once the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asychronous computation + * @param execctx the execution context on which the future is run + * @return the `Future` holding the result of the computation + */ + def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body) + + /** Creates a promise object which can be completed with a value. + * + * @tparam T the type of the value in the promise + * @param execctx the execution context on which the promise is created on + * @return the newly created `Promise` object + */ + def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() + + /** Used to block on a piece of code which potentially blocks. + * + * @param body A piece of code which contains potentially blocking or long running calls. + * + * Calling this method may throw the following exceptions: + * - CancellationException - if the computation was cancelled + * - InterruptedException - in the case that a wait within the blockable object was interrupted + * - TimeoutException - in the case that the blockable object timed out + */ + def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) + + /** Blocks on an awaitable object. + * + * @param awaitable An object with a `block` method which runs potentially blocking or long running calls. + * + * Calling this method may throw the following exceptions: + * - CancellationException - if the computation was cancelled + * - InterruptedException - in the case that a wait within the blockable object was interrupted + * - TimeoutException - in the case that the blockable object timed out + */ + def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = + BlockContext.current.internalBlockingCall(awaitable, atMost) } +/* concurrency constructs */ package concurrent { sealed trait CanAwait @@ -36,9 +78,4 @@ package concurrent { } } - - final class DurationOps private[concurrent] (x: Int) { - // TODO ADD OTHERS - def ns = util.Duration.fromNanos(0) - } } diff --git a/src/library/scala/reflect/ClassManifest.scala b/src/library/scala/reflect/ClassManifest.scala index f143bf8712..d226e43e77 100644 --- a/src/library/scala/reflect/ClassManifest.scala +++ b/src/library/scala/reflect/ClassManifest.scala @@ -184,18 +184,18 @@ object ClassManifestFactory { * pass varargs as arrays into this, we get an infinitely recursive call * to boxArray. (Besides, having a separate case is more efficient) */ - def classType[T <: AnyRef](clazz: jClass[_]): ClassManifest[T] = + def classType[T](clazz: jClass[_]): ClassManifest[T] = new ClassTypeManifest[T](None, clazz, Nil) /** ClassManifest for the class type `clazz[args]`, where `clazz` is * a top-level or static class and `args` are its type arguments */ - def classType[T <: AnyRef](clazz: jClass[_], arg1: OptManifest[_], args: OptManifest[_]*): ClassManifest[T] = + def classType[T](clazz: jClass[_], arg1: OptManifest[_], args: OptManifest[_]*): ClassManifest[T] = new ClassTypeManifest[T](None, clazz, arg1 :: args.toList) /** ClassManifest for the class type `clazz[args]`, where `clazz` is * a class with non-package prefix type `prefix` and type arguments `args`. */ - def classType[T <: AnyRef](prefix: OptManifest[_], clazz: jClass[_], args: OptManifest[_]*): ClassManifest[T] = + def classType[T](prefix: OptManifest[_], clazz: jClass[_], args: OptManifest[_]*): ClassManifest[T] = new ClassTypeManifest[T](Some(prefix), clazz, args.toList) def arrayType[T](arg: OptManifest[_]): ClassManifest[Array[T]] = arg match { @@ -228,7 +228,7 @@ object ClassManifestFactory { /** Manifest for the class type `clazz[args]`, where `clazz` is * a top-level or static class */ -private class ClassTypeManifest[T <: AnyRef]( +private class ClassTypeManifest[T]( prefix: Option[OptManifest[_]], val runtimeClass: jClass[_], override val typeArguments: List[OptManifest[_]]) extends ClassManifest[T] diff --git a/src/library/scala/util/Either.scala b/src/library/scala/util/Either.scala index 1a2e2d48d5..dcfdc16d33 100644 --- a/src/library/scala/util/Either.scala +++ b/src/library/scala/util/Either.scala @@ -13,12 +13,12 @@ package scala.util import language.implicitConversions /** Represents a value of one of two possible types (a disjoint union.) - * Instances of Either are either an instance of [[scala.Left]] or [[scala.Right]]. + * Instances of Either are either an instance of [[scala.util.Left]] or [[scala.util.Right]]. * * A common use of Either is as an alternative to [[scala.Option]] for dealing * with possible missing values. In this usage, [[scala.None]] is replaced - * with a [[scala.Left]] which can contain useful information. - * [[scala.Right]] takes the place of [[scala.Some]]. Convention dictates + * with a [[scala.util.Left]] which can contain useful information. + * [[scala.util.Right]] takes the place of [[scala.Some]]. Convention dictates * that Left is used for failure and Right is used for success. * * For example, you could use ``Either[String, Int]`` to detect whether a @@ -181,7 +181,7 @@ sealed abstract class Either[+A, +B] { } /** - * The left side of the disjoint union, as opposed to the [[scala.Right]] side. + * The left side of the disjoint union, as opposed to the [[scala.util.Right]] side. * * @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse * @version 1.0, 11/10/2008 @@ -192,7 +192,7 @@ final case class Left[+A, +B](a: A) extends Either[A, B] { } /** - * The right side of the disjoint union, as opposed to the [[scala.Left]] side. + * The right side of the disjoint union, as opposed to the [[scala.util.Left]] side. * * @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse * @version 1.0, 11/10/2008 @@ -283,7 +283,7 @@ object Either { * Right(12).left.get // NoSuchElementException * }}} * - * @throws Predef.NoSuchElementException if the projection is [[scala.Right]] + * @throws Predef.NoSuchElementException if the projection is [[scala.util.Right]] */ def get = e match { case Left(a) => a diff --git a/src/library/scala/util/Try.scala b/src/library/scala/util/Try.scala index 988f68bc18..9475a05d5a 100644 --- a/src/library/scala/util/Try.scala +++ b/src/library/scala/util/Try.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** * The `Try` type represents a computation that may either result in an exception, or return a - * successfully computed value. It's similar to, but semantically different from the [[scala.Either]] type. + * successfully computed value. It's similar to, but semantically different from the [[scala.util.Either]] type. * * Instances of `Try[T]`, are either an instance of [[scala.util.Success]][T] or [[scala.util.Failure]][T]. * diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala index b4c685b70c..959d682872 100644 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ b/src/partest/scala/tools/partest/PartestTask.scala @@ -48,6 +48,7 @@ import org.apache.tools.ant.types.Commandline.Argument * - `scalaptests`, * - `scalachecktests`, * - `specializedtests`, + * - `instrumentedtests`, * - `presentationtests`, * - `scripttests`. * @@ -99,6 +100,10 @@ class PartestTask extends Task with CompilationPathProperty { specializedFiles = Some(input) } + def addConfiguredInstrumentedTests(input: FileSet) { + instrumentedFiles = Some(input) + } + def addConfiguredPresentationTests(input: FileSet) { presentationFiles = Some(input) } @@ -189,6 +194,7 @@ class PartestTask extends Task with CompilationPathProperty { private var shootoutFiles: Option[FileSet] = None private var scalapFiles: Option[FileSet] = None private var specializedFiles: Option[FileSet] = None + private var instrumentedFiles: Option[FileSet] = None private var presentationFiles: Option[FileSet] = None private var antFiles: Option[FileSet] = None private var errorOnFailed: Boolean = false @@ -245,6 +251,7 @@ class PartestTask extends Task with CompilationPathProperty { private def getShootoutFiles = getFiles(shootoutFiles) private def getScalapFiles = getFiles(scalapFiles) private def getSpecializedFiles = getFiles(specializedFiles) + private def getInstrumentedFiles = getFilesAndDirs(instrumentedFiles) private def getPresentationFiles = getDirs(presentationFiles) private def getAntFiles = getFiles(antFiles) @@ -375,6 +382,7 @@ class PartestTask extends Task with CompilationPathProperty { (getShootoutFiles, "shootout", "Running shootout tests"), (getScalapFiles, "scalap", "Running scalap tests"), (getSpecializedFiles, "specialized", "Running specialized files"), + (getInstrumentedFiles, "instrumented", "Running instrumented files"), (getPresentationFiles, "presentation", "Running presentation compiler test files"), (getAntFiles, "ant", "Running ant task tests") ) diff --git a/src/partest/scala/tools/partest/instrumented/Instrumentation.scala b/src/partest/scala/tools/partest/instrumented/Instrumentation.scala new file mode 100644 index 0000000000..f29a7f90fd --- /dev/null +++ b/src/partest/scala/tools/partest/instrumented/Instrumentation.scala @@ -0,0 +1,86 @@ +/* NEST (New Scala Test) + * Copyright 2007-2012 LAMP/EPFL + * @author Grzegorz Kossakowski + */ + +package scala.tools.partest.instrumented + +import scala.collection.JavaConverters._ + +case class MethodCallTrace(className: String, methodName: String, methodDescriptor: String) { + override def toString(): String = className + "." + methodName + methodDescriptor +} +object MethodCallTrace { + implicit val ordering: Ordering[MethodCallTrace] = Ordering.by(x => (x.className, x.methodName, x.methodDescriptor)) +} + +/** + * An object that controls profiling of instrumented byte-code. The instrumentation is achieved + * by using `java.lang.instrument` package. The instrumentation agent can be found in + * `scala.tools.partest.javaagent` package. + * + * At the moment the following classes are being instrumented: + * * all classes with empty package + * * all classes from scala package (except for classes responsible for instrumentation) + * + * The canonical way of using instrumentation is have a test-case in `files/instrumented` directory. + * The following code in main: + * + * {{{ + * import scala.tools.partest.instrumented.Instrumentation._ + * def main(args: Array[String]): Unit = { + * startProfiling() + * // should box the boolean + println(true) + stopProfiling() + printStatistics() + * } + * }}} + * + * + * should print: + * + * {{{ + * true + * Method call statistics: + * scala/Predef$.println(Ljava/lang/Object;)V: 1 + * scala/runtime/BoxesRunTime.boxToBoolean(Z)Ljava/lang/Boolean;: 1 + * }}} + */ +object Instrumentation { + + type Statistics = Map[MethodCallTrace, Int] + + def startProfiling(): Unit = Profiler.startProfiling() + def stopProfiling(): Unit = Profiler.stopProfiling() + def resetProfiling(): Unit = Profiler.resetProfiling() + + def getStatistics: Statistics = { + Profiler.stopProfiling() + val stats = Profiler.getStatistics().asScala.toSeq.map { + case (trace, count) => MethodCallTrace(trace.className, trace.methodName, trace.methodDescriptor) -> count.intValue + } + val res = Map(stats: _*) + Profiler.startProfiling() + res + } + + val standardFilter: MethodCallTrace => Boolean = t => { + // ignore all calls to Console trigger by printing + t.className != "scala/Console$" && + // console accesses DynamicVariable, let's discard it too + !t.className.startsWith("scala/util/DynamicVariable") + } + + def printStatistics(stats: Statistics = getStatistics, filter: MethodCallTrace => Boolean = standardFilter): Unit = { + val stats = getStatistics + println("Method call statistics:") + val toBePrinted = stats.toSeq.filter(p => filter(p._1)).sortBy(_._1) + // <count> <trace> + val format = "%5d %s\n" + toBePrinted foreach { + case (trace, count) => printf(format, count, trace) + } + } + +} diff --git a/src/partest/scala/tools/partest/instrumented/Profiler.java b/src/partest/scala/tools/partest/instrumented/Profiler.java new file mode 100644 index 0000000000..215bdbba08 --- /dev/null +++ b/src/partest/scala/tools/partest/instrumented/Profiler.java @@ -0,0 +1,78 @@ +/* NEST (New Scala Test) + * Copyright 2007-2012 LAMP/EPFL + * @author Grzegorz Kossakowski + */ + +package scala.tools.partest.instrumented; + +import java.util.HashMap; +import java.util.Map; + +/** + * A simple profiler class that counts method invocations. It is being used in byte-code instrumentation by inserting + * call to {@link Profiler#methodCalled(String, String, String)} at the beginning of every instrumented class. + * + * WARANING: This class is INTERNAL implementation detail and should never be used directly. It's made public only + * because it must be universally accessible for instrumentation needs. If you want to profile your test use + * {@link Instrumentation} instead. + */ +public class Profiler { + + private static boolean isProfiling = false; + private static Map<MethodCallTrace, Integer> counts = new HashMap<MethodCallTrace, Integer>(); + + static public class MethodCallTrace { + final String className; + final String methodName; + final String methodDescriptor; + + public MethodCallTrace(final String className, final String methodName, final String methodDescriptor) { + this.className = className; + this.methodName = methodName; + this.methodDescriptor = methodDescriptor; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MethodCallTrace)) { + return false; + } else { + MethodCallTrace that = (MethodCallTrace) obj; + return that.className.equals(className) && that.methodName.equals(methodName) && that.methodDescriptor.equals(methodDescriptor); + } + } + @Override + public int hashCode() { + return className.hashCode() ^ methodName.hashCode() ^ methodDescriptor.hashCode(); + } + } + + public static void startProfiling() { + isProfiling = true; + } + + public static void stopProfiling() { + isProfiling = false; + } + + public static void resetProfiling() { + counts = new HashMap<MethodCallTrace, Integer>(); + } + + public static void methodCalled(final String className, final String methodName, final String methodDescriptor) { + if (isProfiling) { + MethodCallTrace trace = new MethodCallTrace(className, methodName, methodDescriptor); + Integer counter = counts.get(trace); + if (counter == null) { + counts.put(trace, 1); + } else { + counts.put(trace, counter+1); + } + } + } + + public static Map<MethodCallTrace, Integer> getStatistics() { + return new HashMap<MethodCallTrace, Integer>(counts); + } + +} diff --git a/src/partest/scala/tools/partest/javaagent/ASMTransformer.java b/src/partest/scala/tools/partest/javaagent/ASMTransformer.java new file mode 100644 index 0000000000..643c683002 --- /dev/null +++ b/src/partest/scala/tools/partest/javaagent/ASMTransformer.java @@ -0,0 +1,38 @@ +/* NEST (New Scala Test) + * Copyright 2007-2012 LAMP/EPFL + * @author Grzegorz Kossakowski + */ + +package scala.tools.partest.javaagent; + +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; + +import scala.tools.asm.ClassReader; +import scala.tools.asm.ClassWriter; + +public class ASMTransformer implements ClassFileTransformer { + + private boolean shouldTransform(String className) { + return + // do not instrument instrumentation logic (in order to avoid infinite recursion) + !className.startsWith("scala/tools/partest/instrumented/") && + !className.startsWith("scala/tools/partest/javaagent/") && + // we instrument all classes from empty package + (!className.contains("/") || + // we instrument all classes from scala package + className.startsWith("scala/")); + } + + public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (shouldTransform(className)) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + ProfilerVisitor visitor = new ProfilerVisitor(writer); + ClassReader reader = new ClassReader(classfileBuffer); + reader.accept(visitor, 0); + return writer.toByteArray(); + } else { + return classfileBuffer; + } + } +} diff --git a/src/partest/scala/tools/partest/javaagent/MANIFEST.MF b/src/partest/scala/tools/partest/javaagent/MANIFEST.MF new file mode 100644 index 0000000000..be0fee46a2 --- /dev/null +++ b/src/partest/scala/tools/partest/javaagent/MANIFEST.MF @@ -0,0 +1 @@ +Premain-Class: scala.tools.partest.javaagent.ProfilingAgent diff --git a/src/partest/scala/tools/partest/javaagent/ProfilerVisitor.java b/src/partest/scala/tools/partest/javaagent/ProfilerVisitor.java new file mode 100644 index 0000000000..f3a25e87d9 --- /dev/null +++ b/src/partest/scala/tools/partest/javaagent/ProfilerVisitor.java @@ -0,0 +1,46 @@ +/* NEST (New Scala Test) + * Copyright 2007-2012 LAMP/EPFL + * @author Grzegorz Kossakowski + */ + +package scala.tools.partest.javaagent; + +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +public class ProfilerVisitor extends ClassVisitor implements Opcodes { + + private static String profilerClass = "scala/tools/partest/instrumented/Profiler"; + + public ProfilerVisitor(final ClassVisitor cv) { + super(ASM4, cv); + } + + private String className = null; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + className = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // delegate the method call to the next + // chained visitor + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + if (!profilerClass.equals(className)) { + // only instrument non-abstract methods + if((access & ACC_ABSTRACT) == 0) { + assert(className != null); + mv.visitLdcInsn(className); + mv.visitLdcInsn(name); + mv.visitLdcInsn(desc); + mv.visitMethodInsn(INVOKESTATIC, profilerClass, "methodCalled", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + } + } + return mv; + } + +} diff --git a/src/partest/scala/tools/partest/javaagent/ProfilingAgent.java b/src/partest/scala/tools/partest/javaagent/ProfilingAgent.java new file mode 100644 index 0000000000..15efd73d94 --- /dev/null +++ b/src/partest/scala/tools/partest/javaagent/ProfilingAgent.java @@ -0,0 +1,25 @@ +/* NEST (New Scala Test) + * Copyright 2007-2012 LAMP/EPFL + * @author Grzegorz Kossakowski + */ + +package scala.tools.partest.javaagent; + +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; + +/** + * Profiling agent that instruments byte-code to insert calls to + * {@link scala.tools.partest.instrumented.Profiler#methodCalled(String, String, String)} + * by using ASM library for byte-code manipulation. + */ +public class ProfilingAgent { + public static void premain(String args, Instrumentation inst) throws UnmodifiableClassException { + // NOTE: we are adding transformer that won't be applied to classes that are already loaded + // This should be ok because premain should be executed before main is executed so Scala library + // and the test-case itself won't be loaded yet. We rely here on the fact that ASMTransformer does + // not depend on Scala library. In case our assumptions are wrong we can always insert call to + // inst.retransformClasses. + inst.addTransformer(new ASMTransformer(), true); + } +} diff --git a/src/partest/scala/tools/partest/nest/CompileManager.scala b/src/partest/scala/tools/partest/nest/CompileManager.scala index c674e21482..6e6c767117 100644 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ b/src/partest/scala/tools/partest/nest/CompileManager.scala @@ -112,6 +112,7 @@ class DirectCompiler(val fileManager: FileManager) extends SimpleCompiler { case "scalap" => ScalapTestFile.apply case "scalacheck" => ScalaCheckTestFile.apply case "specialized" => SpecializedTestFile.apply + case "instrumented" => InstrumentedTestFile.apply case "presentation" => PresentationTestFile.apply case "ant" => AntTestFile.apply } diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala index 962520844c..20e6f47802 100644 --- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala @@ -40,6 +40,7 @@ class ConsoleRunner extends DirectRunner { TestSet("scalacheck", stdFilter, "Testing ScalaCheck tests"), TestSet("scalap", _.isDirectory, "Run scalap decompiler tests"), TestSet("specialized", stdFilter, "Testing specialized tests"), + TestSet("instrumented", stdFilter, "Testing instrumented tests"), TestSet("presentation", _.isDirectory, "Testing presentation compiler tests."), TestSet("ant", antFilter, "Run Ant task tests.") ) diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala index 2bb373d9c5..6c239721c3 100644 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ b/src/partest/scala/tools/partest/nest/DirectRunner.scala @@ -59,7 +59,8 @@ trait DirectRunner { val futures = kindFiles map (f => (f, pool submit callable(manager runTest f))) toMap pool.shutdown() - pool.awaitTermination(1, TimeUnit.HOURS) + if (!pool.awaitTermination(4, TimeUnit.HOURS)) + NestUI.warning("Thread pool timeout elapsed before all tests were complete!") for ((file, future) <- futures) yield { val state = if (future.isCancelled) TestState.Timeout else future.get diff --git a/src/partest/scala/tools/partest/nest/NestUI.scala b/src/partest/scala/tools/partest/nest/NestUI.scala index 7fd503e769..54e99a7ddf 100644 --- a/src/partest/scala/tools/partest/nest/NestUI.scala +++ b/src/partest/scala/tools/partest/nest/NestUI.scala @@ -80,6 +80,7 @@ object NestUI { println(" --scalacheck run ScalaCheck tests") println(" --script run script runner tests") println(" --shootout run shootout tests") + println(" --instrumented run instrumented tests") println(" --presentation run presentation compiler tests") println(" --grep <expr> run all tests whose source file contains <expr>") println diff --git a/src/partest/scala/tools/partest/nest/PathSettings.scala b/src/partest/scala/tools/partest/nest/PathSettings.scala index ac04c64c33..b409a29165 100644 --- a/src/partest/scala/tools/partest/nest/PathSettings.scala +++ b/src/partest/scala/tools/partest/nest/PathSettings.scala @@ -49,6 +49,12 @@ object PathSettings { getOrElse sys.error("No code.jar found in %s".format(srcCodeLibDir)) ) + lazy val instrumentationAgentLib: File = { + findJar(buildPackLibDir.files, "scala-partest-javaagent") getOrElse { + sys.error("No partest-javaagent jar found in '%s' or '%s'".format(buildPackLibDir, srcLibDir)) + } + } + // Directory <root>/build lazy val buildDir: Directory = { val bases = testRoot :: testRoot.parents diff --git a/src/partest/scala/tools/partest/nest/RunnerManager.scala b/src/partest/scala/tools/partest/nest/RunnerManager.scala index feca40a159..dc15d4475b 100644 --- a/src/partest/scala/tools/partest/nest/RunnerManager.scala +++ b/src/partest/scala/tools/partest/nest/RunnerManager.scala @@ -185,7 +185,7 @@ class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunP outDir } - private def execTest(outDir: File, logFile: File, classpathPrefix: String = ""): Boolean = { + private def execTest(outDir: File, logFile: File, classpathPrefix: String = "", javaOpts: String = ""): Boolean = { // check whether there is a ".javaopts" file val argsFile = new File(logFile.getParentFile, fileBase + ".javaopts") val argString = file2String(argsFile) @@ -228,7 +228,7 @@ class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunP val classpath = if (classpathPrefix != "") join(classpathPrefix, CLASSPATH) else CLASSPATH val cmd = javaCmd +: ( - (JAVA_OPTS.split(' ') ++ argString.split(' ')).map(_.trim).filter(_ != "") ++ Seq( + (JAVA_OPTS.split(' ') ++ javaOpts.split(' ') ++ argString.split(' ')).map(_.trim).filter(_ != "") ++ Seq( "-classpath", join(outDir.toString, classpath) ) ++ propertyOptions ++ Seq( @@ -426,6 +426,15 @@ class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunP ) }) + def runInstrumentedTest(file: File): (Boolean, LogContext) = + runTestCommon(file, expectFailure = false)((logFile, outDir) => { + val dir = file.getParentFile + + // adding the javagent option with path to instrumentation agent + execTest(outDir, logFile, javaOpts = "-javaagent:"+PathSettings.instrumentationAgentLib) && + diffCheck(file, compareOutput(dir, logFile)) + }) + def processSingleFile(file: File): (Boolean, LogContext) = kind match { case "scalacheck" => val succFn: (File, File) => Boolean = { (logFile, outDir) => @@ -475,6 +484,9 @@ class RunnerManager(kind: String, val fileManager: FileManager, params: TestRunP case "specialized" => runSpecializedTest(file) + case "instrumented" => + runInstrumentedTest(file) + case "presentation" => runJvmTest(file) // for the moment, it's exactly the same as for a run test diff --git a/src/partest/scala/tools/partest/nest/TestFile.scala b/src/partest/scala/tools/partest/nest/TestFile.scala index 1aa0a7baf6..52af16274e 100644 --- a/src/partest/scala/tools/partest/nest/TestFile.scala +++ b/src/partest/scala/tools/partest/nest/TestFile.scala @@ -78,3 +78,4 @@ case class SpecializedTestFile(file: JFile, fileManager: FileManager) extends Te } case class PresentationTestFile(file: JFile, fileManager: FileManager) extends TestFile("presentation") case class AntTestFile(file: JFile, fileManager: FileManager) extends TestFile("ant") +case class InstrumentedTestFile(file: JFile, fileManager: FileManager) extends TestFile("instrumented") diff --git a/src/reflect/scala/reflect/internal/Constants.scala b/src/reflect/scala/reflect/internal/Constants.scala index 820dfe0868..d6a168ee11 100644 --- a/src/reflect/scala/reflect/internal/Constants.scala +++ b/src/reflect/scala/reflect/internal/Constants.scala @@ -221,7 +221,12 @@ trait Constants extends api.Constants { tag match { case NullTag => "null" case StringTag => "\"" + escape(stringValue) + "\"" - case ClazzTag => "classOf[" + signature(typeValue) + "]" + case ClazzTag => + def show(tpe: Type) = "classOf[" + signature(tpe) + "]" + typeValue match { + case ErasedValueType(orig) => show(orig) + case _ => show(typeValue) + } case CharTag => "'" + escapedChar(charValue) + "'" case LongTag => longValue.toString() + "L" case EnumTag => symbolValue.name.toString() diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index e5d7f67e4c..dced794771 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -100,13 +100,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => case _ => false } - (tp <:< pt) || isCompatibleByName(tp, pt) + (tp weak_<:< pt) || isCompatibleByName(tp, pt) } def signatureAsSpecific(method1: MethodSymbol, method2: MethodSymbol): Boolean = { (substituteTypeParams(method1), substituteTypeParams(method2)) match { case (NullaryMethodType(r1), NullaryMethodType(r2)) => - r1 <:< r2 + r1 weak_<:< r2 case (NullaryMethodType(_), MethodType(_, _)) => true case (MethodType(_, _), NullaryMethodType(_)) => @@ -298,7 +298,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => else { val a = argTypes val p = extend(paramTypes, argTypes.length) - (a corresponds p)(_ <:< _) + (a corresponds p)(_ weak_<:< _) } } } diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 745065b024..7ba749ed2c 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -442,6 +442,9 @@ abstract class TreeInfo { } */ + /** Is this case guarded? */ + def isGuardedCase(cdef: CaseDef) = cdef.guard != EmptyTree + /** Is this pattern node a sequence-valued pattern? */ def isSequenceValued(tree: Tree): Boolean = unbind(tree) match { case Alternative(ts) => ts exists isSequenceValued diff --git a/test/files/instrumented/InstrumentationTest.check b/test/files/instrumented/InstrumentationTest.check new file mode 100644 index 0000000000..3652df270a --- /dev/null +++ b/test/files/instrumented/InstrumentationTest.check @@ -0,0 +1,4 @@ +true +Method call statistics: + 1 scala/Predef$.println(Ljava/lang/Object;)V + 1 scala/runtime/BoxesRunTime.boxToBoolean(Z)Ljava/lang/Boolean; diff --git a/test/files/instrumented/InstrumentationTest.scala b/test/files/instrumented/InstrumentationTest.scala new file mode 100644 index 0000000000..ec5314c624 --- /dev/null +++ b/test/files/instrumented/InstrumentationTest.scala @@ -0,0 +1,14 @@ +import scala.tools.partest.instrumented.Instrumentation._ + +/** Tests if instrumentation itself works correctly */ +object Test { + def main(args: Array[String]) { + // force predef initialization before profiling + Predef + startProfiling() + // should box the boolean + println(true) + stopProfiling() + printStatistics() + } +} diff --git a/test/files/instrumented/README b/test/files/instrumented/README new file mode 100644 index 0000000000..32d0ef2da5 --- /dev/null +++ b/test/files/instrumented/README @@ -0,0 +1,15 @@ +Tests in `instrumented` directory are executed the same way as in `run` but +they have additional byte-code instrumentation performed for profiling. You +should put your tests in `instrumented` directory if you are interested in +method call counts. Examples include tests for specialization (you want to +count boxing and unboxing method calls) or high-level tests for optimizer +where you are interested if methods are successfuly inlined (so they should +not be called at runtime) or closures are eliminated (so no constructors +of closures are called). + +Check `scala.tools.partest.instrumented.Instrumentation` to learn how to +use the instrumentation infrastructure. + +The instrumentation itself is achieved by attaching a Java agent to the forked +VM process that injects calls to profiler. Check +`scala.tools.partest.instrumented.Instrumentation`. diff --git a/test/files/neg/t1286.check b/test/files/neg/t1286.check index c937fb9cf1..912709613c 100644 --- a/test/files/neg/t1286.check +++ b/test/files/neg/t1286.check @@ -1,9 +1,5 @@ -a.scala:1: error: Companions 'object Foo' and 'trait Foo' must be defined in same file: - Found in t1286/b.scala and t1286/a.scala -trait Foo { - ^ b.scala:1: error: Companions 'trait Foo' and 'object Foo' must be defined in same file: Found in t1286/a.scala and t1286/b.scala object Foo extends Foo { ^ -two errors found +one error found diff --git a/test/files/neg/t5830.check b/test/files/neg/t5830.check index 85cb84378f..726fac2a1e 100644 --- a/test/files/neg/t5830.check +++ b/test/files/neg/t5830.check @@ -1,4 +1,7 @@ t5830.scala:6: error: unreachable code case 'a' => println("b") // unreachable ^ -one error found +t5830.scala:4: error: could not emit switch for @switch annotated match + def unreachable(ch: Char) = (ch: @switch) match { + ^ +two errors found diff --git a/test/files/neg/t5956.check b/test/files/neg/t5956.check new file mode 100644 index 0000000000..6641dac97f --- /dev/null +++ b/test/files/neg/t5956.check @@ -0,0 +1,20 @@ +t5956.scala:1: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object O { case class C[T]; class C } + ^ +t5956.scala:2: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object T { case class C[T]; case class C } + ^ +t5956.scala:2: warning: case classes without a parameter list have been deprecated; +use either case objects or case classes with `()' as parameter list. +object T { case class C[T]; case class C } + ^ +t5956.scala:1: error: C is already defined as case class C +object O { case class C[T]; class C } + ^ +t5956.scala:2: error: C is already defined as case class C +object T { case class C[T]; case class C } + ^ +three warnings found +two errors found diff --git a/test/files/neg/t5956.scala b/test/files/neg/t5956.scala new file mode 100644 index 0000000000..d985fa97a4 --- /dev/null +++ b/test/files/neg/t5956.scala @@ -0,0 +1,2 @@ +object O { case class C[T]; class C } +object T { case class C[T]; case class C } diff --git a/test/files/neg/t6011.check b/test/files/neg/t6011.check new file mode 100644 index 0000000000..5b5a861e5b --- /dev/null +++ b/test/files/neg/t6011.check @@ -0,0 +1,10 @@ +t6011.scala:4: error: unreachable code + case 'a' | 'c' => 1 // unreachable + ^ +t6011.scala:10: error: unreachable code + case 'b' | 'a' => 1 // unreachable + ^ +t6011.scala:8: error: could not emit switch for @switch annotated match + def f2(ch: Char): Any = (ch: @annotation.switch) match { + ^ +three errors found diff --git a/test/files/neg/t6011.flags b/test/files/neg/t6011.flags new file mode 100644 index 0000000000..e8fb65d50c --- /dev/null +++ b/test/files/neg/t6011.flags @@ -0,0 +1 @@ +-Xfatal-warnings
\ No newline at end of file diff --git a/test/files/neg/t6011.scala b/test/files/neg/t6011.scala new file mode 100644 index 0000000000..a36cca7897 --- /dev/null +++ b/test/files/neg/t6011.scala @@ -0,0 +1,23 @@ +object Test { + def f(ch: Char): Any = ch match { + case 'a' => 1 + case 'a' | 'c' => 1 // unreachable + } + + // won't be compiled to a switch since it has an unreachable (duplicate) case + def f2(ch: Char): Any = (ch: @annotation.switch) match { + case 'a' | 'b' => 1 + case 'b' | 'a' => 1 // unreachable + case _ => + } + + // s'all good + def f3(ch: Char): Any = (ch: @annotation.switch) match { + case 'a' | 'b' if (true: Boolean) => 1 + case 'b' | 'a' => 1 // ok + case _ => // need third case to check switch annotation (two-case switches are always okay to compile to if-then-else) + } + + + def main(args: Array[String]): Unit = f('a') +}
\ No newline at end of file diff --git a/test/files/neg/t6048.check b/test/files/neg/t6048.check new file mode 100644 index 0000000000..051f41877e --- /dev/null +++ b/test/files/neg/t6048.check @@ -0,0 +1,10 @@ +t6048.scala:3: error: unreachable code + case _ if false => x // unreachable + ^ +t6048.scala:8: error: unreachable code + case _ if false => x // unreachable + ^ +t6048.scala:14: error: unreachable code + case 5 if true => x // unreachable + ^ +three errors found diff --git a/test/files/neg/t6048.flags b/test/files/neg/t6048.flags new file mode 100644 index 0000000000..e8fb65d50c --- /dev/null +++ b/test/files/neg/t6048.flags @@ -0,0 +1 @@ +-Xfatal-warnings
\ No newline at end of file diff --git a/test/files/neg/t6048.scala b/test/files/neg/t6048.scala new file mode 100644 index 0000000000..803e651d19 --- /dev/null +++ b/test/files/neg/t6048.scala @@ -0,0 +1,22 @@ +class A { + def f1(x: Int) = x match { + case _ if false => x // unreachable + case 5 => x + } + + def f2(x: Int) = x match { + case _ if false => x // unreachable + case 5 if true => x + } + + def f3(x: Int) = x match { + case _ => x + case 5 if true => x // unreachable + } + + def test1(x: Int) = x match { + case c if c < 0 => 0 + case 1 => 1 + case _ => 2 + } +} diff --git a/test/files/pos/t6028/t6028_1.scala b/test/files/pos/t6028/t6028_1.scala new file mode 100644 index 0000000000..6edb76069e --- /dev/null +++ b/test/files/pos/t6028/t6028_1.scala @@ -0,0 +1,3 @@ +class C { + def foo(a: Int): Unit = () => a +} diff --git a/test/files/pos/t6028/t6028_2.scala b/test/files/pos/t6028/t6028_2.scala new file mode 100644 index 0000000000..f44048c0ab --- /dev/null +++ b/test/files/pos/t6028/t6028_2.scala @@ -0,0 +1,4 @@ +object Test { + // ensure that parameter names are untouched by lambdalift + new C().foo(a = 0) +} diff --git a/test/files/run/reflect-resolveoverload-invalid.scala b/test/files/run/reflect-resolveoverload-invalid.scala index def28ccbb4..8c5dc9f94b 100644 --- a/test/files/run/reflect-resolveoverload-invalid.scala +++ b/test/files/run/reflect-resolveoverload-invalid.scala @@ -27,7 +27,7 @@ object Test extends App { val d = t member u.newTermName("d") asTermSymbol val e = t member u.newTermName("e") asTermSymbol - val n1 = a.resolveOverloaded(posVargs = List(u.typeOf[Char])) + val n1 = a.resolveOverloaded(posVargs = List(u.typeOf[Long])) val n2 = b.resolveOverloaded(posVargs = List(u.typeOf[A])) val n3 = c.resolveOverloaded(posVargs = List(u.typeOf[B], u.typeOf[B])) val n4 = d.resolveOverloaded(targs = List(u.typeOf[Int])) diff --git a/test/files/run/reflect-resolveoverload2.scala b/test/files/run/reflect-resolveoverload2.scala index b5f719814b..a800a3e92c 100644 --- a/test/files/run/reflect-resolveoverload2.scala +++ b/test/files/run/reflect-resolveoverload2.scala @@ -2,16 +2,20 @@ class A class B extends A class C { - def a(x: Int) = 1 - def a(x: String) = 2 - //def b(x: => Int)(s: String) = 1 - //def b(x: => String)(a: Array[_]) = 2 - def c(x: A) = 1 - def c(x: B) = 2 - //def d(x: => A)(s: String) = 1 - //def d(x: => B)(a: Array[_]) = 2 - def e(x: A) = 1 - def e(x: B = new B) = 2 + def a(x: Int) = 1 + def a(x: String) = 2 + //def b(x: => Int)(s: String) = 1 + //def b(x: => String)(a: Array[_]) = 2 + def c(x: A) = 1 + def c(x: B) = 2 + //def d(x: => A)(s: String) = 1 + //def d(x: => B)(a: Array[_]) = 2 + def e(x: A) = 1 + def e(x: B = new B) = 2 + def f(x: Int) = 1 + def f(x: String) = 2 + def f(x: Long) = 3 + def f(x: Double) = 4 } object Test extends App { @@ -29,6 +33,8 @@ object Test extends App { } assert(c.a(1) == invoke("a", 1, u.typeOf[Int])) assert(c.a("a") == invoke("a", "a", u.typeOf[String])) + assert(c.a('a') == invoke("a", 'a', u.typeOf[Char])) + assert(c.a(3: Byte) == invoke("a", 3: Byte, u.typeOf[Byte])) //assert(c.b(1)(null) == invoke("b", 1, u.typeOf[Int])) //assert(c.b("a")(null) == invoke("b", "a", u.typeOf[String])) assert(c.c(new A) == invoke("c", new A, u.typeOf[A])) @@ -37,4 +43,9 @@ object Test extends App { //assert(c.d(new B)(null) == invoke("d", new B, u.typeOf[B])) assert(c.e(new A) == invoke("e", new A, u.typeOf[A])) assert(c.e(new B) == invoke("e", new B, u.typeOf[B])) + assert(c.f(1: Short) == invoke("f", 1: Short, u.typeOf[Short])) + assert(c.f(2) == invoke("f", 2, u.typeOf[Int])) + assert(c.f(3L) == invoke("f", 3L, u.typeOf[Long])) + assert(c.f(4f) == invoke("f", 4f, u.typeOf[Float])) + assert(c.f(5d) == invoke("f", 5d, u.typeOf[Double])) } diff --git a/test/files/run/t5588.check b/test/files/run/t5588.check new file mode 100644 index 0000000000..bb101b641b --- /dev/null +++ b/test/files/run/t5588.check @@ -0,0 +1,2 @@ +true +true diff --git a/test/files/run/t5588.scala b/test/files/run/t5588.scala new file mode 100644 index 0000000000..f214d16684 --- /dev/null +++ b/test/files/run/t5588.scala @@ -0,0 +1,14 @@ +object Test { + object MyEnum extends Enumeration { + val Foo = Value(2000000000) + val Bar = Value(-2000000000) + val X = Value(Integer.MAX_VALUE) + val Y = Value(Integer.MIN_VALUE) + } + + import MyEnum._ + def main(args: Array[String]) { + println(Foo > Bar) + println(X > Y) + } +} diff --git a/test/files/run/t6011b.check b/test/files/run/t6011b.check new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/test/files/run/t6011b.check @@ -0,0 +1 @@ +3 diff --git a/test/files/run/t6011b.scala b/test/files/run/t6011b.scala new file mode 100644 index 0000000000..3d405e0705 --- /dev/null +++ b/test/files/run/t6011b.scala @@ -0,0 +1,11 @@ +object Test extends App { + var cond = true + + // should not generate a switch + def f(ch: Char): Int = ch match { + case 'a' if cond => 1 + case 'z' | 'a' => 2 + } + + println(f('a') + f('z')) // 3 +}
\ No newline at end of file diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check new file mode 100644 index 0000000000..dca61115ad --- /dev/null +++ b/test/files/run/t6028.check @@ -0,0 +1,84 @@ +[[syntax trees at end of lambdalift]] // newSource1 +package <empty> { + class T extends Object { + <paramaccessor> val T$$classParam: Int = _; + def <init>(classParam: Int): T = { + T.super.<init>(); + () + }; + private[this] val field: Int = 0; + <stable> <accessor> def field(): Int = T.this.field; + def foo(methodParam: Int): Function0 = { + val methodLocal: Int = 0; + { + (new anonymous class $anonfun$foo$1(T.this, methodParam, methodLocal): Function0) + } + }; + def bar(barParam: Int): Object = { + @volatile var MethodLocalObject$module: scala.runtime.VolatileObjectRef = new scala.runtime.VolatileObjectRef(<empty>); + T.this.MethodLocalObject$1(barParam, MethodLocalObject$module) + }; + def tryy(tryyParam: Int): Function0 = { + var tryyLocal: scala.runtime.IntRef = new scala.runtime.IntRef(0); + { + (new anonymous class $anonfun$tryy$1(T.this, tryyParam, tryyLocal): Function0) + } + }; + @SerialVersionUID(0) final <synthetic> class $anonfun$foo$1 extends scala.runtime.AbstractFunction0$mcI$sp with Serializable { + def <init>($outer: T, methodParam$1: Int, methodLocal$1: Int): anonymous class $anonfun$foo$1 = { + $anonfun$foo$1.super.<init>(); + () + }; + final def apply(): Int = $anonfun$foo$1.this.apply$mcI$sp(); + <specialized> def apply$mcI$sp(): Int = $anonfun$foo$1.this.$outer.T$$classParam.+($anonfun$foo$1.this.$outer.field()).+($anonfun$foo$1.this.methodParam$1).+($anonfun$foo$1.this.methodLocal$1); + <synthetic> <paramaccessor> private[this] val $outer: T = _; + <synthetic> <stable> def T$$anonfun$$$outer(): T = $anonfun$foo$1.this.$outer; + final <bridge> def apply(): Object = scala.Int.box($anonfun$foo$1.this.apply()); + <synthetic> <paramaccessor> private[this] val methodParam$1: Int = _; + <synthetic> <paramaccessor> private[this] val methodLocal$1: Int = _ + }; + abstract trait MethodLocalTrait$1 extends Object { + <synthetic> <stable> def T$MethodLocalTrait$$$outer(): T + }; + object MethodLocalObject$2 extends Object with T#MethodLocalTrait$1 { + def <init>($outer: T, barParam$1: Int): ... = { + MethodLocalObject$2.super.<init>(); + MethodLocalObject$2.this.$asInstanceOf[T#MethodLocalTrait$1$class]()./*MethodLocalTrait$1$class*/$init$(barParam$1); + () + }; + <synthetic> <paramaccessor> private[this] val $outer: T = _; + <synthetic> <stable> def T$MethodLocalObject$$$outer(): T = MethodLocalObject$2.this.$outer; + <synthetic> <stable> def T$MethodLocalTrait$$$outer(): T = MethodLocalObject$2.this.$outer + }; + final <stable> private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: scala.runtime.VolatileObjectRef): ... = { + MethodLocalObject$module$1.elem = new ...(T.this, barParam$1); + MethodLocalObject$module$1.elem.$asInstanceOf[...]() + }; + abstract trait MethodLocalTrait$1$class extends Object with T#MethodLocalTrait$1 { + def /*MethodLocalTrait$1$class*/$init$(barParam$1: Int): Unit = { + () + }; + scala.this.Predef.print(scala.Int.box(barParam$1)) + }; + @SerialVersionUID(0) final <synthetic> class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { + def <init>($outer: T, tryyParam$1: Int, tryyLocal$1: scala.runtime.IntRef): anonymous class $anonfun$tryy$1 = { + $anonfun$tryy$1.super.<init>(); + () + }; + final def apply(): Unit = $anonfun$tryy$1.this.apply$mcV$sp(); + <specialized> def apply$mcV$sp(): Unit = try { + $anonfun$tryy$1.this.tryyLocal$1.elem = $anonfun$tryy$1.this.tryyParam$1 + } finally (); + <synthetic> <paramaccessor> private[this] val $outer: T = _; + <synthetic> <stable> def T$$anonfun$$$outer(): T = $anonfun$tryy$1.this.$outer; + final <bridge> def apply(): Object = { + $anonfun$tryy$1.this.apply(); + scala.runtime.BoxedUnit.UNIT + }; + <synthetic> <paramaccessor> private[this] val tryyParam$1: Int = _; + <synthetic> <paramaccessor> private[this] val tryyLocal$1: scala.runtime.IntRef = _ + } + } +} + +warning: there were 1 feature warnings; re-run with -feature for details diff --git a/test/files/run/t6028.scala b/test/files/run/t6028.scala new file mode 100644 index 0000000000..cab17535fc --- /dev/null +++ b/test/files/run/t6028.scala @@ -0,0 +1,21 @@ +import scala.tools.partest._ +import java.io.{Console => _, _} + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Xprint:lambdalift -d " + testOutput.path + + override def code = """class T(classParam: Int) { + | val field: Int = 0 + | def foo(methodParam: Int) = {val methodLocal = 0 ; () => classParam + field + methodParam + methodLocal } + | def bar(barParam: Int) = { trait MethodLocalTrait { print(barParam) }; object MethodLocalObject extends MethodLocalTrait; MethodLocalObject } + | def tryy(tryyParam: Int) = { var tryyLocal = 0; () => try { tryyLocal = tryyParam } finally () } + |} + |""".stripMargin.trim + + override def show(): Unit = { + Console.withErr(System.out) { + compile() + } + } +} diff --git a/test/files/run/t6077_patmat_cse_irrefutable.check b/test/files/run/t6077_patmat_cse_irrefutable.check new file mode 100644 index 0000000000..9766475a41 --- /dev/null +++ b/test/files/run/t6077_patmat_cse_irrefutable.check @@ -0,0 +1 @@ +ok diff --git a/test/files/run/t6077_patmat_cse_irrefutable.scala b/test/files/run/t6077_patmat_cse_irrefutable.scala new file mode 100644 index 0000000000..b130ae7813 --- /dev/null +++ b/test/files/run/t6077_patmat_cse_irrefutable.scala @@ -0,0 +1,13 @@ +class LiteralNode(val value: Any) + +object LiteralNode { + // irrefutable + def unapply(n: LiteralNode) = Some(n.value) +} + +object Test extends App { + ((new LiteralNode(false)): Any) match { + case LiteralNode(true) => println("uh-oh") + case LiteralNode(false) => println("ok") + } +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classmanifest-basic.check b/test/files/run/valueclasses-classmanifest-basic.check new file mode 100644 index 0000000000..554c75e074 --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-basic.check @@ -0,0 +1 @@ +Foo
diff --git a/test/files/run/valueclasses-classmanifest-basic.scala b/test/files/run/valueclasses-classmanifest-basic.scala new file mode 100644 index 0000000000..c2aa08ef86 --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-basic.scala @@ -0,0 +1,5 @@ +class Foo(val x: Int) extends AnyVal + +object Test extends App { + println(classManifest[Foo]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classmanifest-existential.check b/test/files/run/valueclasses-classmanifest-existential.check new file mode 100644 index 0000000000..e9fc6e27ea --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-existential.check @@ -0,0 +1 @@ +Foo[<?>]
diff --git a/test/files/run/valueclasses-classmanifest-existential.scala b/test/files/run/valueclasses-classmanifest-existential.scala new file mode 100644 index 0000000000..11999df678 --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-existential.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(classManifest[Foo[_]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classmanifest-generic.check b/test/files/run/valueclasses-classmanifest-generic.check new file mode 100644 index 0000000000..1418c5cff9 --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-generic.check @@ -0,0 +1 @@ +Foo[java.lang.String]
diff --git a/test/files/run/valueclasses-classmanifest-generic.scala b/test/files/run/valueclasses-classmanifest-generic.scala new file mode 100644 index 0000000000..280152dc1d --- /dev/null +++ b/test/files/run/valueclasses-classmanifest-generic.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(classManifest[Foo[String]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classtag-basic.check b/test/files/run/valueclasses-classtag-basic.check new file mode 100644 index 0000000000..0c13986b32 --- /dev/null +++ b/test/files/run/valueclasses-classtag-basic.check @@ -0,0 +1 @@ +ClassTag[class Foo]
diff --git a/test/files/run/valueclasses-classtag-basic.scala b/test/files/run/valueclasses-classtag-basic.scala new file mode 100644 index 0000000000..912a4bb019 --- /dev/null +++ b/test/files/run/valueclasses-classtag-basic.scala @@ -0,0 +1,5 @@ +class Foo(val x: Int) extends AnyVal + +object Test extends App { + println(scala.reflect.classTag[Foo]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classtag-existential.check b/test/files/run/valueclasses-classtag-existential.check new file mode 100644 index 0000000000..95e94e7aee --- /dev/null +++ b/test/files/run/valueclasses-classtag-existential.check @@ -0,0 +1 @@ +ClassTag[class java.lang.Object]
diff --git a/test/files/run/valueclasses-classtag-existential.scala b/test/files/run/valueclasses-classtag-existential.scala new file mode 100644 index 0000000000..e0db9cdd75 --- /dev/null +++ b/test/files/run/valueclasses-classtag-existential.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(scala.reflect.classTag[Foo[_]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-classtag-generic.check b/test/files/run/valueclasses-classtag-generic.check new file mode 100644 index 0000000000..0c13986b32 --- /dev/null +++ b/test/files/run/valueclasses-classtag-generic.check @@ -0,0 +1 @@ +ClassTag[class Foo]
diff --git a/test/files/run/valueclasses-classtag-generic.scala b/test/files/run/valueclasses-classtag-generic.scala new file mode 100644 index 0000000000..bd1f213835 --- /dev/null +++ b/test/files/run/valueclasses-classtag-generic.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(scala.reflect.classTag[Foo[String]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-manifest-basic.check b/test/files/run/valueclasses-manifest-basic.check new file mode 100644 index 0000000000..554c75e074 --- /dev/null +++ b/test/files/run/valueclasses-manifest-basic.check @@ -0,0 +1 @@ +Foo
diff --git a/test/files/run/valueclasses-manifest-basic.scala b/test/files/run/valueclasses-manifest-basic.scala new file mode 100644 index 0000000000..eefab20168 --- /dev/null +++ b/test/files/run/valueclasses-manifest-basic.scala @@ -0,0 +1,5 @@ +class Foo(val x: Int) extends AnyVal + +object Test extends App { + println(manifest[Foo]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-manifest-existential.check b/test/files/run/valueclasses-manifest-existential.check new file mode 100644 index 0000000000..fdce051039 --- /dev/null +++ b/test/files/run/valueclasses-manifest-existential.check @@ -0,0 +1 @@ +Foo[_ <: Any]
diff --git a/test/files/run/valueclasses-manifest-existential.scala b/test/files/run/valueclasses-manifest-existential.scala new file mode 100644 index 0000000000..47eb6d64dd --- /dev/null +++ b/test/files/run/valueclasses-manifest-existential.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(manifest[Foo[_]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-manifest-generic.check b/test/files/run/valueclasses-manifest-generic.check new file mode 100644 index 0000000000..1418c5cff9 --- /dev/null +++ b/test/files/run/valueclasses-manifest-generic.check @@ -0,0 +1 @@ +Foo[java.lang.String]
diff --git a/test/files/run/valueclasses-manifest-generic.scala b/test/files/run/valueclasses-manifest-generic.scala new file mode 100644 index 0000000000..18313fba6f --- /dev/null +++ b/test/files/run/valueclasses-manifest-generic.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(manifest[Foo[String]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-typetag-basic.check b/test/files/run/valueclasses-typetag-basic.check new file mode 100644 index 0000000000..554c75e074 --- /dev/null +++ b/test/files/run/valueclasses-typetag-basic.check @@ -0,0 +1 @@ +Foo
diff --git a/test/files/run/valueclasses-typetag-basic.scala b/test/files/run/valueclasses-typetag-basic.scala new file mode 100644 index 0000000000..d0243f7378 --- /dev/null +++ b/test/files/run/valueclasses-typetag-basic.scala @@ -0,0 +1,5 @@ +class Foo(val x: Int) extends AnyVal + +object Test extends App { + println(scala.reflect.runtime.universe.typeOf[Foo]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-typetag-existential.check b/test/files/run/valueclasses-typetag-existential.check new file mode 100644 index 0000000000..0efa24a45f --- /dev/null +++ b/test/files/run/valueclasses-typetag-existential.check @@ -0,0 +1 @@ +Foo[_]
diff --git a/test/files/run/valueclasses-typetag-existential.scala b/test/files/run/valueclasses-typetag-existential.scala new file mode 100644 index 0000000000..4cdaa44a83 --- /dev/null +++ b/test/files/run/valueclasses-typetag-existential.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(scala.reflect.runtime.universe.typeOf[Foo[_]]) +}
\ No newline at end of file diff --git a/test/files/run/valueclasses-typetag-generic.check b/test/files/run/valueclasses-typetag-generic.check new file mode 100644 index 0000000000..fce2e64f79 --- /dev/null +++ b/test/files/run/valueclasses-typetag-generic.check @@ -0,0 +1 @@ +Foo[String]
diff --git a/test/files/run/valueclasses-typetag-generic.scala b/test/files/run/valueclasses-typetag-generic.scala new file mode 100644 index 0000000000..eb32dfcadb --- /dev/null +++ b/test/files/run/valueclasses-typetag-generic.scala @@ -0,0 +1,5 @@ +class Foo[T](val x: T) extends AnyVal + +object Test extends App { + println(scala.reflect.runtime.universe.typeOf[Foo[String]]) +}
\ No newline at end of file diff --git a/test/files/specialized/t6035.check b/test/files/specialized/t6035.check new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/test/files/specialized/t6035.check @@ -0,0 +1 @@ +0 diff --git a/test/files/specialized/t6035/first_1.scala b/test/files/specialized/t6035/first_1.scala new file mode 100644 index 0000000000..1289e9f48e --- /dev/null +++ b/test/files/specialized/t6035/first_1.scala @@ -0,0 +1,5 @@ +trait Foo[@specialized(Int) A] { + def foo(x: A): A +} + +abstract class Inter extends Foo[Int] diff --git a/test/files/specialized/t6035/second_2.scala b/test/files/specialized/t6035/second_2.scala new file mode 100644 index 0000000000..fb317e2a6a --- /dev/null +++ b/test/files/specialized/t6035/second_2.scala @@ -0,0 +1,13 @@ +class Baz extends Inter { + def foo(x: Int) = x + 1 +} + +object Test { + def main(args: Array[String]) { + // it's important that the type is Inter so we do not call Baz.foo(I)I directly! + val baz: Inter = new Baz + // here we should go through specialized version of foo and thus have zero boxing + baz.foo(1) + println(runtime.BoxesRunTime.integerBoxCount) + } +} diff --git a/test/files/pos/t1751.cmds b/test/pending/pos/t1751.cmds index d4a4898ffd..d4a4898ffd 100644 --- a/test/files/pos/t1751.cmds +++ b/test/pending/pos/t1751.cmds diff --git a/test/files/pos/t1751/A1_2.scala b/test/pending/pos/t1751/A1_2.scala index 354d5eecd0..354d5eecd0 100644 --- a/test/files/pos/t1751/A1_2.scala +++ b/test/pending/pos/t1751/A1_2.scala diff --git a/test/files/pos/t1751/A2_1.scala b/test/pending/pos/t1751/A2_1.scala index c768062e43..c768062e43 100644 --- a/test/files/pos/t1751/A2_1.scala +++ b/test/pending/pos/t1751/A2_1.scala diff --git a/test/files/pos/t1751/SuiteClasses.java b/test/pending/pos/t1751/SuiteClasses.java index a415e4f572..a415e4f572 100644 --- a/test/files/pos/t1751/SuiteClasses.java +++ b/test/pending/pos/t1751/SuiteClasses.java diff --git a/test/files/pos/t1782.cmds b/test/pending/pos/t1782.cmds index 61f3d3788e..61f3d3788e 100644 --- a/test/files/pos/t1782.cmds +++ b/test/pending/pos/t1782.cmds diff --git a/test/files/pos/t1782/Ann.java b/test/pending/pos/t1782/Ann.java index 0dcfbd2ed7..0dcfbd2ed7 100644 --- a/test/files/pos/t1782/Ann.java +++ b/test/pending/pos/t1782/Ann.java diff --git a/test/files/pos/t1782/Days.java b/test/pending/pos/t1782/Days.java index 203a87b1c2..203a87b1c2 100644 --- a/test/files/pos/t1782/Days.java +++ b/test/pending/pos/t1782/Days.java diff --git a/test/files/pos/t1782/ImplementedBy.java b/test/pending/pos/t1782/ImplementedBy.java index 6aa8b4fa9e..6aa8b4fa9e 100644 --- a/test/files/pos/t1782/ImplementedBy.java +++ b/test/pending/pos/t1782/ImplementedBy.java diff --git a/test/files/pos/t1782/Test_1.scala b/test/pending/pos/t1782/Test_1.scala index 6467a74c29..6467a74c29 100644 --- a/test/files/pos/t1782/Test_1.scala +++ b/test/pending/pos/t1782/Test_1.scala diff --git a/test/files/pos/t294.cmds b/test/pending/pos/t294.cmds index 62c9a5a068..62c9a5a068 100644 --- a/test/files/pos/t294.cmds +++ b/test/pending/pos/t294.cmds diff --git a/test/files/pos/t294/Ann.java b/test/pending/pos/t294/Ann.java index 934ca46297..934ca46297 100644 --- a/test/files/pos/t294/Ann.java +++ b/test/pending/pos/t294/Ann.java diff --git a/test/files/pos/t294/Ann2.java b/test/pending/pos/t294/Ann2.java index 025b79e794..025b79e794 100644 --- a/test/files/pos/t294/Ann2.java +++ b/test/pending/pos/t294/Ann2.java diff --git a/test/files/pos/t294/Test_1.scala b/test/pending/pos/t294/Test_1.scala index ff1f34b10e..ff1f34b10e 100644 --- a/test/files/pos/t294/Test_1.scala +++ b/test/pending/pos/t294/Test_1.scala diff --git a/test/files/pos/t294/Test_2.scala b/test/pending/pos/t294/Test_2.scala index 9fb1c6e175..9fb1c6e175 100644 --- a/test/files/pos/t294/Test_2.scala +++ b/test/pending/pos/t294/Test_2.scala diff --git a/test/files/run/t3897.check b/test/pending/run/t3897.check index 244b83716f..244b83716f 100644 --- a/test/files/run/t3897.check +++ b/test/pending/run/t3897.check diff --git a/test/files/run/t3897/J_2.java b/test/pending/run/t3897/J_2.java index 178412dc92..178412dc92 100644 --- a/test/files/run/t3897/J_2.java +++ b/test/pending/run/t3897/J_2.java diff --git a/test/files/run/t3897/a_1.scala b/test/pending/run/t3897/a_1.scala index 4da959e2ac..4da959e2ac 100644 --- a/test/files/run/t3897/a_1.scala +++ b/test/pending/run/t3897/a_1.scala diff --git a/test/files/run/t3897/a_2.scala b/test/pending/run/t3897/a_2.scala index 4d9e59ef05..4d9e59ef05 100644 --- a/test/files/run/t3897/a_2.scala +++ b/test/pending/run/t3897/a_2.scala diff --git a/test/files/run/t5293-map.scala b/test/pending/run/t5293-map.scala index 2707aed07e..2707aed07e 100644 --- a/test/files/run/t5293-map.scala +++ b/test/pending/run/t5293-map.scala diff --git a/test/files/run/t5293.scala b/test/pending/run/t5293.scala index 01ead45d2a..01ead45d2a 100644 --- a/test/files/run/t5293.scala +++ b/test/pending/run/t5293.scala |