diff --git a/build.number b/build.number index dc85ecb77700..5f8ed6d6b663 100644 --- a/build.number +++ b/build.number @@ -1,9 +1,9 @@ #Tue Sep 11 19:21:09 CEST 2007 version.major=2 version.minor=11 -version.patch=5 +version.patch=6 # This is the -N part of a version. if it's 0, it's dropped from maven versions. version.bnum=0 -# Note: To build a release run ant with -Dbuild.release=true -# To build an RC, run ant with -Dmaven.version.suffix=-RCN +# To build a release, see scripts/jobs/scala-release-2.11.x-build +# (normally run by the eponymous job on scala-ci.typesafe.com). \ No newline at end of file diff --git a/scripts/common b/scripts/common new file mode 100644 index 000000000000..b07546937946 --- /dev/null +++ b/scripts/common @@ -0,0 +1,153 @@ +# This is for forcibly stopping the job from a subshell (see test +# below). +trap "exit 1" TERM +export TOP_PID=$$ +set -e + +# Known problems : does not fare well with interrupted, partial +# compilations. We should perhaps have a multi-dependency version +# of do_i_have below + +LOGGINGDIR="$WORKSPACE/logs" +mkdir -p $LOGGINGDIR + +unset SBT_HOME +SBT_HOME="$WORKSPACE/.sbt" +mkdir -p $SBT_HOME +IVY_CACHE="$WORKSPACE/.ivy2" +mkdir -p $IVY_CACHE +rm -rf $IVY_CACHE/cache/org.scala-lang + +# temp dir where all 'non-build' operation are performed +TMP_ROOT_DIR=$(mktemp -d -t pr-scala.XXXX) +TMP_DIR="${TMP_ROOT_DIR}/tmp" +mkdir "${TMP_DIR}" + + +# detect sed version and how to enable extended regexes +SEDARGS="-n$(if (echo "a" | sed -nE "s/a/b/" &> /dev/null); then echo E; else echo r; fi)" + + + +# :docstring test: +# Usage: test +# Executes , logging the launch of the command to the +# main log file, and kills global script execution with the TERM +# signal if the commands ends up failing. +# DO NOT USE ON FUNCTIONS THAT DECLARE VARIABLES, +# AS YOU'LL BE RUNNING IN A SUBSHELL AND VARIABLE DECLARATIONS WILL BE LOST +# :end docstring: + +function test() { + echo "### $@" + "$@" + status=$? + if [ $status -ne 0 ]; then + say "### ERROR with $1" + kill -s TERM $TOP_PID + fi +} + +# :docstring say: +# Usage: say +# Prints to both console and the main log file. +# :end docstring: + +function say(){ + (echo "$@") | tee -a $LOGGINGDIR/compilation-$SCALADATE-$SCALAHASH.log +} + +# General debug logging +# $* - message +function debug () { + echo "----- $*" +} + +function parseScalaProperties(){ + propFile="$baseDir/$1" + if [ ! -f $propFile ]; then + echo "Property file $propFile not found." + exit 1 + else + awk -f "$scriptsDir/readproperties.awk" "$propFile" > "$propFile.sh" + . "$propFile.sh" # yeah yeah, not that secure, improvements welcome (I tried, but bash made me cry again) + fi +} + + +## TAKEN FROM UBER-BUILD, except that it "returns" (via $RES) true/false +# Check if an artifact is available +# $1 - groupId +# $2 - artifacId +# $3 - version +# $4 - extra repository to look in (optional) +# return value in $RES +function checkAvailability () { + pushd "${TMP_DIR}" + rm -rf * + +# pom file for the test project + cat > pom.xml << EOF + + 4.0.0 + com.typesafe + typesafeDummy + war + 1.0-SNAPSHOT + Dummy + http://127.0.0.1 + + + $1 + $2 + $3 + + + + + sonatype.snapshot + Sonatype maven snapshot repository + https://oss.sonatype.org/content/repositories/snapshots + + daily + + +EOF + + if [ -n "$4" ] + then +# adds the extra repository + cat >> pom.xml << EOF + + extrarepo + extra repository + $4 + +EOF + fi + + cat >> pom.xml << EOF + + +EOF + + set +e + mvn "${MAVEN_ARGS[@]}" compile &> "${TMP_DIR}/mvn.log" + RES=$? + # Quiet the maven, but allow diagnosing problems. + grep -i downloading "${TMP_DIR}/mvn.log" + grep -i exception "${TMP_DIR}/mvn.log" + grep -i error "${TMP_DIR}/mvn.log" + set -e + +# log the result + if [ ${RES} == 0 ] + then + debug "$1:$2:jar:$3 found !" + RES=true + else + debug "$1:$2:jar:$3 not found !" + RES=false + fi + popd +} diff --git a/scripts/jobs/scala-release-2.11.x-build b/scripts/jobs/integrate/bootstrap similarity index 93% rename from scripts/jobs/scala-release-2.11.x-build rename to scripts/jobs/integrate/bootstrap index 21fbb8fa76bc..46d610018c39 100755 --- a/scripts/jobs/scala-release-2.11.x-build +++ b/scripts/jobs/integrate/bootstrap @@ -1,4 +1,9 @@ #!/bin/bash -e +# TODO: different scripts for the different phases -- usually we don't need to bootstrap the modules, +# since we can use the previous version of scala for STARR as well as for compiling the modules (assuming it's binary compatible) +# We should move away from the complicated bootstrap and set up our release schedule so we always have a previous build that satisfies these criteria. +# (Potentially trivially, by splitting up this script, and publishing locker as if it were a real release.) + # requirements: # sbtCmd must point to sbt from sbt-extras (this is the standard on the Scala jenkins, so we only support that one) # - ~/.sonatype-curl that consists of user = USER:PASS @@ -66,7 +71,6 @@ # set to something besides the default to build nightly snapshots of the modules instead of some tagged version moduleVersioning=${moduleVersioning-"versions.properties"} -baseDir=${WORKSPACE-`pwd`} publishPrivateTask=${publishPrivateTask-"publish"} publishSonatypeTaskCore=${publishSonatypeTaskCore-"publish-signed"} publishSonatypeTaskModules=${publishSonatypeTaskModules-"publish-signed"} @@ -88,67 +92,11 @@ forceRebuild=${forceRebuild-no} antBuildTask="${antBuildTask-nightly}" # TESTING leave empty to avoid the sanity check (don't set it to "init" because ant will croak) clean="clean" # TESTING leave empty to speed up testing -scriptsDir="$WORKSPACE/scripts" - -# This is for forcibly stopping the job from a subshell (see test -# below). -trap "exit 1" TERM -export TOP_PID=$$ -set -e - -# Known problems : does not fare well with interrupted, partial -# compilations. We should perhaps have a multi-dependency version -# of do_i_have below - -LOGGINGDIR="$WORKSPACE/logs" -mkdir -p $LOGGINGDIR - -unset SBT_HOME -SBT_HOME="$WORKSPACE/.sbt" -mkdir -p $SBT_HOME -IVY_CACHE="$WORKSPACE/.ivy2" -mkdir -p $IVY_CACHE -rm -rf $IVY_CACHE/cache/org.scala-lang - -# temp dir where all 'non-build' operation are performed -TMP_ROOT_DIR=$(mktemp -d -t pr-scala.XXXX) -TMP_DIR="${TMP_ROOT_DIR}/tmp" -mkdir "${TMP_DIR}" - - -# detect sed version and how to enable extended regexes -SEDARGS="-n$(if (echo "a" | sed -nE "s/a/b/" &> /dev/null); then echo E; else echo r; fi)" - - -# :docstring test: -# Usage: test -# Executes , logging the launch of the command to the -# main log file, and kills global script execution with the TERM -# signal if the commands ends up failing. -# DO NOT USE ON FUNCTIONS THAT DECLARE VARIABLES, -# AS YOU'LL BE RUNNING IN A SUBSHELL AND VARIABLE DECLARATIONS WILL BE LOST -# :end docstring: - -function test() { - echo "### $@" - "$@" - status=$? - if [ $status -ne 0 ]; then - say "### ERROR with $1" - kill -s TERM $TOP_PID - fi -} - -# :docstring say: -# Usage: say -# Prints to both console and the main log file. -# :end docstring: - -function say(){ - (echo "$@") | tee -a $LOGGINGDIR/compilation-$SCALADATE-$SCALAHASH.log -} +baseDir=${WORKSPACE-`pwd`} +scriptsDir="$baseDir/scripts" +. $scriptsDir/common # we must change ivy home to get a fresh ivy cache, otherwise we get half-bootstrapped scala # rm it in case it existed (and there's no ivy2-shadow, which indicates we're running in a TESTING environment)... @@ -163,17 +111,6 @@ mkdir -p $baseDir/resolutionScratch_ privateCred="private-repo" privateRepo="http://private-repo.typesafe.com/typesafe/scala-release-temp/" -function parseScalaProperties(){ - propFile="$baseDir/$1" - if [ ! -f $propFile ]; then - echo "Property file $propFile not found." - exit 1 - else - awk -f "$scriptsDir/readproperties.awk" "$propFile" > "$propFile.sh" - . "$propFile.sh" # yeah yeah, not that secure, improvements welcome (I tried, but bash made me cry again) - fi -} - ##### git gfxd() { git clean -fxd # TESTING diff --git a/scripts/jobs/validate/publish-core b/scripts/jobs/validate/publish-core new file mode 100755 index 000000000000..9dff5a34b0d9 --- /dev/null +++ b/scripts/jobs/validate/publish-core @@ -0,0 +1,44 @@ +#!/bin/bash -e +# This script publishes the core of Scala to maven for use as locker downstream, +# and saves the relevant properties used in its build artifacts, versions.properties. +# (This means we'll use locker instead of quick downstream in dbuild. +# The only downside is that backend improvements don't improve compiler performance itself until they are in STARR). +# The version is suffixed with "-${sha:0:7}-SNAPSHOT" + +baseDir=${WORKSPACE-`pwd`} +scriptsDir="$baseDir/scripts" +. $scriptsDir/common + +case $prDryRun in + yep) + echo "DRY RUN" + mkdir -p build/pack ; mkdir -p dists/maven/latest + ;; + *) + sha=$(git rev-parse HEAD) # TODO: warn if $repo_ref != $sha (we shouldn't do PR validation using symbolic gitrefs) + echo "sha/repo_ref == $sha/$repo_ref ?" + + parseScalaProperties build.number + + ./pull-binary-libs.sh + # "noyoudont" is there juuuust in case + antDeployArgs="-Dmaven.version.suffix=\"-${sha:0:7}-SNAPSHOT\" -Dremote.snapshot.repository=$prRepoUrl -Drepository.credentials.id=pr-scala -Dremote.release.repository=noyoudont" + + echo ">>> Getting Scala version number." + ant -q $antDeployArgs init + parseScalaProperties buildcharacter.properties # produce maven_version_number + + echo ">>> Checking availability of Scala ${maven_version_number} in $prRepoUrl." + checkAvailability "org.scala-lang" "scala-library" "${maven_version_number}" $prRepoUrl; libraryAvailable=$RES + checkAvailability "org.scala-lang" "scala-reflect" "${maven_version_number}" $prRepoUrl; reflectAvailable=$RES + checkAvailability "org.scala-lang" "scala-compiler" "${maven_version_number}" $prRepoUrl; compilerAvailable=$RES + + if $libraryAvailable && $reflectAvailable && $compilerAvailable; then + echo "Scala core already built!" + else + ant $antDeployArgs $antBuildArgs publish-opt-nodocs + fi + + mv buildcharacter.properties jenkins.properties # parsed by the jenkins job + ;; +esac diff --git a/scripts/jobs/validate/test b/scripts/jobs/validate/test new file mode 100755 index 000000000000..47b95bdae57f --- /dev/null +++ b/scripts/jobs/validate/test @@ -0,0 +1,17 @@ +#!/bin/bash -e + +case $prDryRun in + yep) + echo "DRY RUN" + ;; + *) + ./pull-binary-libs.sh + + # build quick using STARR built upstream, as specified by maven_version_number + # (in that sense it's locker, since it was built with starr by that upstream job) + ant -Dstarr.version=$maven_version_number \ + -Dscalac.args.optimise=-optimise \ + -Dlocker.skip=1 -Dstarr.use.released=1 -Dextra.repo.url=$prRepoUrl \ + $testExtraArgs ${testTarget-test.core docs.done} + ;; +esac \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 7269910af691..75aa0fc98469 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -5,10 +5,11 @@ package scala.tools.nsc.backend.jvm -import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode} -import java.io.PrintWriter +import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} +import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.ClassReader +import scala.collection.convert.decorateAsScala._ object AsmUtils { @@ -36,19 +37,12 @@ object AsmUtils { def traceMethod(mnode: MethodNode): Unit = { println(s"Bytecode for method ${mnode.name}") - val p = new Textifier - val tracer = new TraceMethodVisitor(p) - mnode.accept(tracer) - val w = new PrintWriter(System.out) - p.print(w) - w.flush() + println(textify(mnode)) } def traceClass(cnode: ClassNode): Unit = { println(s"Bytecode for class ${cnode.name}") - val w = new PrintWriter(System.out) - cnode.accept(new TraceClassVisitor(w)) - w.flush() + println(textify(cnode)) } def traceClass(bytes: Array[Byte]): Unit = traceClass(readClass(bytes)) @@ -59,8 +53,56 @@ object AsmUtils { node } - def instructionString(instruction: AbstractInsnNode): String = instruction.getOpcode match { - case -1 => instruction.toString - case op => scala.tools.asm.util.Printer.OPCODES(op) + /** + * Returns a human-readable representation of the cnode ClassNode. + */ + def textify(cnode: ClassNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + cnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString } + + /** + * Returns a human-readable representation of the code in the mnode MethodNode. + */ + def textify(mnode: MethodNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + mnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString + } + + /** + * 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 + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: Iterator[AbstractInsnNode]): String = { + val trace = new TraceMethodVisitor(new Textifier) + insns.foreach(_.accept(trace)) + val sw: StringWriter = new StringWriter + val pw: PrintWriter = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: InsnList): String = textify(insns.iterator().asScala) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 806d4b277ce7..8d1c37532ece 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -791,32 +791,28 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { assert(moduleClass.companionClass == NoSymbol, moduleClass) innerClassBufferASM.clear() this.cunit = cunit - val moduleName = internalName(moduleClass) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - flags, - mirrorName, + bType.info.flags, + bType.internalName, null /* no java-generic-signature */, ObjectReference.internalName, EMPTY_STRING_ARRAY ) - if (emitSource) { - mirrorClass.visitSource("" + cunit.source, - null /* SourceDebugExtension */) - } + if (emitSource) + mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */) - val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol) + val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol) mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) - addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass) + addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses + innerClassBufferASM ++= bType.info.nestedClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() @@ -932,7 +928,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index d58368b19d68..c3db28151b55 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -271,7 +271,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") // We're done with BOOL already - from match { + (from: @unchecked) match { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" @@ -361,7 +361,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(elem.isNonVoidPrimitiveType) val rand = { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - elem match { + (elem: @unchecked) match { case BOOL => Opcodes.T_BOOLEAN case BYTE => Opcodes.T_BYTE case SHORT => Opcodes.T_SHORT diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 03bc32061b99..142c901c2166 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -118,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 7defd7c873eb..a9bce82acd63 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -8,9 +8,12 @@ package backend.jvm import scala.tools.asm import asm.Opcodes +import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import opt.ByteCodeRepository +import scala.collection.convert.decorateAsScala._ /** - * The BTypes component defines The BType class hierarchy. BTypes encapsulates all type information + * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information * that is required after building the ASM nodes. This includes optimizations, generation of * InnerClass attributes and generation of stack map frames. * @@ -18,6 +21,22 @@ import asm.Opcodes * be queried by concurrent threads. */ abstract class BTypes { + import BTypes.InternalName + + // Some core BTypes are required here, in class BType, where no Global instance is available. + // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual + // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. + val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] + import coreBTypes._ + + /** + * Tools for parsing classfiles, used by the inliner. + */ + val byteCodeRepository: ByteCodeRepository + + // Allows to define per-run caches here and in the CallGraph component, which don't have a global + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -29,30 +48,83 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - protected val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) /** - * The string represented by the `offset` / `length` values of a ClassBType, see comment of that - * class. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ - protected def internalNameString(offset: Int, lenght: Int): String + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromClassNode(byteCodeRepository.classNode(internalName)) + } /** - * Obtain a previously constructed ClassBType for a given internal name. + * Construct the [[ClassBType]] for a parsed classfile. */ - def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName) + def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { + classBTypeFromInternalName.getOrElse(classNode.name, { + setClassInfo(classNode, ClassBType(classNode.name)) + }) + } - // Some core BTypes are required here, in class BType, where no Global instance is available. - // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual - // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. - val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] - import coreBTypes._ + private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = { + val superClass = classNode.superName match { + case null => + assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") + None + case superName => + Some(classBTypeFromParsedClassfile(superName)) + } + + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + + val flags = classNode.access + + /** + * Find all nested classes of classNode. The innerClasses attribute contains all nested classes + * that are declared inside classNode or used in the bytecode of classNode. So some of them are + * nested in some other class than classNode, and we need to filter them. + * + * For member classes, innerClassNode.outerName is defined, so we compare that to classNode.name. + * + * For local and anonymous classes, innerClassNode.outerName is null. Such classes are required + * to have an EnclosingMethod attribute declaring the outer class. So we keep those local and + * anonymous classes whose outerClass is classNode.name. + * + */ + def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { + (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || + (innerClassNode.outerName == null && byteCodeRepository.classNode(innerClassNode.name).outerClass == classNode.name) + } + + val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) + })(collection.breakOut) + + // if classNode is a nested class, it has an innerClass attribute for itself. in this + // case we build the NestedInfo. + val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map { + case innerEntry => + val enclosingClass = + if (innerEntry.outerName != null) { + // if classNode is a member class, the outerName is non-null + classBTypeFromParsedClassfile(innerEntry.outerName) + } else { + // for anonymous or local classes, the outerName is null, but the enclosing class is + // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). + classBTypeFromParsedClassfile(classNode.outerClass) + } + val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 + NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) + } + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) + classBType + } /** * A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType * referring to BTypes. */ - /*sealed*/ trait BType { // Not sealed for now due to SI-8546 + sealed trait BType { final override def toString: String = this match { case UNIT => "V" case BOOL => "Z" @@ -171,6 +243,9 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. ObjectReference + + case _: MethodBType => + throw new AssertionError(s"unexpected method type when computing maxType: $this") } /** @@ -568,28 +643,14 @@ abstract class BTypes { /** * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. - * - * The `offset` and `length` fields are used to represent the internal name of the class. They - * are indices into some character array. The internal name can be obtained through the method - * `internalNameString`, which is abstract in this component. Name creation is assumed to be - * hash-consed, so if two ClassBTypes have the same internal name, they NEED to have the same - * `offset` and `length`. - * - * The actual implementation in subclass BTypesFromSymbols uses the global `chrs` array from the - * name table. This representation is efficient because the JVM class name is obtained through - * `classSymbol.javaBinaryName`. This already adds the necessary string to the `chrs` array, - * so it makes sense to reuse the same name table in the backend. - * - * ClassBType is not a case class because we want a custom equals method, and because the - * extractor extracts the internalName, which is what you typically need. */ - final class ClassBType(val offset: Int, val length: Int) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have * * B.info.nestedInfo.outerClass == A - * A.info.memberClasses contains B + * A.info.nestedClasses contains B */ private var _info: ClassInfo = null @@ -604,7 +665,7 @@ abstract class BTypes { checkInfoConsistency() } - classBTypeFromInternalNameMap(internalName) = this + classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { // we assert some properties. however, some of the linked ClassBType (members, superClass, @@ -612,7 +673,7 @@ abstract class BTypes { // best-effort verification. def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) - def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" + def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") @@ -627,15 +688,9 @@ abstract class BTypes { s"Invalid interfaces in $this: ${info.interfaces}" ) - assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) + assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) } - /** - * The internal name of a class is the string returned by java.lang.Class.getName, with all '.' - * replaced by '/'. For example "java/lang/String". - */ - def internalName: String = internalNameString(offset, length) - /** * @return The class name without the package prefix */ @@ -661,8 +716,9 @@ abstract class BTypes { outerName.orNull, innerName.orNull, GenBCode.mkFlags( - info.flags, - if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 + // the static flag in the InnerClass table has a special meaning, see InnerClass comment + info.flags & ~Opcodes.ACC_STATIC, + if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) } @@ -736,32 +792,9 @@ abstract class BTypes { } while (fcs == null) fcs } - - /** - * Custom equals / hashCode: we only compare the name (offset / length) - */ - override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case c: ClassBType => c.offset == this.offset && c.length == this.length - case _ => false - }) - - override def hashCode: Int = { - import scala.runtime.Statics - var acc: Int = -889275714 - acc = Statics.mix(acc, offset) - acc = Statics.mix(acc, length) - Statics.finalizeHash(acc, 2) - } } object ClassBType { - /** - * Pattern matching on a ClassBType extracts the `internalName` of the class. - */ - def unapply(c: ClassBType): Option[String] = - if (c == null) None - else Some(c.internalName) - /** * Valid flags for InnerClass attribute entry. * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 @@ -801,12 +834,12 @@ abstract class BTypes { * through the superclass. * @param flags The java flags, obtained through `javaFlags`. Used also to derive * the flags for InnerClass entries. - * @param memberClasses Classes nested in this class. Those need to be added to the + * @param nestedClasses Classes nested in this class. Those need to be added to the * InnerClass table, see the InnerClass spec summary above. * @param nestedInfo If this describes a nested class, information for the InnerClass table. */ - case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, - memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, + nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) /** * Information required to add a class to an InnerClass table. @@ -823,10 +856,10 @@ abstract class BTypes { * a source-level property: if the class is in a static context (does not have an outer pointer). * This is checked when building the NestedInfo. */ - case class NestedInfo(enclosingClass: ClassBType, - outerName: Option[String], - innerName: Option[String], - isStaticNestedClass: Boolean) + final case class NestedInfo(enclosingClass: ClassBType, + outerName: Option[String], + innerName: Option[String], + isStaticNestedClass: Boolean) /** * This class holds the data for an entry in the InnerClass table. See the InnerClass summary @@ -839,9 +872,9 @@ abstract class BTypes { * @param innerName The simple name of the inner class, may be null. * @param flags The flags for this class in the InnerClass entry. */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + final case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) - case class ArrayBType(componentType: BType) extends RefBType { + final case class ArrayBType(componentType: BType) extends RefBType { def dimension: Int = componentType match { case a: ArrayBType => 1 + a.dimension case _ => 1 @@ -853,7 +886,7 @@ abstract class BTypes { } } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType + final case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType /* Some definitions that are required for the implementation of BTypes. They are abstract because * initializing them requires information from types / symbols, which is not accessible here in @@ -873,3 +906,12 @@ abstract class BTypes { */ def isCompilingPrimitive: Boolean } + +object BTypes { + /** + * A marker for strings that represent class internal names. + * Ideally the type would be incompatible with String, for example by making it a value class. + * But that would create overhead in a Collection[InternalName]. + */ + type InternalName = String +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index a0b8d28f1820..94f9b585d96d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,6 +7,10 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm +import opt.ByteCodeRepository +import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source +import BTypes.InternalName /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -32,20 +36,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - final def intializeCoreBTypes(): Unit = { - coreBTypes.setBTypes(new CoreBTypes[this.type](this)) - } - - def internalNameString(offset: Int, length: Int) = new String(global.chrs, offset, length) + val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)])) - protected val classBTypeFromInternalNameMap = { - global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) + final def initializeCoreBTypes(): Unit = { + coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } - /** - * Cache for the method classBTypeFromSymbol. - */ - private val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]() + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache) // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -89,13 +86,11 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { (classSym != NothingClass && classSym != NullClass), s"Cannot create ClassBType for special class symbol ${classSym.fullName}") - convertedClasses.getOrElse(classSym, { - val internalName = classSym.javaBinaryName.toTypeName - // We first create and add the ClassBType to the hash map before computing its info. This - // allows initializing cylic dependencies, see the comment on variable ClassBType._info. - val classBType = new ClassBType(internalName.start, internalName.length) - convertedClasses(classSym) = classBType - setClassInfo(classSym, classBType) + val internalName = classSym.javaBinaryName.toString + classBTypeFromInternalName.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + setClassInfo(classSym, ClassBType(internalName)) }) } @@ -126,25 +121,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * code generation, but those duplicates will be eliminated when emitting the InnerClass * attribute. * - * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to + * Why do we need to collect classes into innerClassBufferASM at all? To collect references to * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. - val nestedClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val nestedClasses = { + val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + if (isTopLevelModuleClass(classSym)) { + // For Java compatibility, member classes of top-level objects are treated as members of + // the top-level companion class, see comment below. + val members = exitingPickler(memberClassesOf(classSym)) + nested diff members + } else { + nested + } + } - // If this is a top-level class, and it has a companion object, the member classes of the - // companion are added as members of the class. For example: + // If this is a top-level class, the member classes of the companion object are added as + // members of the class. For example: // class C { } // object C { // class D // def f = { class E } // } - // The class D is added as a member of class C. The reason is that the InnerClass attribute - // for D will containt class "C" and NOT the module class "C$" as the outer class of D. - // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. + // The class D is added as a member of class C. The reason is: for Java compatibility, the + // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D + // (done by buildNestedInfo). See comment in BTypes. // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks // like D is a member of C, not C$. val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases @@ -174,53 +179,51 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } else true }) - val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) val nestedInfo = buildNestedInfo(classSym) - classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo) + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) classBType } private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") - val isNested = !innerClassSym.rawowner.isPackageClass - if (!isNested) None + val isTopLevel = innerClassSym.rawowner.isPackageClass + if (isTopLevel) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. - val enclosingClassSym = { - if (innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) { - // Example java source: class C { static class D { } } - // The Scala compiler creates a class and a module symbol for C. Because D is a static - // nested class, the symbol for D is nested in the module class C (not in the class C). - // For the InnerClass attribute, we use the class symbol C, which represents the situation - // in the source code. - - // Cannot use innerClassSym.isStatic: this method looks at the owner, which is a package - // at this pahse (after lambdalift, flatten). - assert(isOriginallyStaticOwner(innerClassSym.originalOwner), innerClassSym.originalOwner) - + val enclosingClass = { + // (1) Example java source: class C { static class D { } } + // The Scala compiler creates a class and a module symbol for C. Because D is a static + // nested class, the symbol for D is nested in the module class C (not in the class C). + // For the InnerClass attribute, we use the class symbol C, which represents the situation + // in the source code. + + // (2) Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. + if ((innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) || // (1) + (!isAnonymousOrLocalClass(innerClassSym) && isTopLevelModuleClass(innerClassSym.rawowner))) { // (2) // phase travel for linkedCoC - does not always work in late phases - exitingPickler(innerClassSym.rawowner.linkedClassOfClass) + exitingPickler(innerClassSym.rawowner.linkedClassOfClass) match { + case NoSymbol => + // For top-level modules without a companion class, see doc of mirrorClassClassBType. + mirrorClassClassBType(exitingPickler(innerClassSym.rawowner)) + + case companionClass => + classBTypeFromSymbol(companionClass) + } + } else { + classBTypeFromSymbol(innerClassSym.rawowner) } - else innerClassSym.rawowner } - val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) val outerName: Option[String] = { - if (isAnonymousOrLocalClass(innerClassSym)) { - None - } else { - val outerName = innerClassSym.rawowner.javaBinaryName - // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. - val outerNameModule = if (isTopLevelModuleClass(innerClassSym.rawowner)) outerName.dropModule - else outerName - Some(outerNameModule.toString) - } + if (isAnonymousOrLocalClass(innerClassSym)) None + else Some(enclosingClass.internalName) } val innerName: Option[String] = { @@ -232,6 +235,29 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } } + /** + * For top-level objects without a companion class, the compilere generates a mirror class with + * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a + * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). + */ + def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = { + assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.dropModule.toString + classBTypeFromInternalName.getOrElse(internalName, { + val c = ClassBType(internalName) + // class info consistent with BCodeHelpers.genMirrorClass + val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + c.info = ClassInfo( + superClass = Some(ObjectReference), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + nestedClasses = nested, + nestedInfo = None + ) + c + }) + } + /** * True for module classes of package level objects. The backend will generate a mirror class for * such objects. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index de468c0a6db2..abe3bc512c71 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -677,7 +677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { + def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) { /* The outer name for this inner class. Note that it returns null * when the inner class should not get an index in the constant pool. * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. @@ -698,11 +698,19 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => else innerSym.rawname + innerSym.moduleSuffix - // This collects all inner classes of csym, including local and anonymous: lambdalift makes - // them members of their enclosing class. - innerClassBuffer ++= exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + innerClassBuffer ++= { + val members = exitingPickler(memberClassesOf(csym)) + // lambdalift makes all classes (also local, anonymous) members of their enclosing class + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) - // Add members of the companion object (if top-level). why, see comment in BTypes.scala. + // for the mirror class, we take the members of the companion module class (Java compat, + // see doc in BTypes.scala). for module classes, we filter out those members. + if (isMirror) members + else if (isTopLevelModule(csym)) allNested diff members + else allNested + } + + // If this is a top-level class, add members of the companion object. val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases if (isTopLevelModule(linkedClass)) { // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, @@ -2796,7 +2804,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) - addInnerClasses(modsym, mirrorClass) + addInnerClasses(modsym, mirrorClass, isMirror = true) mirrorClass.visitEnd() writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index a45f5866668f..d5e95c47cff6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -286,7 +286,7 @@ abstract class GenBCode extends BCodeSyncAndTry { val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer) arrivalPos = 0 // just in case scalaPrimitives.init() - bTypes.intializeCoreBTypes() + bTypes.initializeCoreBTypes() Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala new file mode 100644 index 000000000000..7b424d2107b3 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -0,0 +1,112 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm +import asm.tree._ +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassFileLookup +import OptimizerReporting._ +import ByteCodeRepository._ +import BTypes.InternalName + +/** + * The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation + * classpath. Parsed classes are cached in the `classes` map. + * + * @param classPath The compiler classpath where classfiles are searched and read from. + * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: + * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode + * corresponds to a class being compiled. + */ +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, (ClassNode, Source)]) { + /** + * The class node and source for an internal name. If the class node is not yet available, it is + * parsed from the classfile on the compile classpath. + */ + def classNodeAndSource(internalName: InternalName): (ClassNode, Source) = { + classes.getOrElseUpdate(internalName, (parseClass(internalName), Classfile)) + } + + /** + * The class node for an internal name. If the class node is not yet available, it is parsed from + * the classfile on the compile classpath. + */ + def classNode(internalName: InternalName) = classNodeAndSource(internalName)._1 + + /** + * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the field may be in one of the superclasses. + * + * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. + */ + def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { + val c = classNode(classInternalName) + c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { + Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) + } + } + + /** + * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the method may be in one of the parents. + * + * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. + */ + def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { + val c = classNode(classInternalName) + c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse { + val parents = Option(c.superName) ++ c.interfaces.asScala + // `view` to stop at the first result + parents.view.flatMap(methodNode(_, name, descriptor)).headOption + } + } + + private def parseClass(internalName: InternalName): ClassNode = { + val fullName = internalName.replace('/', '.') + classPath.findClassFile(fullName) map { classFile => + val classNode = new asm.tree.ClassNode() + val classReader = new asm.ClassReader(classFile.toByteArray) + // We don't need frames when inlining, but we want to keep the local variable table, so we + // don't use SKIP_DEBUG. + classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES) + // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after + // inlining. + // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? + // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension + // attribute that contains JSR-45 data that encodes debugging info. + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 + // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html + removeLineNumberNodes(classNode) + classNode + } getOrElse { + inlineFailure(s"Class file for class $fullName not found.") + } + } + + private def removeLineNumberNodes(classNode: ClassNode): Unit = { + for (method <- classNode.methods.asScala) { + val iter = method.instructions.iterator() + while (iter.hasNext) iter.next() match { + case _: LineNumberNode => iter.remove() + case _ => + } + } + } +} + +object ByteCodeRepository { + /** + * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the + * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath. + */ + sealed trait Source + object CompilationUnit extends Source + object Classfile extends Source +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala new file mode 100644 index 000000000000..7002e43d98f1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -0,0 +1,24 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm +import asm.tree._ + +/** + * Reporting utilities used in the optimizer. + */ +object OptimizerReporting { + def methodSignature(className: String, methodName: String, methodDescriptor: String): String = { + className + "::" + methodName + methodDescriptor + } + + def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc) + + def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) + def assertionError(message: String): Nothing = throw new AssertionError(message) +} diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index c86a1108b251..1691b01e3efd 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -894,7 +894,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } val specMember = subst(outerEnv)(specializedOverload(owner, sym, spec)) - owner.info.decls.enter(specMember) typeEnv(specMember) = typeEnv(sym) ++ outerEnv ++ spec wasSpecializedForTypeVars(specMember) ++= spec collect { case (s, tp) if s.tpe == tp => s } diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 1c1a0522e43d..3820048cb411 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -84,17 +84,15 @@ object Test extends BytecodeTest { } def testA3() = { - def t(c: String) = { - val List(b1, b2) = innerClassNodes(c) - // the outer class for classes nested inside top-level modules is not the module class, but the mirror class. - // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility". - assertMember(b1, "A3", "B1", flags = publicStatic) - assertMember(b2, "A3", "B2$", flags = publicStatic) - } - t("A3$") - // the mirror class has the same inner class attributes as the module - // class (added when the mirror is created in the backend) - t("A3") + // the inner class entries for top-level object members are in the companion class, so nothing in the module class + val List() = innerClassNodes("A3$") + + // inner class entries in the companion class (a backend-generated mirror class in this case) + val List(b1, b2) = innerClassNodes("A3") + // the outer class for classes nested inside top-level modules is not the module class, but the mirror class. + // this is a hack for java interop, handled in the backend. see BTypes.scala, comment on "Java Compatibility". + assertMember(b1, "A3", "B1", flags = publicStatic) + assertMember(b2, "A3", "B2$", flags = publicStatic) } def testA4() = { @@ -164,7 +162,10 @@ object Test extends BytecodeTest { } def testA15() = { - val List(b) = innerClassNodes("A15") + // no member classes, only anonymous / local. these are nested in the module class, not the companion. + val List() = innerClassNodes("A15") + + val List(b) = innerClassNodes("A15$") assertLocal(b, "A15$B$3", "B$3") val List(_, c) = innerClassNodes("A15$B$3") @@ -283,9 +284,7 @@ object Test extends BytecodeTest { assertMember(i3c, "A21", "I3$", flags = publicStatic) assertLocal(j1, "A21$J1$1", "J1$1") - val List(i2m, i3m, j3, j4, j5) = innerClassNodes("A21$") - assertMember(i2m, "A21", "I2", flags = publicStatic) - assertMember(i3m, "A21", "I3$", flags = publicStatic) + val List(j3, j4, j5) = innerClassNodes("A21$") assertLocal(j3, "A21$J3$1", "J3$1") assertLocal(j4, "A21$J4$1", "J4$1") assertLocal(j5, "A21$J5$1", "J5$1") // non-static! diff --git a/test/files/jvm/t8582.check b/test/files/jvm/t8582.check index 564f482ff87f..e38836627081 100644 --- a/test/files/jvm/t8582.check +++ b/test/files/jvm/t8582.check @@ -14,10 +14,10 @@ Reflection can find direct nested classes (A2-B2-C2) A2$B2: List(class A2$B2$C2) A2$B2$C2: List() -Mirror classes have the same InnerClass attributes as the corresponding module class: - className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9] -Module class +The InnerClass attribute of a mirror class contains the members of the module class: className[p1/p2/Singleton$Singleton$] outerClassName[p1/p2/Singleton] innerName[Singleton$] access[9] +The module members are not in the InnerClass table of the module class (unless referenced): + An outer class has a InnerClass attribute for direct nested classes className[A1$B1] outerClassName[A1] innerName[B1] access[1] diff --git a/test/files/jvm/t8582.scala b/test/files/jvm/t8582.scala index 8a57ef795239..e9a01f901664 100644 --- a/test/files/jvm/t8582.scala +++ b/test/files/jvm/t8582.scala @@ -55,9 +55,9 @@ object Test extends BytecodeTest { println(nested(classOf[A2#B2])) println(nested(classOf[A2#B2#C2])) - nprintln("Mirror classes have the same InnerClass attributes as the corresponding module class:") + nprintln("The InnerClass attribute of a mirror class contains the members of the module class:") printInner("p1.p2.Singleton") // mirror class - println("Module class") + println("The module members are not in the InnerClass table of the module class (unless referenced):") printInner("p1.p2.Singleton$") nprintln("An outer class has a InnerClass attribute for direct nested classes") diff --git a/test/files/res/t9089.check b/test/files/res/t9089.check new file mode 100644 index 000000000000..6cf64f734be2 --- /dev/null +++ b/test/files/res/t9089.check @@ -0,0 +1,4 @@ + +nsc> +nsc> +nsc> diff --git a/test/files/res/t9089.res b/test/files/res/t9089.res new file mode 100644 index 000000000000..ab5cc8534d30 --- /dev/null +++ b/test/files/res/t9089.res @@ -0,0 +1,2 @@ +t9089/A.scala +t9089/A.scala diff --git a/test/files/res/t9089/A.scala b/test/files/res/t9089/A.scala new file mode 100644 index 000000000000..bccf26963938 --- /dev/null +++ b/test/files/res/t9089/A.scala @@ -0,0 +1 @@ +object O { def f(x: => Int): Int = x } diff --git a/test/files/run/icode-reader-dead-code.check b/test/files/run/icode-reader-dead-code.check index d1739fed3b18..c9de93283e87 100644 --- a/test/files/run/icode-reader-dead-code.check +++ b/test/files/run/icode-reader-dead-code.check @@ -1,4 +1,7 @@ Bytecode for method f + + // access flags 0x11 + public final f()I L0 LINENUMBER 4 L0 ICONST_1 @@ -7,7 +10,11 @@ Bytecode for method f LOCALVARIABLE this Lp/A; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 + Bytecode for method f + + // access flags 0x11 + public final f()I L0 LINENUMBER 4 L0 ICONST_1 @@ -17,3 +24,4 @@ Bytecode for method f LOCALVARIABLE this Lp/A; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 + diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala index 221aad653679..2347e8288ea8 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala @@ -19,7 +19,7 @@ class BTypesTest { val btypes = new BTypesFromSymbols[g.type](g) import btypes._ - duringBackend(btypes.intializeCoreBTypes()) + duringBackend(btypes.initializeCoreBTypes()) def classBTypeFromSymbol(sym: Symbol) = duringBackend(btypes.classBTypeFromSymbol(sym)) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala new file mode 100644 index 000000000000..2975bd060dba --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -0,0 +1,95 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +import scala.collection.convert.decorateAsScala._ + +@RunWith(classOf[JUnit4]) +class BTypesFromClassfileTest { + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode") + + import compiler._ + import definitions._ + import genBCode.bTypes + import bTypes._ + + def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f) + + val run = new compiler.Run() // initializes some of the compiler + duringBackend(bTypes.initializeCoreBTypes()) + + def clearCache() = bTypes.classBTypeFromInternalName.clear() + + def sameBType(fromSym: ClassBType, fromClassfile: ClassBType, checked: Set[InternalName] = Set.empty): Set[InternalName] = { + if (checked(fromSym.internalName)) checked + else { + assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile") + sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName) + } + } + + def sameBTypes(fromSyms: Iterable[ClassBType], fromClassfiles: Iterable[ClassBType], checked: Set[InternalName]): Set[InternalName] = { + assert(fromSyms.size == fromClassfiles.size, s"\n$fromSyms\n$fromClassfiles") + (fromSyms, fromClassfiles).zipped.foldLeft(checked) { + case (chk, (fromSym, fromClassfile)) => sameBType(fromSym, fromClassfile, chk) + } + } + + def sameInfo(fromSym: ClassInfo, fromClassfile: ClassInfo, checked: Set[InternalName]): Set[InternalName] = { + assert({ + // Nested class symbols can undergo makeNotPrivate (ExplicitOuter). But this is only applied + // for symbols of class symbols that are being compiled, not those read from a pickle. + // So a class may be public in bytecode, but the symbol still says private. + if (fromSym.nestedInfo.isEmpty) fromSym.flags == fromClassfile.flags + else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC) + }, s"class flags differ\n$fromSym\n$fromClassfile") + + val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked) + + val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1) + + // The fromSym info has only member classes, no local or anonymous. The symbol is read from the + // Scala pickle data and only member classes are created / entered. + // (This is different for symbols that are being compiled, there flatten will enter all local + // and anonymous classes as members of the outer class. But not for unpickled symbols). + // The fromClassfile info has all nested classes, including anonymous and local. So we filter + // them out: member classes are identified by having the `outerName` defined. + val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined) + // Sorting is required: the backend sorts all InnerClass entries by internalName before writing + // them to the classfile (to make it deterministic: the entries are collected in a Set during + // code generation). + val chk3 = sameBTypes(fromSym.nestedClasses.sortBy(_.internalName), memberClassesFromClassfile.sortBy(_.internalName), chk2) + sameBTypes(fromSym.nestedInfo.map(_.enclosingClass), fromClassfile.nestedInfo.map(_.enclosingClass), chk3) + } + + def check(classSym: Symbol): Unit = duringBackend { + clearCache() + val fromSymbol = classBTypeFromSymbol(classSym) + clearCache() + val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName) + sameBType(fromSymbol, fromClassfile) + } + + @Test + def compareClassBTypes(): Unit = { + // Note that not only these classes are tested, but also all their parents and all nested + // classes in their InnerClass attributes. + check(ObjectClass) + check(JavaNumberClass) + check(ConsClass) + check(ListModule.moduleClass) + } +} diff --git a/versions.properties b/versions.properties index a474b19c5b78..9e2e6cea796e 100644 --- a/versions.properties +++ b/versions.properties @@ -4,7 +4,7 @@ # when adding new properties that influence a release, # also add them to the update.versions mechanism in build.xml, # which is used by scala-release-2.11.x in scala/jenkins-scripts -starr.version=2.11.2 +starr.version=2.11.5 starr.use.released=1 # These are the versions of the modules that go with this release. @@ -14,7 +14,7 @@ starr.use.released=1 scala.binary.version=2.11 # e.g. 2.11.0-RC1, 2.11.0, 2.11.1-RC1, 2.11.1 # this defines the dependency on scala-continuations-plugin in scala-dist's pom -scala.full.version=2.11.2 +scala.full.version=2.11.5 # external modules shipped with distribution, as specified by scala-library-all's pom scala-xml.version.number=1.0.3