diff options
42 files changed, 952 insertions, 429 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 0df1b2029d..cd7e0b83e8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -10,6 +10,7 @@ import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.{ClassWriter, Attribute, ClassReader} import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype object AsmUtils { @@ -81,13 +82,16 @@ object AsmUtils { /** * Returns a human-readable representation of the given instruction. */ - def textify(insn: AbstractInsnNode): String = { - val trace = new TraceMethodVisitor(new Textifier) - insn.accept(trace) - val sw = new StringWriter - val pw = new PrintWriter(sw) - trace.p.print(pw) - sw.toString.trim + def textify(insn: AbstractInsnNode): String = insn match { + case _: InitialProducer => + insn.toString + case _ => + val trace = new TraceMethodVisitor(new Textifier) + insn.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index daa31c5dfe..93f5159f89 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -256,14 +256,17 @@ final class BCodeAsmCommon[G <: Global](val global: G) { if (hasAbstractMethod) ACC_ABSTRACT else 0 } GenBCode.mkFlags( - if (classSym.isPublic) ACC_PUBLIC else 0, - if (classSym.isFinal) ACC_FINAL else 0, + // SI-9393: the classfile / java source parser make java annotation symbols look like classes. + // here we recover the actual classfile flags. + if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0, + if (classSym.isPublic) ACC_PUBLIC else 0, + if (classSym.isFinal) ACC_FINAL else 0, // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. - if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, + if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags) - if (!classSym.hasEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (classSym.isArtifact) ACC_SYNTHETIC else 0, - if (classSym.hasEnumFlag) enumFlags else 0 + if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (classSym.isArtifact) ACC_SYNTHETIC else 0, + if (classSym.hasJavaEnumFlag) enumFlags else 0 ) } @@ -310,10 +313,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } private def retentionPolicyOf(annot: AnnotationInfo): Symbol = - annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).map(assoc => + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc => assoc.collectFirst { case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value - }).flatten.getOrElse(AnnotationRetentionPolicyClassValue) + }).getOrElse(AnnotationRetentionPolicyClassValue) def implementedInterfaces(classSym: Symbol): List[Symbol] = { // Additional interface parents based on annotations and other cues @@ -322,9 +325,18 @@ final class BCodeAsmCommon[G <: Global](val global: G) { case _ => None } - def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait + // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag - val allParents = classSym.info.parents ++ classSym.annotations.flatMap(newParentForAnnotation) + val classParents = { + val parents = classSym.info.parents + // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the + // parents of a java annotations. undo this for the backend (where we need classfile-level information). + if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) + else parents + } + + val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) // We keep the superClass when computing minimizeParents to eliminate more interfaces. // Example: T can be eliminated from D diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 40ba0c010b..67fc7923ea 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -632,10 +632,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") } - case Apply(_, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => + case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get genLoadArguments(args, paramTKs(app)) genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) + generatedType = asmMethodType(fun.symbol).returnType case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 6aa3a62295..65a6b82570 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -329,7 +329,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { // If the `sym` is a java module class, we use the java class instead. This ensures that we // register the class (instead of the module class) in innerClassBufferASM. // The two symbols have the same name, so the resulting internalName is the same. - val classSym = if (sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass else sym + // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) + val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym getClassBTypeAndRegisterInnerClass(classSym).internalName } @@ -714,7 +715,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { { val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) mv.visitCode() - mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.encoded, "$deserializeLambdaCache$", "Ljava/util/Map;") + // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. + mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") mv.visitVarInsn(ASTORE, 1) mv.visitVarInsn(ALOAD, 1) val l0 = new asm.Label() @@ -724,13 +726,13 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) mv.visitVarInsn(ASTORE, 1) mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.encoded, "$deserializeLambdaCache$", "Ljava/util/Map;") + mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") mv.visitLabel(l0) - mv.visitFrame(asm.Opcodes.F_APPEND,1, Array("java/util/Map"), 0, null) + mv.visitFieldInsn(GETSTATIC, "scala/compat/java8/runtime/LambdaDeserializer$", "MODULE$", "Lscala/compat/java8/runtime/LambdaDeserializer$;") mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) mv.visitVarInsn(ALOAD, 1) mv.visitVarInsn(ALOAD, 0) - mv.visitMethodInsn(INVOKESTATIC, "scala/compat/java8/runtime/LambdaDeserializer", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) + mv.visitMethodInsn(INVOKEVIRTUAL, "scala/compat/java8/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) mv.visitInsn(ARETURN) mv.visitEnd() } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index c7c2e43f82..a9b6a312e9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -153,9 +153,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { */ private def initJClass(jclass: asm.ClassVisitor) { - val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) - val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map { + val bType = classBTypeFromSymbol(claszSymbol) + val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName + val interfaceNames = bType.info.get.interfaces map { case classBType => if (classBType.isNestedClass.get) { innerClassBufferASM += classBType } classBType.internalName diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index cf29c8090b..45d9cc3ff3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -216,7 +216,18 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { - val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass + // Check for isImplClass: trait implementation classes have NoSymbol as superClass + // Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add + // scala.annotation.Annotation as superclass to java annotations. In reality, java + // annotation classfiles have superclass Object (like any interface classfile). + val superClassSym = if (classSym.isImplClass || classSym.hasJavaAnnotationFlag) ObjectClass else { + val sc = classSym.superClass + // SI-9393: Java annotation classes don't have the ABSTRACT/INTERFACE flag, so they appear + // (wrongly) as superclasses. Fix this for BTypes: the java annotation will appear as interface + // (handled by method implementedInterfaces), the superclass is set to Object. + if (sc.hasJavaAnnotationFlag) ObjectClass + else sc + } assert( if (classSym == ObjectClass) superClassSym == NoSymbol @@ -351,7 +362,15 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val isTopLevel = innerClassSym.rawowner.isPackageClass // impl classes are considered top-level, see comment in BTypes if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None - else { + else if (innerClassSym.rawowner.isTerm) { + // This case should never be reached: the lambdalift phase mutates the rawowner field of all + // classes to be the enclosing class. SI-9392 shows an errant macro that leaves a reference + // to a local class symbol that no longer exists, which is not updated by lambdalift. + devWarning(innerClassSym.pos, + s"""The class symbol $innerClassSym with the term symbol ${innerClassSym.rawowner} as `rawowner` reached the backend. + |Most likely this indicates a stale reference to a non-existing class introduced by a macro, see SI-9392.""".stripMargin) + None + } else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) @@ -559,7 +578,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, if (sym.isArtifact) ACC_SYNTHETIC else 0, if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasEnumFlag) ACC_ENUM else 0, + if (sym.hasJavaEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0, if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0 diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index 4fc05cafdc..b41d0de92f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -1,7 +1,7 @@ package scala.tools.nsc package backend.jvm -import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.reflect.internal.util.Position import scala.tools.nsc.settings.ScalaSettings @@ -246,11 +246,16 @@ object BackendReporting { case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + case object UnknownInvokeDynamicInstruction extends OptimizerWarning { + override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)." + def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed + } + /** * Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten * to the closure body method. */ - trait RewriteClosureApplyToClosureBodyFailed extends OptimizerWarning { + sealed trait RewriteClosureApplyToClosureBodyFailed extends OptimizerWarning { def pos: Position override def emitWarning(settings: ScalaSettings): Boolean = this match { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 2f1cd732da..a34ab914ef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -307,7 +307,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, if (sym.isArtifact) ACC_SYNTHETIC else 0, if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasEnumFlag) ACC_ENUM else 0, + if (sym.hasJavaEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 ) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala index 40f91cbed4..700b2f2f6c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala @@ -57,8 +57,20 @@ import scala.collection.convert.decorateAsScala._ * copying operations. */ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) { + + /* Timers for benchmarking ProdCons + import scala.reflect.internal.util.Statistics._ + import ProdConsAnalyzer._ + val analyzerTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - analysis", prodConsAnalyzerTimer) + val consumersTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - consumers", prodConsAnalyzerTimer) + */ + val analyzer = new Analyzer(new InitialProducerSourceInterpreter) + +// val start = analyzerTimer.start() analyzer.analyze(classInternalName, methodNode) +// analyzerTimer.stop(start) +// println(analyzerTimer.line) def frameAt(insn: AbstractInsnNode) = analyzer.frameAt(insn, methodNode) @@ -103,7 +115,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) def initialProducersForValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = { def initialProducers(insn: AbstractInsnNode, producedSlot: Int): Set[AbstractInsnNode] = { if (isCopyOperation(insn)) { - _initialProducersCache.getOrElseUpdate((insn, producedSlot), { + val key = (insn, producedSlot) + _initialProducersCache.getOrElseUpdate(key, { + // prevent infinite recursion if an instruction is its own producer or consumer + // see cyclicProdCons in ProdConsAnalyzerTest + _initialProducersCache(key) = Set.empty val (sourceValue, sourceValueSlot) = copyOperationSourceValue(insn, producedSlot) sourceValue.insns.iterator.asScala.flatMap(initialProducers(_, sourceValueSlot)).toSet }) @@ -121,7 +137,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) def ultimateConsumersOfValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = { def ultimateConsumers(insn: AbstractInsnNode, consumedSlot: Int): Set[AbstractInsnNode] = { if (isCopyOperation(insn)) { - _ultimateConsumersCache.getOrElseUpdate((insn, consumedSlot), { + val key = (insn, consumedSlot) + _ultimateConsumersCache.getOrElseUpdate(key, { + // prevent infinite recursion if an instruction is its own producer or consumer + // see cyclicProdCons in ProdConsAnalyzerTest + _ultimateConsumersCache(key) = Set.empty for { producedSlot <- copyOperationProducedValueSlots(insn, consumedSlot) consumer <- consumersOfValueAt(insn.getNext, producedSlot) @@ -384,6 +404,7 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) /** For each instruction, a set of potential consumers of the produced values. */ private lazy val _consumersOfOutputsFrom: Map[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] = { +// val start = consumersTimer.start() var res = Map.empty[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] for { insn <- methodNode.instructions.iterator.asScala @@ -396,6 +417,8 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) val outputIndex = producedSlots.indexOf(i) res = res.updated(producer, currentConsumers.updated(outputIndex, currentConsumers(outputIndex) + insn)) } +// consumersTimer.stop(start) +// println(consumersTimer.line) res } @@ -403,6 +426,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) private val _ultimateConsumersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty } +object ProdConsAnalyzer { + import scala.reflect.internal.util.Statistics._ + val prodConsAnalyzerTimer = newTimer("Time in ProdConsAnalyzer", "jvm") +} + /** * A class for pseudo-instructions representing the initial producers of local values that have * no producer instruction in the method: diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 0ec550981a..df8dcc690a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -12,7 +12,7 @@ import scala.collection.mutable import scala.reflect.internal.util.Collections._ import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ -import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes} +import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type} import scala.tools.asm.tree._ import GenBCode._ import scala.collection.convert.decorateAsScala._ @@ -104,6 +104,8 @@ object BytecodeUtils { def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0 + def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY + def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction do { result = result.getNext } @@ -331,6 +333,26 @@ object BytecodeUtils { } /** + * This method is used by optimizer components to eliminate phantom values of instruction + * that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well + * with stack map frames. + * + * For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a + * method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`. + * + * During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that + * method which explains the issue with such phantom values. + */ + def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { + if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) + } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) + } + } + + /** * A wrapper to make ASM's Analyzer a bit easier to use. */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index 8abecdb261..96455c0e38 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,7 +9,7 @@ package opt import scala.reflect.internal.util.{NoPosition, Position} import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter} -import scala.tools.asm.{Opcodes, Type} +import scala.tools.asm.{Opcodes, Type, Handle} import scala.tools.asm.tree._ import scala.collection.concurrent import scala.collection.convert.decorateAsScala._ @@ -24,7 +24,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty) - val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, (MethodNode, ClassBType)] = recordPerRunCache(concurrent.TrieMap.empty) + val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty) def addClass(classNode: ClassNode): Unit = { val classType = classBTypeFromClassNode(classNode) @@ -33,14 +33,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) { (calls, closureInits) = analyzeCallsites(m, classType) } { calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite) - closureInits foreach (indy => closureInstantiations(indy) = (m, classType)) + closureInits foreach (lmf => closureInstantiations(lmf.indy) = ClosureInstantiation(lmf, m, classType)) } } /** * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions. */ - def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[InvokeDynamicInsnNode]) = { + def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = { case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, @@ -129,7 +129,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { } val callsites = new collection.mutable.ListBuffer[Callsite] - val closureInstantiations = new collection.mutable.ListBuffer[InvokeDynamicInsnNode] + val closureInstantiations = new collection.mutable.ListBuffer[LambdaMetaFactoryCall] methodNode.instructions.iterator.asScala foreach { case call: MethodInsnNode => @@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { callsitePosition = callsitePositions.getOrElse(call, NoPosition) ) - case indy: InvokeDynamicInsnNode => - if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy + case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) => + closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) case _ => } @@ -236,4 +236,82 @@ class CallGraph[BT <: BTypes](val btypes: BT) { calleeInfoWarning: Option[CalleeInfoWarning]) { assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.") } + + final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType) { + override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)" + } + final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type) + + object LambdaMetaFactoryCall { + private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory" + + private val metafactoryHandle = { + val metafactoryMethodName: String = "metafactory" + val metafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" + new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc) + } + + private val altMetafactoryHandle = { + val altMetafactoryMethodName: String = "altMetafactory" + val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;" + new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc) + } + + def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match { + case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle => + indy.bsmArgs match { + case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_* + // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda + // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc). + // + // The closure optimizer supports only one of those adaptations: it will cast arguments + // to the correct type when re-writing a closure call to the body method. Example: + // + // val fun: String => String = l => l + // val l = List("") + // fun(l.head) + // + // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType + // is `(String)String`. The return type of `List.head` is `Object`. + // + // The implMethod has the signature `C$anonfun(String)String`. + // + // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`, + // so the object returned by `List.head` can be directly passed into the call (no cast). + // + // The closure object will cast the object to String before passing it to the implMethod. + // + // When re-writing the closure callsite to the implMethod, we have to insert a cast. + // + // The check below ensures that + // (1) the implMethod type has the expected singature (captured types plus argument types + // from instantiatedMethodType) + // (2) the receiver of the implMethod matches the first captured type + // (3) all parameters that are not the same in samMethodType and instantiatedMethodType + // are reference types, so that we can insert casts to perform the same adaptation + // that the closure object would. + + val isStatic = implMethod.getTag == Opcodes.H_INVOKESTATIC + val indyParamTypes = Type.getArgumentTypes(indy.desc) + val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes + val expectedImplMethodType = { + val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes + Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*) + } + + val isIndyLambda = ( + Type.getType(implMethod.getDesc) == expectedImplMethodType // (1) + && (isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName) // (2) + && samMethodType.getArgumentTypes.corresponds(instantiatedMethodArgTypes)((samArgType, instArgType) => + samArgType == instArgType || isReference(samArgType) && isReference(instArgType)) // (3) + ) + + if (isIndyLambda) Some((indy, samMethodType, implMethod, instantiatedMethodType)) + else None + + case _ => None + } + case _ => None + } + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 1648a53ed8..92b9b34006 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -8,8 +8,9 @@ package backend.jvm package opt import scala.annotation.switch +import scala.collection.immutable import scala.reflect.internal.util.NoPosition -import scala.tools.asm.{Handle, Type, Opcodes} +import scala.tools.asm.{Type, Opcodes} import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer @@ -23,90 +24,157 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ + /** + * If a closure is allocated and invoked within the same method, re-write the invocation to the + * closure body method. + * + * Note that the closure body method (generated by delambdafy:method) takes additional parameters + * for the values captured by the closure. The bytecode is transformed from + * + * [generate captured values] + * [closure init, capturing values] + * [...] + * [load closure object] + * [generate closure invocation arguments] + * [invoke closure.apply] + * + * to + * + * [generate captured values] + * [store captured values into new locals] + * [load the captured values from locals] // a future optimization will eliminate the closure + * [closure init, capturing values] // instantiation if the closure object becomes unused + * [...] + * [load closure object] + * [generate closure invocation arguments] + * [store argument values into new locals] + * [drop the closure object] + * [load captured values from locals] + * [load argument values from locals] + * [invoke the closure body method] + */ def rewriteClosureApplyInvocations(): Unit = { - closureInstantiations foreach { - case (indy, (methodNode, ownerClass)) => - val warnings = rewriteClosureApplyInvocations(indy, methodNode, ownerClass) - warnings.foreach(w => backendReporting.inlinerWarning(w.pos, w.toString)) + implicit object closureInitOrdering extends Ordering[ClosureInstantiation] { + override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = { + val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName + if (cls != 0) return cls + + val mName = x.ownerMethod.name compareTo y.ownerMethod.name + if (mName != 0) return mName + + val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc + if (mDesc != 0) return mDesc + + def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy) + pos(x) - pos(y) + } } - } - private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory" + // Grouping the closure instantiations by method allows running the ProdConsAnalyzer only once per + // method. Also sort the instantiations: If there are multiple closure instantiations in a method, + // closure invocations need to be re-written in a consistent order for bytecode stability. The local + // variable slots for storing captured values depends on the order of rewriting. + val closureInstantiationsByMethod: Map[MethodNode, immutable.TreeSet[ClosureInstantiation]] = { + closureInstantiations.values.groupBy(_.ownerMethod).mapValues(immutable.TreeSet.empty ++ _) + } - private val metafactoryHandle = { - val metafactoryMethodName: String = "metafactory" - val metafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" - new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc) - } + // For each closure instantiation, a list of callsites of the closure that can be re-written + // If a callsite cannot be rewritten, for example because the lambda body method is not accessible, + // a warning is returned instead. + val callsitesToRewrite: List[(ClosureInstantiation, List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]])] = { + closureInstantiationsByMethod.iterator.flatMap({ + case (methodNode, closureInits) => + // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`) + lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.head.ownerClass.internalName) + closureInits.iterator.map(init => (init, closureCallsites(init, prodCons))) + }).toList // mapping to a list (not a map) to keep the sorting of closureInstantiationsByMethod + } - private val altMetafactoryHandle = { - val altMetafactoryMethodName: String = "altMetafactory" - val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;" - new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc) + // Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten) + for ((closureInit, callsites) <- callsitesToRewrite) { + // Local variables that hold the captured values and the closure invocation arguments. + // They are lazy vals to ensure that locals for captured values are only allocated if there's + // actually a callsite to rewrite (an not only warnings to be issued). + lazy val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit) + for (callsite <- callsites) callsite match { + case Left(warning) => + backendReporting.inlinerWarning(warning.pos, warning.toString) + + case Right((invocation, stackHeight)) => + rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList) + } + } } - def isClosureInstantiation(indy: InvokeDynamicInsnNode): Boolean = { - (indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle) && - { - indy.bsmArgs match { - case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs @ _*) => - // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda - // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc). - // - // The closure optimizer supports only one of those adaptations: it will cast arguments - // to the correct type when re-writing a closure call to the body method. Example: - // - // val fun: String => String = l => l - // val l = List("") - // fun(l.head) - // - // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType - // is `(String)String`. The return type of `List.head` is `Object`. - // - // The implMethod has the signature `C$anonfun(String)String`. - // - // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`, - // so the object returned by `List.head` can be directly passed into the call (no cast). - // - // The closure object will cast the object to String before passing it to the implMethod. - // - // When re-writing the closure callsite to the implMethod, we have to insert a cast. - // - // The check below ensures that - // (1) the implMethod type has the expected singature (captured types plus argument types - // from instantiatedMethodType) - // (2) the receiver of the implMethod matches the first captured type - // (3) all parameters that are not the same in samMethodType and instantiatedMethodType - // are reference types, so that we can insert casts to perform the same adaptation - // that the closure object would. - - val isStatic = implMethod.getTag == H_INVOKESTATIC - val indyParamTypes = Type.getArgumentTypes(indy.desc) - val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes - val expectedImplMethodType = { - val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes - Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*) - } - - { - Type.getType(implMethod.getDesc) == expectedImplMethodType // (1) - } && { - isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName // (2) - } && { - def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY - (samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall { - case (samArgType, instArgType) => - samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3) - } - } - + /** + * Insert instructions to store the values captured by a closure instantiation into local variables, + * and load the values back to the stack. + * + * Returns the list of locals holding those captured values, and a list of locals that should be + * used at the closure invocation callsite to store the arguments passed to the closure invocation. + */ + private def localsForClosureRewrite(closureInit: ClosureInstantiation): (LocalsList, LocalsList) = { + val ownerMethod = closureInit.ownerMethod + val captureLocals = storeCaptures(closureInit) + + // allocate locals for storing the arguments of the closure apply callsites. + // if there are multiple callsites, the same locals are re-used. + val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes + val firstArgLocal = ownerMethod.maxLocals + + // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce + // casts for arguments that have different types in samMethodType and instantiatedMethodType. + val castLoadTypes = { + val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType + (argTypes, instantiatedMethodType.getArgumentTypes).zipped map { + case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType => + // the LambdaMetaFactoryCall extractor ensures that the two types are reference types, + // so we don't end up casting primitive values. + Some(instantiatedArgType) case _ => - false + None } } + val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes) + ownerMethod.maxLocals = firstArgLocal + argLocals.size + + (captureLocals, argLocals) + } + + /** + * Find all callsites of a closure within the method where the closure is allocated. + */ + private def closureCallsites(closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]] = { + val ownerMethod = closureInit.ownerMethod + val ownerClass = closureInit.ownerClass + val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod + + ownerMethod.instructions.iterator.asScala.collect({ + case invocation: MethodInsnNode if isSamInvocation(invocation, closureInit, prodCons) => + // TODO: This is maybe over-cautious. + // We are checking if the closure body method is accessible at the closure callsite. + // If the closure allocation has access to the body method, then the callsite (in the same + // method as the alloction) should have access too. + val bodyAccessible: Either[OptimizerWarning, Boolean] = for { + (bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)] + isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass) + } yield { + isAccessible + } + + def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition) + val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match { + case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w)) + case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName)) + case _ => Right(prodCons.frameAt(invocation).getStackSize) + } + + stackSize.right.map((invocation, _)) + }).toList } - def isSamInvocation(invocation: MethodInsnNode, indy: InvokeDynamicInsnNode, prodCons: => ProdConsAnalyzer): Boolean = { + private def isSamInvocation(invocation: MethodInsnNode, closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): Boolean = { + val indy = closureInit.lambdaMetaFactoryCall.indy if (invocation.getOpcode == INVOKESTATIC) false else { def closureIsReceiver = { @@ -120,20 +188,95 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { } invocation.name == indy.name && { - val indySamMethodDesc = indy.bsmArgs(0).asInstanceOf[Type].getDescriptor // safe, checked in isClosureInstantiation + val indySamMethodDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor indySamMethodDesc == invocation.desc } && - closureIsReceiver // most expensive check last + closureIsReceiver // most expensive check last + } + } + + private def rewriteClosureApplyInvocation(closureInit: ClosureInstantiation, invocation: MethodInsnNode, stackHeight: Int, localsForCapturedValues: LocalsList, argumentLocalsList: LocalsList): Unit = { + val ownerMethod = closureInit.ownerMethod + val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod + + // store arguments + insertStoreOps(invocation, ownerMethod, argumentLocalsList) + + // drop the closure from the stack + ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP)) + + // load captured values and arguments + insertLoadOps(invocation, ownerMethod, localsForCapturedValues) + insertLoadOps(invocation, ownerMethod, argumentLocalsList) + + // update maxStack + val capturesStackSize = localsForCapturedValues.size + val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone + if (invocationStackHeight > ownerMethod.maxStack) + ownerMethod.maxStack = invocationStackHeight + + // replace the callsite with a new call to the body method + val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match { + case H_INVOKEVIRTUAL => INVOKEVIRTUAL + case H_INVOKESTATIC => INVOKESTATIC + case H_INVOKESPECIAL => INVOKESPECIAL + case H_INVOKEINTERFACE => INVOKEINTERFACE + case H_NEWINVOKESPECIAL => + val insns = ownerMethod.instructions + insns.insertBefore(invocation, new TypeInsnNode(NEW, lambdaBodyHandle.getOwner)) + insns.insertBefore(invocation, new InsnNode(DUP)) + INVOKESPECIAL } + val isInterface = bodyOpcode == INVOKEINTERFACE + val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface) + ownerMethod.instructions.insertBefore(invocation, bodyInvocation) + + val returnType = Type.getReturnType(lambdaBodyHandle.getDesc) + fixLoadedNothingOrNullValue(returnType, bodyInvocation, ownerMethod, btypes) // see comment of that method + + ownerMethod.instructions.remove(invocation) + + // update the call graph + val originalCallsite = callGraph.callsites.remove(invocation) + + // the method node is needed for building the call graph entry + val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc) + def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false) + val bodyMethodCallsite = Callsite( + callsiteInstruction = bodyInvocation, + callsiteMethod = ownerMethod, + callsiteClass = closureInit.ownerClass, + callee = bodyMethod.map({ + case (bodyMethodNode, bodyMethodDeclClass) => Callee( + callee = bodyMethodNode, + calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass), + safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled, + safeToRewrite = false, // the lambda body method is not a trait interface method + annotatedInline = false, + annotatedNoInline = false, + calleeInfoWarning = None) + }), + argInfos = Nil, + callsiteStackHeight = invocationStackHeight, + receiverKnownNotNull = true, // see below (*) + callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition) + ) + // (*) The documentation in class LambdaMetafactory says: + // "if implMethod corresponds to an instance method, the first capture argument + // (corresponding to the receiver) must be non-null" + // Explanation: If the lambda body method is non-static, the receiver is a captured + // value. It can only be captured within some instance method, so we know it's non-null. + callGraph.callsites(bodyInvocation) = bodyMethodCallsite } /** - * Stores the values captured by a closure creation into fresh local variables. - * Returns the list of locals holding the captured values. + * Stores the values captured by a closure creation into fresh local variables, and loads the + * values back onto the stack. Returns the list of locals holding the captured values. */ - private def storeCaptures(indy: InvokeDynamicInsnNode, methodNode: MethodNode): LocalsList = { + private def storeCaptures(closureInit: ClosureInstantiation): LocalsList = { + val indy = closureInit.lambdaMetaFactoryCall.indy val capturedTypes = Type.getArgumentTypes(indy.desc) - val firstCaptureLocal = methodNode.maxLocals + val firstCaptureLocal = closureInit.ownerMethod.maxLocals // This could be optimized: in many cases the captured values are produced by LOAD instructions. // If the variable is not modified within the method, we could avoid introducing yet another @@ -144,10 +287,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy // instruction match exactly the corresponding parameter types in the body method. val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None) - methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size + closureInit.ownerMethod.maxLocals = firstCaptureLocal + localsForCaptures.size - insertStoreOps(indy, methodNode, localsForCaptures) - insertLoadOps(indy, methodNode, localsForCaptures) + insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures) + insertLoadOps(indy, closureInit.ownerMethod, localsForCaptures) localsForCaptures } @@ -184,145 +327,6 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { } } - def rewriteClosureApplyInvocations(indy: InvokeDynamicInsnNode, methodNode: MethodNode, ownerClass: ClassBType): List[RewriteClosureApplyToClosureBodyFailed] = { - val lambdaBodyHandle = indy.bsmArgs(1).asInstanceOf[Handle] // safe, checked in isClosureInstantiation - - // Kept as a lazy val to make sure the analysis is only computed if it's actually needed. - // ProdCons is used to identify closure body invocations (see isSamInvocation), but only if the - // callsite has the right name and signature. If the method has no invcation instruction with - // the right name and signature, the analysis is not executed. - lazy val prodCons = new ProdConsAnalyzer(methodNode, ownerClass.internalName) - - // First collect all callsites without modifying the instructions list yet. - // Once we start modifying the instruction list, prodCons becomes unusable. - - // A list of callsites and stack heights. If the invocation cannot be rewritten, a warning - // message is stored in the stack height value. - val invocationsToRewrite: List[(MethodInsnNode, Either[RewriteClosureApplyToClosureBodyFailed, Int])] = methodNode.instructions.iterator.asScala.collect({ - case invocation: MethodInsnNode if isSamInvocation(invocation, indy, prodCons) => - val bodyAccessible: Either[OptimizerWarning, Boolean] = for { - (bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)] - isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass) - } yield { - isAccessible - } - - def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition) - val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match { - case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w)) - case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName)) - case _ => Right(prodCons.frameAt(invocation).getStackSize) - } - - (invocation, stackSize) - }).toList - - if (invocationsToRewrite.isEmpty) Nil - else { - // lazy val to make sure locals for captures and arguments are only allocated if there's - // effectively a callsite to rewrite. - lazy val (localsForCapturedValues, argumentLocalsList) = { - val captureLocals = storeCaptures(indy, methodNode) - - // allocate locals for storing the arguments of the closure apply callsites. - // if there are multiple callsites, the same locals are re-used. - val argTypes = indy.bsmArgs(0).asInstanceOf[Type].getArgumentTypes // safe, checked in isClosureInstantiation - val firstArgLocal = methodNode.maxLocals - - // The comment in `isClosureInstantiation` explains why we have to introduce casts for - // arguments that have different types in samMethodType and instantiatedMethodType. - val castLoadTypes = { - val instantiatedMethodType = indy.bsmArgs(2).asInstanceOf[Type] - (argTypes, instantiatedMethodType.getArgumentTypes).zipped map { - case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType => - // isClosureInstantiation ensures that the two types are reference types, so we don't - // end up casting primitive values. - Some(instantiatedArgType) - case _ => - None - } - } - val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes) - methodNode.maxLocals = firstArgLocal + argLocals.size - - (captureLocals, argLocals) - } - - val warnings = invocationsToRewrite flatMap { - case (invocation, Left(warning)) => Some(warning) - - case (invocation, Right(stackHeight)) => - // store arguments - insertStoreOps(invocation, methodNode, argumentLocalsList) - - // drop the closure from the stack - methodNode.instructions.insertBefore(invocation, new InsnNode(POP)) - - // load captured values and arguments - insertLoadOps(invocation, methodNode, localsForCapturedValues) - insertLoadOps(invocation, methodNode, argumentLocalsList) - - // update maxStack - val capturesStackSize = localsForCapturedValues.size - val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone - if (invocationStackHeight > methodNode.maxStack) - methodNode.maxStack = invocationStackHeight - - // replace the callsite with a new call to the body method - val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match { - case H_INVOKEVIRTUAL => INVOKEVIRTUAL - case H_INVOKESTATIC => INVOKESTATIC - case H_INVOKESPECIAL => INVOKESPECIAL - case H_INVOKEINTERFACE => INVOKEINTERFACE - case H_NEWINVOKESPECIAL => - val insns = methodNode.instructions - insns.insertBefore(invocation, new TypeInsnNode(NEW, lambdaBodyHandle.getOwner)) - insns.insertBefore(invocation, new InsnNode(DUP)) - INVOKESPECIAL - } - val isInterface = bodyOpcode == INVOKEINTERFACE - val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface) - methodNode.instructions.insertBefore(invocation, bodyInvocation) - methodNode.instructions.remove(invocation) - - // update the call graph - val originalCallsite = callGraph.callsites.remove(invocation) - - // the method node is needed for building the call graph entry - val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc) - def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false) - val bodyMethodCallsite = Callsite( - callsiteInstruction = bodyInvocation, - callsiteMethod = methodNode, - callsiteClass = ownerClass, - callee = bodyMethod.map({ - case (bodyMethodNode, bodyMethodDeclClass) => Callee( - callee = bodyMethodNode, - calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass), - safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled, - safeToRewrite = false, // the lambda body method is not a trait interface method - annotatedInline = false, - annotatedNoInline = false, - calleeInfoWarning = None) - }), - argInfos = Nil, - callsiteStackHeight = invocationStackHeight, - receiverKnownNotNull = true, // see below (*) - callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition) - ) - // (*) The documentation in class LambdaMetafactory says: - // "if implMethod corresponds to an instance method, the first capture argument - // (corresponding to the receiver) must be non-null" - // Explanation: If the lambda body method is non-static, the receiver is a captured - // value. It can only be captured within some instance method, so we know it's non-null. - callGraph.callsites(bodyInvocation) = bodyMethodCallsite - None - } - - warnings.toList - } - } - /** * A list of local variables. Each local stores information about its type, see class [[Local]]. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index e8e848161c..8477f5461a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -9,6 +9,7 @@ package opt import scala.annotation.tailrec import scala.tools.asm +import asm.Handle import asm.Opcodes._ import asm.tree._ import scala.collection.convert.decorateAsScala._ @@ -455,9 +456,9 @@ class Inliner[BT <: BTypes](val btypes: BT) { case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.get(indy) match { - case Some((methodNode, ownerClass)) => + case Some(closureInit) => val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode] - callGraph.closureInstantiations(newIndy) = (callsiteMethod, callsiteClass) + callGraph.closureInstantiations(newIndy) = ClosureInstantiation(closureInit.lambdaMetaFactoryCall.copy(indy = newIndy), callsiteMethod, callsiteClass) case None => } @@ -687,9 +688,67 @@ class Inliner[BT <: BTypes](val btypes: BT) { } } - case ivd: InvokeDynamicInsnNode => - // TODO @lry check necessary conditions to inline an indy, instead of giving up - Right(false) + case _: InvokeDynamicInsnNode if destinationClass == calleeDeclarationClass => + // within the same class, any indy instruction can be inlined + Right(true) + + // does the InvokeDynamicInsnNode call LambdaMetaFactory? + case LambdaMetaFactoryCall(_, _, implMethod, _) => + // an indy instr points to a "call site specifier" (CSP) [1] + // - a reference to a bootstrap method [2] + // - bootstrap method name + // - references to constant arguments, which can be: + // - constant (string, long, int, float, double) + // - class + // - method type (without name) + // - method handle + // - a method name+type + // + // execution [3] + // - resolve the CSP, yielding the boostrap method handle, the static args and the name+type + // - resolution entails accessibility checking [4] + // - execute the `invoke` method of the boostrap method handle (which is signature polymorphic, check its javadoc) + // - the descriptor for the call is made up from the actual arguments on the stack: + // - the first parameters are "MethodHandles.Lookup, String, MethodType", then the types of the constant arguments, + // - the return type is CallSite + // - the values for the call are + // - the bootstrap method handle of the CSP is the receiver + // - the Lookup object for the class in which the callsite occurs (obtained as through calling MethodHandles.lookup()) + // - the method name of the CSP + // - the method type of the CSP + // - the constants of the CSP (primitives are not boxed) + // - the resulting `CallSite` object + // - has as `type` the method type of the CSP + // - is popped from the operand stack + // - the `invokeExact` method (signature polymorphic!) of the `target` method handle of the CallSite is invoked + // - the method descriptor is that of the CSP + // - the receiver is the target of the CallSite + // - the other argument values are those that were on the operand stack at the indy instruction (indyLambda: the captured values) + // + // [1] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.10 + // [2] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.23 + // [3] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokedynamic + // [4] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3 + + // We cannot generically check if an `invokedynamic` instruction can be safely inlined into + // a different class, that depends on the bootstrap method. The Lookup object passed to the + // bootstrap method is a capability to access private members of the callsite class. We can + // only move the invokedynamic to a new class if we know that the bootstrap method doesn't + // use this capability for otherwise non-accessible members. + // In the case of indyLambda, it depends on the visibility of the implMethod handle. If + // the implMethod is public, lambdaMetaFactory doesn't use the Lookup object's extended + // capability, and we can safely inline the instruction into a different class. + + val methodRefClass = classBTypeFromParsedClassfile(implMethod.getOwner) + for { + (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, implMethod.getName, implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)] + methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode) + res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass, destinationClass) + } yield { + res + } + + case _: InvokeDynamicInsnNode => Left(UnknownInvokeDynamicInstruction) case ci: LdcInsnNode => ci.cst match { case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass) diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 9708cba281..eb25eb6e06 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -370,7 +370,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { flags |= Flags.FINAL in.nextToken() case DEFAULT => - flags |= Flags.DEFAULTMETHOD + flags |= Flags.JAVA_DEFAULTMETHOD in.nextToken() case NATIVE => addAnnot(NativeAttr) @@ -489,8 +489,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { val vparams = formalParams() if (!isVoid) rtpt = optArrayBrackets(rtpt) optThrows() - val isStatic = mods hasFlag Flags.STATIC - val bodyOk = !inInterface || ((mods hasFlag Flags.DEFAULTMETHOD) || isStatic) + val isConcreteInterfaceMethod = !inInterface || (mods hasFlag Flags.JAVA_DEFAULTMETHOD) || (mods hasFlag Flags.STATIC) + val bodyOk = !(mods1 hasFlag Flags.DEFERRED) && isConcreteInterfaceMethod val body = if (bodyOk && in.token == LBRACE) { methodBody() @@ -509,7 +509,9 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { EmptyTree } } - if (inInterface && !isStatic) mods1 |= Flags.DEFERRED + // for abstract methods (of classes), the `DEFERRED` flag is alredy set. + // here we also set it for interface methods that are not static and not default. + if (!isConcreteInterfaceMethod) mods1 |= Flags.DEFERRED List { atPos(pos) { DefDef(mods1, name.toTermName, tparams, List(vparams), rtpt, body) @@ -749,7 +751,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { val (statics, body) = typeBody(AT, name) val templ = makeTemplate(annotationParents, body) addCompanionObject(statics, atPos(pos) { - ClassDef(mods, name, List(), templ) + ClassDef(mods | Flags.JAVA_ANNOTATION, name, List(), templ) }) } @@ -807,7 +809,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { if (hasAbstractMember) Flags.ABSTRACT else 0l } addCompanionObject(consts ::: statics ::: predefs, atPos(pos) { - ClassDef(mods | Flags.ENUM | finalFlag | abstractFlag, name, List(), + ClassDef(mods | Flags.JAVA_ENUM | finalFlag | abstractFlag, name, List(), makeTemplate(superclazz :: interfaces, body)) }) } @@ -828,7 +830,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { skipAhead() accept(RBRACE) } - ValDef(Modifiers(Flags.ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr) + ValDef(Modifiers(Flags.JAVA_ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr) } (res, hasClassBody) } diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 660028eab8..06a0299d2a 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -284,7 +284,7 @@ abstract class ClassfileParser { def getType(index: Int): Type = getType(null, index) def getType(sym: Symbol, index: Int): Type = sigToType(sym, getExternalName(index)) - def getSuperClass(index: Int): Symbol = if (index == 0) AnyClass else getClassSymbol(index) + def getSuperClass(index: Int): Symbol = if (index == 0) AnyClass else getClassSymbol(index) // the only classfile that is allowed to have `0` in the super_class is java/lang/Object (see jvm spec) private def createConstant(index: Int): Constant = { val start = starts(index) @@ -862,7 +862,7 @@ abstract class ClassfileParser { srcfile0 = settings.outputDirs.srcFilesFor(in.file, srcpath).find(_.exists) case tpnme.CodeATTR => if (sym.owner.isInterface) { - sym setFlag DEFAULTMETHOD + sym setFlag JAVA_DEFAULTMETHOD log(s"$sym in ${sym.owner} is a java8+ default method.") } in.skip(attrLen) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index c1655467e9..4ad81b60ae 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -145,8 +145,8 @@ trait Namers extends MethodSynthesis { // while Scala's enum constants live directly in the class. // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal // cyclic reference error. See the commit message for details. - if (context.unit.isJava) owner.companionClass.hasEnumFlag else owner.hasEnumFlag - vd.mods.hasAllFlags(ENUM | STABLE | STATIC) && ownerHasEnumFlag + if (context.unit.isJava) owner.companionClass.hasJavaEnumFlag else owner.hasJavaEnumFlag + vd.mods.hasAllFlags(JAVA_ENUM | STABLE | STATIC) && ownerHasEnumFlag } def setPrivateWithin[T <: Symbol](tree: Tree, sym: T, mods: Modifiers): T = diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 36423fa2aa..0198529ef7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -421,7 +421,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans overrideError("cannot be used here - classes can only override abstract types") } else if (other.isEffectivelyFinal) { // (1.2) overrideError("cannot override final member") - } else if (!other.isDeferredOrDefault && !other.hasFlag(DEFAULTMETHOD) && !member.isAnyOverride && !member.isSynthetic) { // (*) + } else if (!other.isDeferredOrJavaDefault && !other.hasFlag(JAVA_DEFAULTMETHOD) && !member.isAnyOverride && !member.isSynthetic) { // (*) // (*) Synthetic exclusion for (at least) default getters, fixes SI-5178. We cannot assign the OVERRIDE flag to // the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket. if (isNeitherInClass && !(other.owner isSubClass member.owner)) @@ -604,7 +604,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def checkNoAbstractMembers(): Unit = { // Avoid spurious duplicates: first gather any missing members. def memberList = clazz.info.nonPrivateMembersAdmitting(VBRIDGE) - val (missing, rest) = memberList partition (m => m.isDeferredNotDefault && !ignoreDeferred(m)) + val (missing, rest) = memberList partition (m => m.isDeferredNotJavaDefault && !ignoreDeferred(m)) // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name) diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala index 90f314428b..b4c686473b 100644 --- a/src/partest-extras/scala/tools/partest/ASMConverters.scala +++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala @@ -58,21 +58,24 @@ object ASMConverters { case class Method(instructions: List[Instruction], handlers: List[ExceptionHandler], localVars: List[LocalVariable]) - case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction - case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction - case class Op (opcode: Int) extends Instruction - case class IntOp (opcode: Int, operand: Int) extends Instruction - case class Jump (opcode: Int, label: Label) extends Instruction - case class Ldc (opcode: Int, cst: Any) extends Instruction - case class LookupSwitch(opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction - case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction - case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction - case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction - case class TypeOp (opcode: Int, desc: String) extends Instruction - case class VarOp (opcode: Int, `var`: Int) extends Instruction - case class Label (offset: Int) extends Instruction { def opcode: Int = -1 } - case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 } - case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 } + case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction + case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction + case class Op (opcode: Int) extends Instruction + case class IntOp (opcode: Int, operand: Int) extends Instruction + case class Jump (opcode: Int, label: Label) extends Instruction + case class Ldc (opcode: Int, cst: Any) extends Instruction + case class LookupSwitch (opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction + case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction + case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction + case class InvokeDynamic(opcode: Int, name: String, desc: String, bsm: MethodHandle, bsmArgs: List[AnyRef]) extends Instruction + case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction + case class TypeOp (opcode: Int, desc: String) extends Instruction + case class VarOp (opcode: Int, `var`: Int) extends Instruction + case class Label (offset: Int) extends Instruction { def opcode: Int = -1 } + case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 } + case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 } + + case class MethodHandle(tag: Int, owner: String, name: String, desc: String) case class ExceptionHandler(start: Label, end: Label, handler: Label, desc: Option[String]) case class LocalVariable(name: String, desc: String, signature: Option[String], start: Label, end: Label, index: Int) @@ -111,6 +114,7 @@ object ASMConverters { case i: t.LookupSwitchInsnNode => LookupSwitch (op(i), applyLabel(i.dflt), lst(i.keys) map (x => x: Int), lst(i.labels) map applyLabel) case i: t.TableSwitchInsnNode => TableSwitch (op(i), i.min, i.max, applyLabel(i.dflt), lst(i.labels) map applyLabel) case i: t.MethodInsnNode => Invoke (op(i), i.owner, i.name, i.desc, i.itf) + case i: t.InvokeDynamicInsnNode => InvokeDynamic(op(i), i.name, i.desc, convertMethodHandle(i.bsm), convertBsmArgs(i.bsmArgs)) case i: t.MultiANewArrayInsnNode => NewArray (op(i), i.desc, i.dims) case i: t.TypeInsnNode => TypeOp (op(i), i.desc) case i: t.VarInsnNode => VarOp (op(i), i.`var`) @@ -119,6 +123,13 @@ object ASMConverters { case i: t.LineNumberNode => LineNumber (i.line, applyLabel(i.start)) } + private def convertBsmArgs(a: Array[Object]): List[Object] = a.map({ + case h: asm.Handle => convertMethodHandle(h) + case _ => a // can be: Class, method Type, primitive constant + })(collection.breakOut) + + private def convertMethodHandle(h: asm.Handle): MethodHandle = MethodHandle(h.getTag, h.getOwner, h.getName, h.getDesc) + private def convertHandlers(method: t.MethodNode): List[ExceptionHandler] = { method.tryCatchBlocks.asScala.map(h => ExceptionHandler(applyLabel(h.start), applyLabel(h.end), applyLabel(h.handler), Option(h.`type`)))(collection.breakOut) } @@ -197,21 +208,28 @@ object ASMConverters { case x => x.asInstanceOf[Object] } + def unconvertMethodHandle(h: MethodHandle): asm.Handle = new asm.Handle(h.tag, h.owner, h.name, h.desc) + def unconvertBsmArgs(a: List[Object]): Array[Object] = a.map({ + case h: MethodHandle => unconvertMethodHandle(h) + case o => o + })(collection.breakOut) + private def visitMethod(method: t.MethodNode, instruction: Instruction, asmLabel: Map[Label, asm.Label]): Unit = instruction match { - case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc) - case Incr(op, vr, incr) => method.visitIincInsn(vr, incr) - case Op(op) => method.visitInsn(op) - case IntOp(op, operand) => method.visitIntInsn(op, operand) - case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label)) - case Ldc(op, cst) => method.visitLdcInsn(cst) - case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray) - case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*) - case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf) - case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims) - case TypeOp(op, desc) => method.visitTypeInsn(op, desc) - case VarOp(op, vr) => method.visitVarInsn(op, vr) - case l: Label => method.visitLabel(asmLabel(l)) - case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray) - case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start)) + case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc) + case Incr(op, vr, incr) => method.visitIincInsn(vr, incr) + case Op(op) => method.visitInsn(op) + case IntOp(op, operand) => method.visitIntInsn(op, operand) + case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label)) + case Ldc(op, cst) => method.visitLdcInsn(cst) + case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray) + case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*) + case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf) + case InvokeDynamic(op, name, desc, bsm, bsmArgs) => method.visitInvokeDynamicInsn(name, desc, unconvertMethodHandle(bsm), unconvertBsmArgs(bsmArgs)) + case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims) + case TypeOp(op, desc) => method.visitTypeInsn(op, desc) + case VarOp(op, vr) => method.visitVarInsn(op, vr) + case l: Label => method.visitLabel(asmLabel(l)) + case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray) + case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start)) } } diff --git a/src/reflect/scala/reflect/internal/ClassfileConstants.scala b/src/reflect/scala/reflect/internal/ClassfileConstants.scala index 53241fb15b..e5d97e8959 100644 --- a/src/reflect/scala/reflect/internal/ClassfileConstants.scala +++ b/src/reflect/scala/reflect/internal/ClassfileConstants.scala @@ -344,7 +344,8 @@ object ClassfileConstants { case JAVA_ACC_STATIC => STATIC case JAVA_ACC_ABSTRACT => if (isAnnotation) 0L else if (isClass) ABSTRACT else DEFERRED case JAVA_ACC_INTERFACE => if (isAnnotation) 0L else TRAIT | INTERFACE | ABSTRACT - case JAVA_ACC_ENUM => ENUM + case JAVA_ACC_ENUM => JAVA_ENUM + case JAVA_ACC_ANNOTATION => JAVA_ANNOTATION case _ => 0L } private def translateFlags(jflags: Int, baseFlags: Long, isClass: Boolean): Long = { @@ -360,6 +361,7 @@ object ClassfileConstants { res |= translateFlag0(jflags & JAVA_ACC_ABSTRACT) res |= translateFlag0(jflags & JAVA_ACC_INTERFACE) res |= translateFlag0(jflags & JAVA_ACC_ENUM) + res |= translateFlag0(jflags & JAVA_ACC_ANNOTATION) res } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 4e0fc1e36e..0bdf5b4647 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -815,7 +815,7 @@ trait Definitions extends api.StandardDefinitions { // must filter out "universal" members (getClass is deferred for some reason) val deferredMembers = ( tp membersBasedOnFlags (excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD) - filter (mem => mem.isDeferredNotDefault && !isUniversalMember(mem)) // TODO: test + filter (mem => mem.isDeferredNotJavaDefault && !isUniversalMember(mem)) // TODO: test ) // if there is only one, it's monomorphic and has a single argument list diff --git a/src/reflect/scala/reflect/internal/FlagSets.scala b/src/reflect/scala/reflect/internal/FlagSets.scala index ef9c77878f..b6521634fb 100644 --- a/src/reflect/scala/reflect/internal/FlagSets.scala +++ b/src/reflect/scala/reflect/internal/FlagSets.scala @@ -42,7 +42,7 @@ trait FlagSets extends api.FlagSets { self: SymbolTable => val DEFAULTPARAM : FlagSet = Flags.DEFAULTPARAM val PRESUPER : FlagSet = Flags.PRESUPER val DEFAULTINIT : FlagSet = Flags.DEFAULTINIT - val ENUM : FlagSet = Flags.ENUM + val ENUM : FlagSet = Flags.JAVA_ENUM val PARAMACCESSOR : FlagSet = Flags.PARAMACCESSOR val CASEACCESSOR : FlagSet = Flags.CASEACCESSOR val SYNTHETIC : FlagSet = Flags.SYNTHETIC diff --git a/src/reflect/scala/reflect/internal/Flags.scala b/src/reflect/scala/reflect/internal/Flags.scala index 1707061817..754b96a9dd 100644 --- a/src/reflect/scala/reflect/internal/Flags.scala +++ b/src/reflect/scala/reflect/internal/Flags.scala @@ -15,65 +15,65 @@ import scala.collection.{ mutable, immutable } // // Generated by mkFlagsTable() at Thu Feb 02 20:31:52 PST 2012 // -// 0: PROTECTED/M -// 1: OVERRIDE/M -// 2: PRIVATE/M -// 3: ABSTRACT/M -// 4: DEFERRED/M -// 5: FINAL/M -// 6: METHOD -// 7: INTERFACE/M -// 8: MODULE -// 9: IMPLICIT/M -// 10: SEALED/M -// 11: CASE/M -// 12: MUTABLE/M -// 13: PARAM/M -// 14: PACKAGE -// 15: MACRO/M -// 16: BYNAMEPARAM/M CAPTURED COVARIANT/M -// 17: CONTRAVARIANT/M INCONSTRUCTOR LABEL -// 18: ABSOVERRIDE/M -// 19: LOCAL/M -// 20: JAVA/M -// 21: SYNTHETIC -// 22: STABLE -// 23: STATIC/M -// 24: CASEACCESSOR/M -// 25: DEFAULTPARAM/M TRAIT/M -// 26: BRIDGE -// 27: ACCESSOR -// 28: SUPERACCESSOR -// 29: PARAMACCESSOR/M -// 30: MODULEVAR -// 31: LAZY/M -// 32: IS_ERROR -// 33: OVERLOADED -// 34: LIFTED -// 35: EXISTENTIAL MIXEDIN -// 36: EXPANDEDNAME -// 37: IMPLCLASS PRESUPER/M -// 38: TRANS_FLAG -// 39: LOCKED -// 40: SPECIALIZED -// 41: DEFAULTINIT/M -// 42: VBRIDGE -// 43: VARARGS -// 44: TRIEDCOOKING -// 45: SYNCHRONIZED/M -// 46: ARTIFACT -// 47: DEFAULTMETHOD/M -// 48: ENUM -// 49: +// 0: PROTECTED/M +// 1: OVERRIDE/M +// 2: PRIVATE/M +// 3: ABSTRACT/M +// 4: DEFERRED/M +// 5: FINAL/M +// 6: METHOD +// 7: INTERFACE/M +// 8: MODULE +// 9: IMPLICIT/M +// 10: SEALED/M +// 11: CASE/M +// 12: MUTABLE/M +// 13: PARAM/M +// 14: PACKAGE +// 15: MACRO/M +// 16: BYNAMEPARAM/M CAPTURED COVARIANT/M +// 17: CONTRAVARIANT/M INCONSTRUCTOR LABEL +// 18: ABSOVERRIDE/M +// 19: LOCAL/M +// 20: JAVA/M +// 21: SYNTHETIC +// 22: STABLE +// 23: STATIC/M +// 24: CASEACCESSOR/M +// 25: DEFAULTPARAM/M TRAIT/M +// 26: BRIDGE +// 27: ACCESSOR +// 28: SUPERACCESSOR +// 29: PARAMACCESSOR/M +// 30: MODULEVAR +// 31: LAZY/M +// 32: IS_ERROR +// 33: OVERLOADED +// 34: LIFTED +// 35: EXISTENTIAL MIXEDIN +// 36: EXPANDEDNAME +// 37: IMPLCLASS PRESUPER/M +// 38: TRANS_FLAG +// 39: LOCKED +// 40: SPECIALIZED +// 41: DEFAULTINIT/M +// 42: VBRIDGE +// 43: VARARGS +// 44: TRIEDCOOKING +// 45: SYNCHRONIZED/M +// 46: ARTIFACT +// 47: JAVA_DEFAULTMETHOD/M +// 48: JAVA_ENUM +// 49: JAVA_ANNOTATION // 50: -// 51: lateDEFERRED -// 52: lateFINAL -// 53: lateMETHOD -// 54: lateINTERFACE -// 55: lateMODULE -// 56: notPROTECTED -// 57: notOVERRIDE -// 58: notPRIVATE +// 51: lateDEFERRED +// 52: lateFINAL +// 53: lateMETHOD +// 54: lateINTERFACE +// 55: lateMODULE +// 56: notPROTECTED +// 57: notOVERRIDE +// 58: notPRIVATE // 59: // 60: // 61: @@ -119,8 +119,9 @@ class ModifierFlags { final val DEFAULTINIT = 1L << 41 // symbol is initialized to the default value: used by -Xcheckinit final val ARTIFACT = 1L << 46 // symbol should be ignored when typechecking; will be marked ACC_SYNTHETIC in bytecode // to see which symbols are marked as ARTIFACT, see scaladocs for FlagValues.ARTIFACT - final val DEFAULTMETHOD = 1L << 47 // symbol is a java default method - final val ENUM = 1L << 48 // symbol is an enum + final val JAVA_DEFAULTMETHOD = 1L << 47 // symbol is a java default method + final val JAVA_ENUM = 1L << 48 // symbol is a java enum + final val JAVA_ANNOTATION = 1L << 49 // symbol is a java annotation // Overridden. def flagToString(flag: Long): String = "" @@ -172,12 +173,28 @@ class Flags extends ModifierFlags { final val SYNCHRONIZED = 1L << 45 // symbol is a method which should be marked ACC_SYNCHRONIZED // ------- shift definitions ------------------------------------------------------- + // + // Flags from 1L to (1L << 50) are normal flags. + // + // The flags DEFERRED (1L << 4) to MODULE (1L << 8) have a `late` counterpart. Late flags change + // their counterpart from 0 to 1 after a specific phase (see below). The first late flag + // (lateDEFERRED) is at (1L << 51), i.e., late flags are shifted by 47. The last one is (1L << 55). + // + // The flags PROTECTED (1L) to PRIVATE (1L << 2) have a `not` counterpart. Negated flags change + // their counterpart from 1 to 0 after a specific phase (see below). They are shifted by 56, i.e., + // the first negated flag (notPROTECTED) is at (1L << 56), the last at (1L << 58). + // + // Late and negative flags are only enabled after certain phases, implemented by the phaseNewFlags + // method of the SubComponent, so they implement a bit of a flag history. + // + // The flags (1L << 59) to (1L << 63) are currently unused. If added to the InitialFlags mask, + // they could be used as normal flags. - final val InitialFlags = 0x0001FFFFFFFFFFFFL // flags that are enabled from phase 1. - final val LateFlags = 0x00FE000000000000L // flags that override flags in 0x1FC. - final val AntiFlags = 0x7F00000000000000L // flags that cancel flags in 0x07F - final val LateShift = 47L - final val AntiShift = 56L + final val InitialFlags = 0x0007FFFFFFFFFFFFL // normal flags, enabled from the first phase: 1L to (1L << 50) + final val LateFlags = 0x00F8000000000000L // flags that override flags in (1L << 4) to (1L << 8): DEFERRED, FINAL, INTERFACE, METHOD, MODULE + final val AntiFlags = 0x0700000000000000L // flags that cancel flags in 1L to (1L << 2): PROTECTED, OVERRIDE, PRIVATE + final val LateShift = 47 + final val AntiShift = 56 // Flags which sketchily share the same slot // 16: BYNAMEPARAM/M CAPTURED COVARIANT/M @@ -243,7 +260,7 @@ class Flags extends ModifierFlags { */ final val ExplicitFlags = PRIVATE | PROTECTED | ABSTRACT | FINAL | SEALED | - OVERRIDE | CASE | IMPLICIT | ABSOVERRIDE | LAZY | DEFAULTMETHOD + OVERRIDE | CASE | IMPLICIT | ABSOVERRIDE | LAZY | JAVA_DEFAULTMETHOD /** The two bridge flags */ final val BridgeFlags = BRIDGE | VBRIDGE @@ -434,9 +451,9 @@ class Flags extends ModifierFlags { case TRIEDCOOKING => "<triedcooking>" // (1L << 44) case SYNCHRONIZED => "<synchronized>" // (1L << 45) case ARTIFACT => "<artifact>" // (1L << 46) - case DEFAULTMETHOD => "<defaultmethod>" // (1L << 47) - case ENUM => "<enum>" // (1L << 48) - case 0x2000000000000L => "" // (1L << 49) + case JAVA_DEFAULTMETHOD => "<defaultmethod>" // (1L << 47) + case JAVA_ENUM => "<enum>" // (1L << 48) + case JAVA_ANNOTATION => "<annotation>" // (1L << 49) case 0x4000000000000L => "" // (1L << 50) case `lateDEFERRED` => "<latedeferred>" // (1L << 51) case `lateFINAL` => "<latefinal>" // (1L << 52) diff --git a/src/reflect/scala/reflect/internal/HasFlags.scala b/src/reflect/scala/reflect/internal/HasFlags.scala index aa8f4c532e..5162b15206 100644 --- a/src/reflect/scala/reflect/internal/HasFlags.scala +++ b/src/reflect/scala/reflect/internal/HasFlags.scala @@ -79,49 +79,50 @@ trait HasFlags { // Tests which come through cleanly: both Symbol and Modifiers use these // identically, testing for a single flag. - def hasAbstractFlag = hasFlag(ABSTRACT) - def hasAccessorFlag = hasFlag(ACCESSOR) - def hasDefault = hasFlag(DEFAULTPARAM) && hasFlag(METHOD | PARAM) // Second condition disambiguates with TRAIT - def hasEnumFlag = hasFlag(ENUM) + def hasAbstractFlag = hasFlag(ABSTRACT) + def hasAccessorFlag = hasFlag(ACCESSOR) + def hasDefault = hasFlag(DEFAULTPARAM) && hasFlag(METHOD | PARAM) // Second condition disambiguates with TRAIT + def hasJavaEnumFlag = hasFlag(JAVA_ENUM) + def hasJavaAnnotationFlag = hasFlag(JAVA_ANNOTATION) @deprecated("Use isLocalToThis instead", "2.11.0") - def hasLocalFlag = hasFlag(LOCAL) - def isLocalToThis = hasFlag(LOCAL) - def hasModuleFlag = hasFlag(MODULE) - def hasPackageFlag = hasFlag(PACKAGE) - def hasStableFlag = hasFlag(STABLE) - def hasStaticFlag = hasFlag(STATIC) - def isAbstractOverride = hasFlag(ABSOVERRIDE) - def isAnyOverride = hasFlag(OVERRIDE | ABSOVERRIDE) - def isCase = hasFlag(CASE) - def isCaseAccessor = hasFlag(CASEACCESSOR) - def isDeferred = hasFlag(DEFERRED) - def isFinal = hasFlag(FINAL) - def isArtifact = hasFlag(ARTIFACT) - def isImplicit = hasFlag(IMPLICIT) - def isInterface = hasFlag(INTERFACE) - def isJavaDefined = hasFlag(JAVA) - def isLabel = hasAllFlags(LABEL | METHOD) && !hasAccessorFlag - def isLazy = hasFlag(LAZY) - def isLifted = hasFlag(LIFTED) - def isMacro = hasFlag(MACRO) - def isMutable = hasFlag(MUTABLE) - def isOverride = hasFlag(OVERRIDE) - def isParamAccessor = hasFlag(PARAMACCESSOR) - def isPrivate = hasFlag(PRIVATE) + def hasLocalFlag = hasFlag(LOCAL) + def isLocalToThis = hasFlag(LOCAL) + def hasModuleFlag = hasFlag(MODULE) + def hasPackageFlag = hasFlag(PACKAGE) + def hasStableFlag = hasFlag(STABLE) + def hasStaticFlag = hasFlag(STATIC) + def isAbstractOverride = hasFlag(ABSOVERRIDE) + def isAnyOverride = hasFlag(OVERRIDE | ABSOVERRIDE) + def isCase = hasFlag(CASE) + def isCaseAccessor = hasFlag(CASEACCESSOR) + def isDeferred = hasFlag(DEFERRED) + def isFinal = hasFlag(FINAL) + def isArtifact = hasFlag(ARTIFACT) + def isImplicit = hasFlag(IMPLICIT) + def isInterface = hasFlag(INTERFACE) + def isJavaDefined = hasFlag(JAVA) + def isLabel = hasAllFlags(LABEL | METHOD) && !hasAccessorFlag + def isLazy = hasFlag(LAZY) + def isLifted = hasFlag(LIFTED) + def isMacro = hasFlag(MACRO) + def isMutable = hasFlag(MUTABLE) + def isOverride = hasFlag(OVERRIDE) + def isParamAccessor = hasFlag(PARAMACCESSOR) + def isPrivate = hasFlag(PRIVATE) @deprecated ("Use `hasPackageFlag` instead", "2.11.0") - def isPackage = hasFlag(PACKAGE) - def isPrivateLocal = hasAllFlags(PrivateLocal) - def isProtected = hasFlag(PROTECTED) - def isProtectedLocal = hasAllFlags(ProtectedLocal) - def isPublic = hasNoFlags(PRIVATE | PROTECTED) && !hasAccessBoundary - def isSealed = hasFlag(SEALED) - def isSpecialized = hasFlag(SPECIALIZED) - def isSuperAccessor = hasFlag(SUPERACCESSOR) - def isSynthetic = hasFlag(SYNTHETIC) - def isTrait = hasFlag(TRAIT) && !hasFlag(PARAM) - - def isDeferredOrDefault = hasFlag(DEFERRED | DEFAULTMETHOD) - def isDeferredNotDefault = isDeferred && !hasFlag(DEFAULTMETHOD) + def isPackage = hasFlag(PACKAGE) + def isPrivateLocal = hasAllFlags(PrivateLocal) + def isProtected = hasFlag(PROTECTED) + def isProtectedLocal = hasAllFlags(ProtectedLocal) + def isPublic = hasNoFlags(PRIVATE | PROTECTED) && !hasAccessBoundary + def isSealed = hasFlag(SEALED) + def isSpecialized = hasFlag(SPECIALIZED) + def isSuperAccessor = hasFlag(SUPERACCESSOR) + def isSynthetic = hasFlag(SYNTHETIC) + def isTrait = hasFlag(TRAIT) && !hasFlag(PARAM) + + def isDeferredOrJavaDefault = hasFlag(DEFERRED | JAVA_DEFAULTMETHOD) + def isDeferredNotJavaDefault = isDeferred && !hasFlag(JAVA_DEFAULTMETHOD) def flagBitsToString(bits: Long): String = { // Fast path for common case diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 2329b30638..56fb1181fe 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -732,7 +732,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def flags: Long = { if (Statistics.hotEnabled) Statistics.incCounter(flagsCount) val fs = _rawflags & phase.flagMask - (fs | ((fs & LateFlags) >>> LateShift)) & ~(fs >>> AntiShift) + (fs | ((fs & LateFlags) >>> LateShift)) & ~((fs & AntiFlags) >>> AntiShift) } def flags_=(fs: Long) = _rawflags = fs def rawflags_=(x: Long) { _rawflags = x } diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 8c32a92ecd..d0670f337a 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -755,6 +755,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive val ifaces = jclazz.getGenericInterfaces.toList map typeToScala val isAnnotation = JavaAccFlags(jclazz).isAnnotation if (isAnnotation) AnnotationClass.tpe :: ClassfileAnnotationClass.tpe :: ifaces + else if (jclazz.isInterface) ObjectTpe :: ifaces // interfaces have Object as superclass in the classfile (see jvm spec), but getGenericSuperclass seems to return null else (if (jsuperclazz == null) AnyTpe else typeToScala(jsuperclazz)) :: ifaces } finally { parentsLevel -= 1 diff --git a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ClassFileParser.scala b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ClassFileParser.scala index cfd750055b..eed76c3774 100644 --- a/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ClassFileParser.scala +++ b/src/scalap/scala/tools/scalap/scalax/rules/scalasig/ClassFileParser.scala @@ -114,6 +114,9 @@ object ClassFileParser extends ByteCodeReader { val methodRef = memberRef("Method") val interfaceMethodRef = memberRef("InterfaceMethod") val nameAndType = u2 ~ u2 ^^ add1 { case name ~ descriptor => pool => "NameAndType: " + pool(name) + ", " + pool(descriptor) } + val methodHandle = u1 ~ u2 ^^ add1 { case referenceKind ~ referenceIndex => pool => "MethodHandle: " + referenceKind + ", " + pool(referenceIndex) } + val methodType = u2 ^^ add1 { case descriptorIndex => pool => "MethodType: " + pool(descriptorIndex) } + val invokeDynamic = u2 ~ u2 ^^ add1 { case bootstrapMethodAttrIndex ~ nameAndTypeIndex => pool => "InvokeDynamic: " + "bootstrapMethodAttrIndex = " + bootstrapMethodAttrIndex + ", " + pool(nameAndTypeIndex) } val constantPoolEntry = u1 >> { case 1 => utf8String @@ -127,6 +130,9 @@ object ClassFileParser extends ByteCodeReader { case 10 => methodRef case 11 => interfaceMethodRef case 12 => nameAndType + case 15 => methodHandle + case 16 => methodType + case 18 => invokeDynamic } val interfaces = u2 >> u2.times diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 376b3c895b..3a6737ca46 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -149,9 +149,7 @@ object Test extends BytecodeTest { def testA11() = { val List(ann) = innerClassNodes("A11") - // in the java class file, the INNERCLASS attribute has more flags (public | static | abstract | interface | annotation) - // the scala compiler has its own interpretation of java annotations ant their flags.. it only emits publicStatic. - assertMember(ann, "JavaAnnot_1", "Ann", flags = publicStatic) + assertMember(ann, "JavaAnnot_1", "Ann", flags = publicAbstractInterface | Flags.ACC_STATIC | Flags.ACC_ANNOTATION) } def testA13() = { diff --git a/test/files/neg/t6013/Base.java b/test/files/neg/t6013/Base.java index b73d7fd821..ce6ee47e64 100644 --- a/test/files/neg/t6013/Base.java +++ b/test/files/neg/t6013/Base.java @@ -2,7 +2,7 @@ abstract public class Base { // This must considered to be overridden by Abstract#foo based // on the erased signatures. This special case is handled by // `javaErasedOverridingSym` in `RefChecks`. - public abstract void bar(java.util.List<java.lang.String> foo) { return; } + public void bar(java.util.List<java.lang.String> foo) { return; } // But, a concrete method in a Java superclass must not excuse // a deferred method in the Java subclass! diff --git a/test/files/pos/t9392/client_2.scala b/test/files/pos/t9392/client_2.scala new file mode 100644 index 0000000000..6b706fea12 --- /dev/null +++ b/test/files/pos/t9392/client_2.scala @@ -0,0 +1,4 @@ +class Client { + Macro() +} + diff --git a/test/files/pos/t9392/macro_1.scala b/test/files/pos/t9392/macro_1.scala new file mode 100644 index 0000000000..3f67ac17b2 --- /dev/null +++ b/test/files/pos/t9392/macro_1.scala @@ -0,0 +1,16 @@ +import language.experimental.macros + + +object Macro { + + import reflect.macros.blackbox.Context + def impl(c: Context)(): c.Tree = { + import c.universe._ + val tree = q"""class C; new C""" + val tree1 = c.typecheck(tree) + val tpe = tree1.tpe + val tree2 = c.typecheck(c.untypecheck(tree1)) + q"""$tree2.asInstanceOf[$tpe]""" + } + def apply(): Any = macro impl +} diff --git a/test/files/pos/t9393/NamedImpl_1.java b/test/files/pos/t9393/NamedImpl_1.java new file mode 100644 index 0000000000..02ec9b4671 --- /dev/null +++ b/test/files/pos/t9393/NamedImpl_1.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com> + */ +package bug; + +import bug.Named_1; +import java.io.Serializable; +import java.lang.annotation.Annotation; + +public class NamedImpl_1 implements Named_1 { + + public Class<? extends Annotation> annotationType() { + return null; + } +} diff --git a/test/files/pos/t9393/NamedImpl_2.java b/test/files/pos/t9393/NamedImpl_2.java new file mode 100644 index 0000000000..c87e94016d --- /dev/null +++ b/test/files/pos/t9393/NamedImpl_2.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com> + */ +package bug; + +import bug.Named_2; +import java.io.Serializable; +import java.lang.annotation.Annotation; + +public class NamedImpl_2 implements Named_2 { + + public Class<? extends Annotation> annotationType() { + return null; + } +} diff --git a/test/files/pos/t9393/Named_1.java b/test/files/pos/t9393/Named_1.java new file mode 100644 index 0000000000..30a6c9839a --- /dev/null +++ b/test/files/pos/t9393/Named_1.java @@ -0,0 +1,3 @@ +package bug; + +public @interface Named_1 {} diff --git a/test/files/pos/t9393/Named_2.java b/test/files/pos/t9393/Named_2.java new file mode 100644 index 0000000000..3210fb636a --- /dev/null +++ b/test/files/pos/t9393/Named_2.java @@ -0,0 +1,3 @@ +package bug; + +public @interface Named_2 {} diff --git a/test/files/pos/t9393/test_2.scala b/test/files/pos/t9393/test_2.scala new file mode 100644 index 0000000000..8ea346129d --- /dev/null +++ b/test/files/pos/t9393/test_2.scala @@ -0,0 +1,4 @@ +class C { + new bug.NamedImpl_1 // separate compilation, testing the classfile parser + new bug.NamedImpl_2 // mixed compilation, testing the java source parser +} diff --git a/test/files/run/scalapInvokedynamic.check b/test/files/run/scalapInvokedynamic.check new file mode 100644 index 0000000000..8e4b08f234 --- /dev/null +++ b/test/files/run/scalapInvokedynamic.check @@ -0,0 +1,5 @@ +class C extends scala.AnyRef { + def this() = { /* compiled code */ } + def m: java.lang.String = { /* compiled code */ } +} + diff --git a/test/files/run/scalapInvokedynamic.scala b/test/files/run/scalapInvokedynamic.scala new file mode 100644 index 0000000000..670cf26662 --- /dev/null +++ b/test/files/run/scalapInvokedynamic.scala @@ -0,0 +1,11 @@ +class C { + def m = { + val f = (x: String) => x.trim + f(" H ae i ") + } +} + +object Test extends App { + val testClassesDir = System.getProperty("partest.output") + scala.tools.scalap.Main.main(Array("-cp", testClassesDir, "C")) +}
\ No newline at end of file diff --git a/test/files/run/t9387.scala b/test/files/run/t9387.scala new file mode 100644 index 0000000000..3e33d19fd2 --- /dev/null +++ b/test/files/run/t9387.scala @@ -0,0 +1,20 @@ +class G[T] +object G { + def v[T](x: T): G[T] = null +} + +class A[T] +object A { + def apply[T](x: => G[T]): A[T] = null +} + +object T { + A[Unit](G.v(() => ())) // Was VerifyError +} + +object Test { + def main(args: Array[String]): Unit = { + T + } + +}
\ No newline at end of file diff --git a/test/files/run/t9387b.check b/test/files/run/t9387b.check new file mode 100644 index 0000000000..6a452c185a --- /dev/null +++ b/test/files/run/t9387b.check @@ -0,0 +1 @@ +() diff --git a/test/files/run/t9387b.scala b/test/files/run/t9387b.scala new file mode 100644 index 0000000000..6339f4caba --- /dev/null +++ b/test/files/run/t9387b.scala @@ -0,0 +1,16 @@ +object T { + val f: Unit = () => () + println(f) +} + +object U { + def f[T](t: T): T = t + f[Unit](() => ()) +} + +object Test { + def main(args: Array[String]): Unit = { + T + U + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala index 9af9ef54fc..a5b3faced8 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala @@ -246,4 +246,46 @@ class ProdConsAnalyzerTest extends ClearAfterClass { testSingleInsn(a.consumersOfOutputsFrom(l2i), "IRETURN") testSingleInsn(a.producersForInputsOf(ret), "L2I") } + + @Test + def cyclicProdCons(): Unit = { + import Opcodes._ + val m = genMethod(descriptor = "(I)I")( + Label(1), + VarOp(ILOAD, 1), + IntOp(BIPUSH, 10), + Op(IADD), // consumer of the above ILOAD + + Op(ICONST_0), + Jump(IF_ICMPNE, Label(2)), + + VarOp(ILOAD, 1), + VarOp(ISTORE, 1), + Jump(GOTO, Label(1)), + + Label(2), + IntOp(BIPUSH, 9), + Op(IRETURN) + ) + m.maxLocals = 2 + m.maxStack = 2 + val a = new ProdConsAnalyzer(m, "C") + + val List(iadd) = findInstr(m, "IADD") + val firstLoad = iadd.getPrevious.getPrevious + assert(firstLoad.getOpcode == ILOAD) + val secondLoad = findInstr(m, "ISTORE").head.getPrevious + assert(secondLoad.getOpcode == ILOAD) + + testSingleInsn(a.producersForValueAt(iadd, 2), "ILOAD") + testSingleInsn(a.initialProducersForValueAt(iadd, 2), "ParameterProducer(1)") + testMultiInsns(a.producersForInputsOf(firstLoad), List("ParameterProducer", "ISTORE")) + testMultiInsns(a.producersForInputsOf(secondLoad), List("ParameterProducer", "ISTORE")) + + testSingleInsn(a.ultimateConsumersOfOutputsFrom(firstLoad), "IADD") + testSingleInsn(a.ultimateConsumersOfOutputsFrom(secondLoad), "IADD") + + testSingleInsn(a.consumersOfOutputsFrom(firstLoad), "IADD") + testSingleInsn(a.consumersOfOutputsFrom(secondLoad), "ISTORE") + } } diff --git a/test/junit/scala/tools/nsc/symtab/FlagsTest.scala b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala new file mode 100644 index 0000000000..fc0e8b0f6b --- /dev/null +++ b/test/junit/scala/tools/nsc/symtab/FlagsTest.scala @@ -0,0 +1,89 @@ +package scala.tools.nsc +package symtab + +import org.junit.Assert._ +import scala.tools.testing.AssertUtil._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class FlagsTest { + object symbolTable extends SymbolTableForUnitTesting + import symbolTable._ + import Flags._ + + def sym = NoSymbol.newTermSymbol(nme.EMPTY) + + def withFlagMask[A](mask: Long)(body: => A): A = enteringPhase(new Phase(NoPhase) { + override def flagMask = mask + def name = "" + def run() = () + })(body) + + def testTimedFlag(flag: Long, test: Symbol => Boolean, enabling: Boolean) = { + assertEquals(withFlagMask(InitialFlags)(test(sym.setFlag(flag))), !enabling) + assertEquals(withFlagMask(InitialFlags | flag)(test(sym.setFlag(flag))), enabling) + } + + def testLate(flag: Long, test: Symbol => Boolean) = testTimedFlag(flag, test, enabling = true) + def testNot(flag: Long, test: Symbol => Boolean) = testTimedFlag(flag, test, enabling = false) + + @Test + def testTimedFlags(): Unit = { + testLate(lateDEFERRED, _.isDeferred) + testLate(lateFINAL, _.isFinal) + testLate(lateINTERFACE, _.isInterface) + testLate(lateMETHOD, _.isMethod) + testLate(lateMODULE, _.isModule) + testNot(PROTECTED | notPROTECTED, _.isProtected) + testNot(OVERRIDE | notOVERRIDE, _.isOverride) + testNot(PRIVATE | notPRIVATE, _.isPrivate) + + assertFalse(withFlagMask(AllFlags)(sym.setFlag(PRIVATE | notPRIVATE).isPrivate)) + + assertEquals(withFlagMask(InitialFlags)(sym.setFlag(PRIVATE | notPRIVATE).flags & PRIVATE), PRIVATE) + assertEquals(withFlagMask(AllFlags)(sym.setFlag(PRIVATE | notPRIVATE).flags & PRIVATE), 0) + } + + @Test + def normalLateOverlap(): Unit = { + // late flags are shifted by LateShift == 47. + // however, the first late flag is lateDEFERRED, which is DEFERRED << 47 == (1 << 4) << 47 == 1 << 51 + // the flags from 1 << 47 to 1 << 50 are not late flags. this is ensured by the LateFlags mask. + + for (i <- 0 to 3) { + val f = 1L << i + assertEquals(withFlagMask(AllFlags)(sym.setFlag(f << LateShift).flags & f), 0) // not treated as late flag + } + for (i <- 4 to 8) { + val f = 1L << i + assertEquals(withFlagMask(AllFlags)(sym.setFlag(f << LateShift).flags & f), f) // treated as late flag + } + } + + @Test + def normalAnti(): Unit = { + for (i <- 0 to 2) { + val f = 1L << i + assertEquals(withFlagMask(AllFlags)(sym.setFlag(f | (f << AntiShift)).flags & f), 0) // negated flags + } + for (i <- 3 to 7) { + val f = 1L << i + assertEquals(withFlagMask(AllFlags)(sym.setFlag(f | (f << AntiShift)).flags & f), f) // not negated + } + } + + @Test + def lateAntiCrossCheck(): Unit = { + val allButNegatable = AllFlags & ~(PROTECTED | OVERRIDE | PRIVATE) + val lateable = 0L | DEFERRED | FINAL | INTERFACE | METHOD | MODULE + val lateFlags = lateable << LateShift + val allButLateable = AllFlags & ~lateable + + assertEquals(withFlagMask(AllFlags)(sym.setFlag(AllFlags).flags), allButNegatable) + assertEquals(withFlagMask(AllFlags)(sym.setFlag(allButLateable).flags), allButNegatable) + + assertEquals(withFlagMask(AllFlags)(sym.setFlag(lateFlags).flags), lateFlags | lateable) + } +} |