summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2014-12-16 10:50:10 +0100
committerLukas Rytz <lukas.rytz@gmail.com>2015-01-16 23:18:12 +0100
commitafebceee78155c66564921eab7dd2170f820fdbf (patch)
tree65fba50f00339b69e4b0713f4120722115cf9a9c /src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
parent7c1983d153a81ba43858a1bb5f0b59c5708bbfcf (diff)
downloadscala-afebceee78155c66564921eab7dd2170f820fdbf.tar.gz
scala-afebceee78155c66564921eab7dd2170f820fdbf.tar.bz2
scala-afebceee78155c66564921eab7dd2170f820fdbf.zip
Construct ClassBTypes from parsed classfiles
This infrastructure is required for the inliner: when inlining code from a classfile, the corresponding ClassBType is needed for various things (eg access checks, InnerClass attribute). The test creates two ClassBTypes for the same class: once using the (unpickled) Symbol, once using the parsed ASM ClassNode, and verifies that the two are the same. There's a cleanup to the InnerClass attribute: object T { class Member; def foo = { class Local } } class T For Java compatibility the InnerClass entry for Member says the class is nested in T (not in the module class T$). We now make sure to add that entry only to T, not to T$ (unless Member is actually referenced in the classfile T$, in that case it will be added, as required).
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
new file mode 100644
index 0000000000..285855d083
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CodeRepository.scala
@@ -0,0 +1,93 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.tools.asm
+import asm.tree._
+import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.io.AbstractFile
+import scala.tools.nsc.util.ClassFileLookup
+import OptimizerReporting._
+
+class CodeRepository(val classPath: ClassFileLookup[AbstractFile]) {
+ import BTypes.InternalName
+
+ /**
+ * Cache for parsed ClassNodes.
+ */
+ val classes: collection.concurrent.Map[InternalName, ClassNode] = collection.concurrent.TrieMap.empty[InternalName, ClassNode]
+
+ /**
+ * The class node for an internal name. If the class node is not yet available, it is parsed from
+ * the classfile on the compile classpath.
+ */
+ def classNode(internalName: InternalName): ClassNode = {
+ classes.getOrElseUpdate(internalName, parseClass(internalName))
+ }
+
+ /**
+ * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
+ * The declaration of the field may be in one of the superclasses.
+ *
+ * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class.
+ */
+ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = {
+ val c = classNode(classInternalName)
+ c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse {
+ Option(c.superName).flatMap(n => fieldNode(n, name, descriptor))
+ }
+ }
+
+ /**
+ * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
+ * The declaration of the method may be in one of the parents.
+ *
+ * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class.
+ */
+ def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = {
+ val c = classNode(classInternalName)
+ c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse {
+ val parents = Option(c.superName) ++ c.interfaces.asScala
+ // `view` to stop at the first result
+ parents.view.flatMap(methodNode(_, name, descriptor)).headOption
+ }
+ }
+
+ private def parseClass(internalName: InternalName): ClassNode = {
+ val fullName = internalName.replace('/', '.')
+ classPath.findClassFile(fullName) map { classFile =>
+ val classNode = new asm.tree.ClassNode()
+ val classReader = new asm.ClassReader(classFile.toByteArray)
+ // We don't need frames when inlining, but we want to keep the local variable table, so we
+ // don't use SKIP_DEBUG.
+ classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES)
+ // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after
+ // inlining.
+ // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining?
+ // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension
+ // attribute that contains JSR-45 data that encodes debugging info.
+ // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11
+ // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html
+ removeLineNumberNodes(classNode)
+ classes(internalName) = classNode
+ classNode
+ } getOrElse {
+ inlineFailure(s"Class file for class $fullName not found.")
+ }
+ }
+
+ private def removeLineNumberNodes(classNode: ClassNode): Unit = {
+ for (method <- classNode.methods.asScala) {
+ val iter = method.instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case _: LineNumberNode => iter.remove()
+ case _ =>
+ }
+ }
+ }
+}