summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.xml19
-rw-r--r--src/compiler/scala/reflect/reify/utils/NodePrinters.scala2
-rwxr-xr-xsrc/compiler/scala/tools/nsc/ast/DocComments.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala6647
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala3
-rw-r--r--src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala2
-rw-r--r--src/compiler/scala/tools/nsc/javac/JavaScanners.scala4
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala32
-rw-r--r--src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala18
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala85
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala375
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala13
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala2
-rw-r--r--src/intellij/README11
-rw-r--r--src/intellij/actors.iml.SAMPLE4
-rw-r--r--src/intellij/asm.iml.SAMPLE12
-rw-r--r--src/intellij/compiler.iml.SAMPLE8
-rw-r--r--src/intellij/fjbg.iml.SAMPLE12
-rw-r--r--src/intellij/forkjoin.iml.SAMPLE12
-rw-r--r--src/intellij/library.iml.SAMPLE6
-rw-r--r--src/intellij/manual.iml.SAMPLE2
-rw-r--r--src/intellij/msil.iml.SAMPLE24
-rw-r--r--src/intellij/partest.iml.SAMPLE6
-rw-r--r--src/intellij/reflect.iml.SAMPLE6
-rw-r--r--src/intellij/scala-lang.ipr.SAMPLE30
-rw-r--r--src/intellij/scalap.iml.SAMPLE3
-rw-r--r--src/intellij/swing.iml.SAMPLE2
-rw-r--r--src/intellij/test.iml.SAMPLE10
-rw-r--r--src/library/scala/Option.scala8
-rw-r--r--src/library/scala/concurrent/ConcurrentPackageObject.scala66
-rw-r--r--src/library/scala/concurrent/DelayedLazyVal.scala9
-rw-r--r--src/library/scala/concurrent/ExecutionContext.scala5
-rw-r--r--src/library/scala/concurrent/Future.scala2
-rw-r--r--src/library/scala/concurrent/JavaConversions.scala12
-rw-r--r--src/library/scala/concurrent/default/TaskImpl.scala.disabled4
-rw-r--r--src/library/scala/concurrent/impl/ExecutionContextImpl.scala74
-rw-r--r--src/library/scala/concurrent/impl/Future.scala15
-rw-r--r--src/library/scala/concurrent/impl/Promise.scala3
-rw-r--r--src/library/scala/concurrent/package.scala51
-rw-r--r--src/library/scala/reflect/base/Names.scala3
-rw-r--r--src/library/scala/util/Either.scala12
-rw-r--r--src/library/scala/util/Try.scala2
-rw-r--r--src/partest/scala/tools/partest/PartestTask.scala8
-rw-r--r--src/partest/scala/tools/partest/instrumented/Instrumentation.scala86
-rw-r--r--src/partest/scala/tools/partest/instrumented/Profiler.java78
-rw-r--r--src/partest/scala/tools/partest/javaagent/ASMTransformer.java38
-rw-r--r--src/partest/scala/tools/partest/javaagent/MANIFEST.MF1
-rw-r--r--src/partest/scala/tools/partest/javaagent/ProfilerVisitor.java46
-rw-r--r--src/partest/scala/tools/partest/javaagent/ProfilingAgent.java25
-rw-r--r--src/partest/scala/tools/partest/nest/CompileManager.scala1
-rw-r--r--src/partest/scala/tools/partest/nest/ConsoleRunner.scala1
-rw-r--r--src/partest/scala/tools/partest/nest/NestUI.scala1
-rw-r--r--src/partest/scala/tools/partest/nest/PathSettings.scala6
-rw-r--r--src/partest/scala/tools/partest/nest/RunnerManager.scala16
-rw-r--r--src/partest/scala/tools/partest/nest/TestFile.scala1
-rw-r--r--src/reflect/scala/reflect/internal/Names.scala33
-rw-r--r--src/reflect/scala/reflect/internal/StdNames.scala15
-rw-r--r--src/reflect/scala/reflect/internal/TreeInfo.scala5
-rw-r--r--src/reflect/scala/reflect/runtime/JavaMirrors.scala2
-rw-r--r--test/files/instrumented/InstrumentationTest.check4
-rw-r--r--test/files/instrumented/InstrumentationTest.scala14
-rw-r--r--test/files/instrumented/README15
-rw-r--r--test/files/neg/reflection-names-neg.check10
-rw-r--r--test/files/neg/reflection-names-neg.scala6
-rw-r--r--test/files/neg/t1286.check6
-rw-r--r--test/files/neg/t5830.check5
-rw-r--r--test/files/neg/t5956.check20
-rw-r--r--test/files/neg/t5956.scala2
-rw-r--r--test/files/neg/t6011.check10
-rw-r--r--test/files/neg/t6011.flags1
-rw-r--r--test/files/neg/t6011.scala23
-rw-r--r--test/files/neg/t6048.check10
-rw-r--r--test/files/neg/t6048.flags1
-rw-r--r--test/files/neg/t6048.scala22
-rw-r--r--test/files/pos/t6028/t6028_1.scala3
-rw-r--r--test/files/pos/t6028/t6028_2.scala4
-rw-r--r--test/files/run/reflection-names.check4
-rw-r--r--test/files/run/reflection-names.scala15
-rw-r--r--test/files/run/t6011b.check1
-rw-r--r--test/files/run/t6011b.scala11
-rw-r--r--test/files/run/t6028.check84
-rw-r--r--test/files/run/t6028.scala21
-rw-r--r--test/files/specialized/t6035.check1
-rw-r--r--test/files/specialized/t6035/first_1.scala5
-rw-r--r--test/files/specialized/t6035/second_2.scala13
89 files changed, 4558 insertions, 3752 deletions
diff --git a/build.xml b/build.xml
index 0e0bda934a..8fa1b9cd76 100644
--- a/build.xml
+++ b/build.xml
@@ -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/reflect/reify/utils/NodePrinters.scala b/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
index 7214da597e..6394e1eac2 100644
--- a/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
+++ b/src/compiler/scala/reflect/reify/utils/NodePrinters.scala
@@ -42,7 +42,7 @@ trait NodePrinters {
val buf = new collection.mutable.ListBuffer[String]
val annotations = m.group(3)
- if (buf.nonEmpty || annotations.nonEmpty)
+ if (buf.nonEmpty || annotations != "")
buf.append("List(" + annotations + ")")
val privateWithin = "" + m.group(2)
diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala
index 316faba6e2..b545140c4a 100755
--- a/src/compiler/scala/tools/nsc/ast/DocComments.scala
+++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala
@@ -522,7 +522,7 @@ trait DocComments { self: Global =>
val substAliases = new TypeMap {
def apply(tp: Type) = mapOver(tp) match {
- case tp1 @ TypeRef(pre, sym, args) if (sym.name.length > 1 && sym.name(0) == '$') =>
+ case tp1 @ TypeRef(pre, sym, args) if (sym.name.length > 1 && sym.name.startChar == '$') =>
subst(sym, aliases, aliasExpansions) match {
case TypeRef(pre1, sym1, _) =>
typeRef(pre1, sym1, args)
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 3232bde3b4..e0c9631246 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -763,7 +763,7 @@ self =>
def precedence(operator: Name): Int =
if (operator eq nme.ERROR) -1
else {
- val firstCh = operator(0)
+ val firstCh = operator.startChar
if (isScalaLetter(firstCh)) 1
else if (nme.isOpAssignmentName(operator)) 0
else firstCh match {
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 9661ae6b3e..912a5b0e90 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/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
index a3f6726b44..c79248e1c1 100644
--- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
+++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
@@ -92,7 +92,7 @@ trait ScratchPadMaker { self: Global =>
case PackageDef(_, _) =>
super.traverse(tree)
case ModuleDef(_, name, Template(_, _, body)) =>
- if (objectName.length == 0 /* objectName.isEmpty does not compile on Java 5 due to ambiguous implicit conversions: augmentString, stringToTermName */)
+ if (objectName.length == 0)
objectName = tree.symbol.fullName
body foreach traverseStat
applyPendingPatches(skipped)
diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
index 73b5a752b4..a37fdc3ed1 100644
--- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
+++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala
@@ -778,7 +778,7 @@ trait JavaScanners extends ast.parser.ScannersCommon {
*/
def intVal(negated: Boolean): Long = {
if (token == CHARLIT && !negated) {
- if (name.length > 0) name(0) else 0
+ if (name.length > 0) name.charAt(0) else 0
} else {
var value: Long = 0
val divider = if (base == 10) 1 else 2
@@ -787,7 +787,7 @@ trait JavaScanners extends ast.parser.ScannersCommon {
var i = 0
val len = name.length
while (i < len) {
- val d = digit2int(name(i), base)
+ val d = digit2int(name.charAt(i), base)
if (d < 0) {
syntaxError("malformed integer number")
return 0
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index 60e11291c1..a035a346e6 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -307,7 +307,7 @@ abstract class ClassfileParser {
val start = starts(index)
if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start)
val name = getExternalName(in.getChar(start + 1))
- if (name(0) == ARRAY_TAG) {
+ if (name.charAt(0) == ARRAY_TAG) {
c = sigToType(null, name)
values(index) = c
} else {
@@ -674,16 +674,16 @@ abstract class ClassfileParser {
var index = 0
val end = sig.length
def accept(ch: Char) {
- assert(sig(index) == ch, (sig(index), ch))
+ assert(sig.charAt(index) == ch, (sig.charAt(index), ch))
index += 1
}
def subName(isDelimiter: Char => Boolean): Name = {
val start = index
- while (!isDelimiter(sig(index))) { index += 1 }
+ while (!isDelimiter(sig.charAt(index))) { index += 1 }
sig.subName(start, index)
}
def sig2type(tparams: immutable.Map[Name,Symbol], skiptvs: Boolean): Type = {
- val tag = sig(index); index += 1
+ val tag = sig.charAt(index); index += 1
tag match {
case BYTE_TAG => definitions.ByteClass.tpe
case CHAR_TAG => definitions.CharClass.tpe
@@ -704,12 +704,12 @@ abstract class ClassfileParser {
def processClassType(tp: Type): Type = tp match {
case TypeRef(pre, classSym, args) =>
val existentials = new ListBuffer[Symbol]()
- if (sig(index) == '<') {
+ if (sig.charAt(index) == '<') {
accept('<')
val xs = new ListBuffer[Type]()
var i = 0
- while (sig(index) != '>') {
- sig(index) match {
+ while (sig.charAt(index) != '>') {
+ sig.charAt(index) match {
case variance @ ('+' | '-' | '*') =>
index += 1
val bounds = variance match {
@@ -745,14 +745,14 @@ abstract class ClassfileParser {
res
}
case tp =>
- assert(sig(index) != '<', tp)
+ assert(sig.charAt(index) != '<', tp)
tp
}
val classSym = classNameToSymbol(subName(c => c == ';' || c == '<'))
assert(!classSym.isOverloaded, classSym.alternatives)
var tpe = processClassType(processInner(classSym.tpe))
- while (sig(index) == '.') {
+ while (sig.charAt(index) == '.') {
accept('.')
val name = subName(c => c == ';' || c == '<' || c == '.').toTypeName
val clazz = tpe.member(name)
@@ -761,7 +761,7 @@ abstract class ClassfileParser {
accept(';')
tpe
case ARRAY_TAG =>
- while ('0' <= sig(index) && sig(index) <= '9') index += 1
+ while ('0' <= sig.charAt(index) && sig.charAt(index) <= '9') index += 1
var elemtp = sig2type(tparams, skiptvs)
// make unbounded Array[T] where T is a type variable into Array[T with Object]
// (this is necessary because such arrays have a representation which is incompatible
@@ -778,7 +778,7 @@ abstract class ClassfileParser {
// we need a method symbol. given in line 486 by calling getType(methodSym, ..)
assert(sym ne null, sig)
val paramtypes = new ListBuffer[Type]()
- while (sig(index) != ')') {
+ while (sig.charAt(index) != ')') {
paramtypes += objToAny(sig2type(tparams, skiptvs))
}
index += 1
@@ -798,9 +798,9 @@ abstract class ClassfileParser {
def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean): Type = {
val ts = new ListBuffer[Type]
- while (sig(index) == ':') {
+ while (sig.charAt(index) == ':') {
index += 1
- if (sig(index) != ':') // guard against empty class bound
+ if (sig.charAt(index) != ':') // guard against empty class bound
ts += objToAny(sig2type(tparams, skiptvs))
}
TypeBounds.upper(intersectionType(ts.toList, sym))
@@ -808,11 +808,11 @@ abstract class ClassfileParser {
var tparams = classTParams
val newTParams = new ListBuffer[Symbol]()
- if (sig(index) == '<') {
+ if (sig.charAt(index) == '<') {
assert(sym != null, sig)
index += 1
val start = index
- while (sig(index) != '>') {
+ while (sig.charAt(index) != '>') {
val tpname = subName(':'.==).toTypeName
val s = sym.newTypeParameter(tpname)
tparams = tparams + (tpname -> s)
@@ -820,7 +820,7 @@ abstract class ClassfileParser {
newTParams += s
}
index = start
- while (sig(index) != '>') {
+ while (sig.charAt(index) != '>') {
val tpname = subName(':'.==).toTypeName
val s = tparams(tpname)
s.setInfo(sig2typeBounds(tparams, false))
diff --git a/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala b/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala
index e54ecdd590..c5092aa057 100644
--- a/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala
@@ -35,8 +35,6 @@ abstract class TypeParser {
protected var busy: Boolean = false // lock to detect recursive reads
- private implicit def stringToTermName(s: String): TermName = newTermName(s)
-
private object unpickler extends UnPickler {
val global: TypeParser.this.global.type = TypeParser.this.global
}
@@ -260,8 +258,8 @@ abstract class TypeParser {
|| ntype.IsInterface /* TODO why shouldn't nested ifaces be type-parsed too? */ )
{
val loader = new loaders.MsilFileLoader(new MsilFile(ntype))
- val nclazz = statics.newClass(ntype.Name.toTypeName)
- val nmodule = statics.newModule(ntype.Name)
+ val nclazz = statics.newClass(ntype.Name)
+ val nmodule = statics.newModule(ntype.Name)
nclazz.setInfo(loader)
nmodule.setInfo(loader)
staticDefs.enter(nclazz)
@@ -311,7 +309,7 @@ abstract class TypeParser {
assert(prop.PropertyType == getter.ReturnType);
val gparams: Array[ParameterInfo] = getter.GetParameters();
gparamsLength = gparams.length;
- val name: Name = if (gparamsLength == 0) prop.Name else nme.apply;
+ val name: TermName = if (gparamsLength == 0) prop.Name else nme.apply;
val flags = translateAttributes(getter);
val owner: Symbol = if (getter.IsStatic) statics else clazz;
val methodSym = owner.newMethod(name, NoPosition, flags)
@@ -333,7 +331,7 @@ abstract class TypeParser {
if(getter != null)
assert(sparams.length == gparamsLength + 1, "" + getter + "; " + setter);
- val name: Name = if (gparamsLength == 0) nme.getterToSetter(prop.Name)
+ val name: TermName = if (gparamsLength == 0) nme.getterToSetter(prop.Name)
else nme.update;
val flags = translateAttributes(setter);
val mtype = methodType(setter, definitions.UnitClass.tpe);
@@ -494,13 +492,13 @@ abstract class TypeParser {
else clrTypes.methods(methodSym) = method.asInstanceOf[MethodInfo];
}
- private def createMethod(name: Name, flags: Long, args: Array[MSILType], retType: MSILType, method: MethodInfo, statik: Boolean): Symbol = {
+ private def createMethod(name: TermName, flags: Long, args: Array[MSILType], retType: MSILType, method: MethodInfo, statik: Boolean): Symbol = {
val mtype = methodType(args, getCLSType(retType))
assert(mtype != null)
createMethod(name, flags, mtype, method, statik)
}
- private def createMethod(name: Name, flags: Long, mtype: Symbol => Type, method: MethodInfo, statik: Boolean): Symbol = {
+ private def createMethod(name: TermName, flags: Long, mtype: Symbol => Type, method: MethodInfo, statik: Boolean): Symbol = {
val methodSym: Symbol = (if (statik) statics else clazz).newMethod(name)
methodSym.setFlag(flags).setInfo(mtype(methodSym))
(if (statik) staticDefs else instanceDefs).enter(methodSym)
@@ -541,7 +539,7 @@ abstract class TypeParser {
s = createMethod(nme.MINUS, flags, args, typ, clrTypes.DELEGATE_REMOVE, false);
}
- private def getName(method: MethodBase): Name = {
+ private def getName(method: MethodBase): TermName = {
def operatorOverload(name : String, paramsArity : Int) : Option[Name] = paramsArity match {
case 1 => name match {
@@ -653,7 +651,7 @@ abstract class TypeParser {
private def getClassType(typ: MSILType): Type = {
assert(typ != null);
- val res = rootMirror.getClassByName(typ.FullName.replace('+', '.')).tpe;
+ val res = rootMirror.getClassByName(typ.FullName.replace('+', '.') : TypeName).tpe;
//if (res.isError())
// global.reporter.error("unknown class reference " + type.FullName);
res
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/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 663b3dd2e9..de0650b2ea 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/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
index 95d0369707..7bd5f4caeb 100644
--- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
@@ -540,7 +540,7 @@ trait MethodSynthesis {
val ValDef(mods, name, _, _) = tree
val beans = beanAccessorsFromNames(tree)
if (beans.nonEmpty) {
- if (!name(0).isLetter)
+ if (!name.charAt(0).isLetter)
BeanPropertyAnnotationFieldWithoutLetterError(tree)
else if (mods.isPrivate) // avoids name clashes with private fields in traits
BeanPropertyAnnotationPrivateFieldError(tree)
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 2a7a1bac5c..4e4176e531 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._
@@ -1251,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)
@@ -2195,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
@@ -2972,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
@@ -3008,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
+ }
+
+ private def noGuards(cs: List[CaseDef]): Boolean = !cs.exists(isGuardedCase)
- // requires(cs.forall(_.guard == EmptyTree))
+ // 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
}
@@ -3101,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)
+ }
}
- }
- (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 caseDefsWithGuards = sequence(caseDefs) match {
+ case None => return Nil
+ case Some(cds) => cds
+ }
+
+ 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/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 5241974793..a570cd74d6 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -4282,7 +4282,7 @@ trait Typers extends Modes with Adaptations with Tags {
}
def convertToAssignment(fun: Tree, qual: Tree, name: Name, args: List[Tree]): Tree = {
- val prefix = name stripSuffix nme.EQL
+ val prefix = name.toTermName stripSuffix nme.EQL
def mkAssign(vble: Tree): Tree =
Assign(
vble,
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/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..debfc226db 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
*/
diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala
index 75a83d6ef8..e556be4fe3 100644
--- a/src/library/scala/concurrent/Future.scala
+++ b/src/library/scala/concurrent/Future.scala
@@ -136,7 +136,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)
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/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..0c031743db 100644
--- a/src/library/scala/concurrent/impl/Future.scala
+++ b/src/library/scala/concurrent/impl/Future.scala
@@ -46,26 +46,13 @@ private[concurrent] object Future {
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
- }
-
private[impl] class PromiseCompletingRunnable[T](body: => T)
extends Runnable {
val promise = new Promise.DefaultPromise[T]()
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..ccfcd30c97 100644
--- a/src/library/scala/concurrent/impl/Promise.scala
+++ b/src/library/scala/concurrent/impl/Promise.scala
@@ -59,7 +59,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 +80,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/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/base/Names.scala b/src/library/scala/reflect/base/Names.scala
index edf2ba7dc9..280a6ce8a2 100644
--- a/src/library/scala/reflect/base/Names.scala
+++ b/src/library/scala/reflect/base/Names.scala
@@ -11,6 +11,9 @@ package base
* `name1 == name2` implies `name1 eq name2`.
*/
trait Names {
+ /** Intentionally no implicit from String => Name. */
+ implicit def stringToTermName(s: String): TermName = newTermName(s)
+ implicit def stringToTypeName(s: String): TypeName = newTypeName(s)
/** The abstract type of names */
type Name >: Null <: NameBase
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/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/Names.scala b/src/reflect/scala/reflect/internal/Names.scala
index 18671871ae..20da38fd63 100644
--- a/src/reflect/scala/reflect/internal/Names.scala
+++ b/src/reflect/scala/reflect/internal/Names.scala
@@ -10,12 +10,18 @@ import scala.io.Codec
import java.security.MessageDigest
import language.implicitConversions
+trait LowPriorityNames {
+ self: Names =>
+
+ implicit def nameToNameOps(name: Name): NameOps[Name] = new NameOps[Name](name)
+}
+
/** The class Names ...
*
* @author Martin Odersky
* @version 1.0, 05/02/2005
*/
-trait Names extends api.Names {
+trait Names extends api.Names with LowPriorityNames {
implicit def promoteTermNamesAsNecessary(name: Name): TermName = name.toTermName
// Operations -------------------------------------------------------------
@@ -139,7 +145,7 @@ trait Names extends api.Names {
* or Strings as Names. Give names the key functions the absence of which
* make people want Strings all the time.
*/
- sealed abstract class Name(protected val index: Int, protected val len: Int) extends NameApi with Function1[Int, Char] {
+ sealed abstract class Name(protected val index: Int, protected val len: Int) extends NameApi {
type ThisNameType >: Null <: Name
protected[this] def thisName: ThisNameType
@@ -220,7 +226,7 @@ trait Names extends api.Names {
}
/** @return the i'th Char of this name */
- final def apply(i: Int): Char = chrs(index + i)
+ final def charAt(i: Int): Char = chrs(index + i)
/** @return the index of first occurrence of char c in this name, length if not found */
final def pos(c: Char): Int = pos(c, 0)
@@ -349,18 +355,13 @@ trait Names extends api.Names {
/** Some thoroughly self-explanatory convenience functions. They
* assume that what they're being asked to do is known to be valid.
*/
- final def startChar: Char = apply(0)
- final def endChar: Char = apply(len - 1)
+ final def startChar: Char = this charAt 0
+ final def endChar: Char = this charAt len - 1
final def startsWith(char: Char): Boolean = len > 0 && startChar == char
final def startsWith(name: String): Boolean = startsWith(newTermName(name))
final def endsWith(char: Char): Boolean = len > 0 && endChar == char
final def endsWith(name: String): Boolean = endsWith(newTermName(name))
- def dropRight(n: Int): ThisNameType = subName(0, len - n)
- def drop(n: Int): ThisNameType = subName(n, len)
- def stripSuffix(suffix: Name): ThisNameType =
- if (this endsWith suffix) dropRight(suffix.length) else thisName
-
def indexOf(ch: Char) = {
val idx = pos(ch)
if (idx == length) -1 else idx
@@ -379,7 +380,7 @@ trait Names extends api.Names {
val cs = new Array[Char](len)
var i = 0
while (i < len) {
- val ch = this(i)
+ val ch = charAt(i)
cs(i) = if (ch == from) to else ch
i += 1
}
@@ -429,6 +430,16 @@ trait Names extends api.Names {
def debugString = { val s = decode ; if (isTypeName) s + "!" else s }
}
+ implicit def TermNameOps(name: TermName): NameOps[TermName] = new NameOps(name)
+ implicit def TypeNameOps(name: TypeName): NameOps[TypeName] = new NameOps(name)
+
+ final class NameOps[T <: Name](name: T) {
+ def stripSuffix(suffix: Name): T = if (name endsWith suffix) dropRight(suffix.length) else name
+ def dropRight(n: Int): T = name.subName(0, name.length - n).asInstanceOf[T]
+ def drop(n: Int): T = name.subName(n, name.length).asInstanceOf[T]
+ def nonEmpty: Boolean = name.length > 0
+ }
+
implicit val NameTag = ClassTag[Name](classOf[Name])
/** A name that contains no operator chars nor dollar signs.
diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala
index 22b0908cab..165e04863c 100644
--- a/src/reflect/scala/reflect/internal/StdNames.scala
+++ b/src/reflect/scala/reflect/internal/StdNames.scala
@@ -84,6 +84,9 @@ trait StdNames {
abstract class CommonNames extends NamesApi {
type NameType >: Null <: Name
+ // Masking some implicits so as to allow our targeted => NameType.
+ protected val stringToTermName = null
+ protected val stringToTypeName = null
protected implicit def createNameType(name: String): NameType
def flattenedName(segments: Name*): NameType =
@@ -371,9 +374,9 @@ trait StdNames {
*/
def originalName(name: Name): Name = {
var i = name.length
- while (i >= 2 && !(name(i - 1) == '$' && name(i - 2) == '$')) i -= 1
+ while (i >= 2 && !(name.charAt(i - 1) == '$' && name.charAt(i - 2) == '$')) i -= 1
if (i >= 2) {
- while (i >= 3 && name(i - 3) == '$') i -= 1
+ while (i >= 3 && name.charAt(i - 3) == '$') i -= 1
name.subName(i, name.length)
} else name
}
@@ -452,10 +455,10 @@ trait StdNames {
// Otherwise return the argument.
def stripAnonNumberSuffix(name: Name): Name = {
var pos = name.length
- while (pos > 0 && name(pos - 1).isDigit)
+ while (pos > 0 && name.charAt(pos - 1).isDigit)
pos -= 1
- if (pos <= 0 || pos == name.length || name(pos - 1) != '$') name
+ if (pos <= 0 || pos == name.length || name.charAt(pos - 1) != '$') name
else name.subName(0, pos - 1)
}
@@ -964,7 +967,7 @@ trait StdNames {
case -1 => if (name == "") scala.Nil else scala.List(mkName(name, assumeTerm))
// otherwise, we can tell based on whether '#' or '.' is the following char.
case idx =>
- val (simple, div, rest) = (name take idx, name charAt idx, newTermName(name) drop (idx + 1))
+ val (simple, div, rest) = (name take idx, name charAt idx, name drop idx + 1)
mkName(simple, div == '.') :: segments(rest, assumeTerm)
}
}
@@ -1039,6 +1042,8 @@ trait StdNames {
}
abstract class SymbolNames {
+ protected val stringToTermName = null
+ protected val stringToTypeName = null
protected implicit def createNameType(s: String): TypeName = newTypeNameCached(s)
val BeanProperty : TypeName
diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala
index 698d219634..7ba749ed2c 100644
--- a/src/reflect/scala/reflect/internal/TreeInfo.scala
+++ b/src/reflect/scala/reflect/internal/TreeInfo.scala
@@ -316,7 +316,7 @@ abstract class TreeInfo {
/** Is name a variable name? */
def isVariableName(name: Name): Boolean = {
- val first = name(0)
+ val first = name.startChar
((first.isLower && first.isLetter) || first == '_') && !reserved(name)
}
@@ -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/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala
index eae6a3b297..185621efa4 100644
--- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala
+++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala
@@ -658,7 +658,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
val ownerModule: ModuleSymbol =
if (split > 0) packageNameToScala(fullname take split) else this.RootPackage
val owner = ownerModule.moduleClass
- val name = newTermName(fullname drop (split + 1))
+ val name = (fullname: TermName) drop split + 1
val opkg = owner.info decl name
if (opkg.isPackage)
opkg.asModuleSymbol
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/reflection-names-neg.check b/test/files/neg/reflection-names-neg.check
new file mode 100644
index 0000000000..a56a19e7fd
--- /dev/null
+++ b/test/files/neg/reflection-names-neg.check
@@ -0,0 +1,10 @@
+reflection-names-neg.scala:5: error: type mismatch;
+ found : String("abc")
+ required: reflect.runtime.universe.Name
+Note that implicit conversions are not applicable because they are ambiguous:
+ both method stringToTermName in trait Names of type (s: String)reflect.runtime.universe.TermName
+ and method stringToTypeName in trait Names of type (s: String)reflect.runtime.universe.TypeName
+ are possible conversion functions from String("abc") to reflect.runtime.universe.Name
+ val x2 = ("abc": Name) drop 1 // error
+ ^
+one error found
diff --git a/test/files/neg/reflection-names-neg.scala b/test/files/neg/reflection-names-neg.scala
new file mode 100644
index 0000000000..7283d16db9
--- /dev/null
+++ b/test/files/neg/reflection-names-neg.scala
@@ -0,0 +1,6 @@
+import scala.reflect.runtime.universe._
+
+object Test {
+ val x1 = "abc" drop 1 // "bc": String
+ val x2 = ("abc": Name) drop 1 // error
+}
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/reflection-names.check b/test/files/run/reflection-names.check
new file mode 100644
index 0000000000..f8cb78cc67
--- /dev/null
+++ b/test/files/run/reflection-names.check
@@ -0,0 +1,4 @@
+(java.lang.String,bc)
+(scala.reflect.internal.Names$TermName_R,bc)
+(scala.reflect.internal.Names$TypeName_R,bc)
+(scala.reflect.internal.Names$TypeName_R,bc)
diff --git a/test/files/run/reflection-names.scala b/test/files/run/reflection-names.scala
new file mode 100644
index 0000000000..2433c84813
--- /dev/null
+++ b/test/files/run/reflection-names.scala
@@ -0,0 +1,15 @@
+import scala.tools.nsc._
+
+object Test {
+ val global = new Global(new Settings())
+ import global._
+
+ val x1 = "abc" drop 1 // "bc": String
+ val x2 = ("abc": TermName) drop 1 // "bc": TermName
+ val x3 = ("abc": TypeName) drop 1 // "bc": TypeName
+ val x4 = (("abc": TypeName): Name) drop 1 // "bc": Name
+
+ def main(args: Array[String]): Unit = {
+ List(x1, x2, x3, x4) foreach (x => println(x.getClass.getName, x))
+ }
+}
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/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)
+ }
+}