Skip to content

Commit

Permalink
Use InnerClasses attribute to find all reachable classes
Browse files Browse the repository at this point in the history
  • Loading branch information
szeiger committed Nov 30, 2016
1 parent f3f8939 commit 615b35a
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with
else owner.fullName + "." + bytecodeName
}

var _innerClasses: Seq[String] = Seq.empty
def innerClasses = { ensureLoaded(); _innerClasses }

var _isTopLevel = true
def isTopLevel = { ensureLoaded(); _isTopLevel }

final override def equals(other: Any): Boolean = other match {
case that: ClassInfo => (that canEqual this) && this.fullName == that.fullName
case _ => false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ abstract class ClassfileParser(definitions: Definitions) {
val attrNameIndex = in.nextChar
c.sourceFileName = pool.getName(attrNameIndex)
}
} else if (attrName == "InnerClasses") {
val entries = in.nextChar.toInt
c._innerClasses = (0 until entries).map { _ =>
val innerIndex, outerIndex, innerNameIndex = in.nextChar.toInt
in.skip(2)
if (innerIndex != 0 && outerIndex != 0 && innerNameIndex != 0) {
val n = pool.getClassName(innerIndex)
if (n == c.bytecodeName) c._isTopLevel = false // an inner class lists itself in InnerClasses
if (pool.getClassName(outerIndex) == c.bytecodeName) n else ""
} else ""
}.filterNot(_.isEmpty)
} else if (attrName == "Scala" || attrName == "ScalaSig") {
this.parsedClass.isScala = true
}
Expand Down
41 changes: 9 additions & 32 deletions core/src/main/scala/com/typesafe/tools/mima/core/PackageInfo.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.typesafe.tools.mima.core

import scala.annotation.tailrec
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassPath
import collection.mutable
Expand Down Expand Up @@ -64,48 +65,24 @@ abstract class PackageInfo(val owner: PackageInfo) {
def classes: mutable.Map[String, ClassInfo]

lazy val accessibleClasses: Set[ClassInfo] = {
// FIXME: Make this a tailrecursive implementation
/** Fixed point iteration for finding all accessible classes. */
def accessibleClassesUnder(prefix: Set[ClassInfo]): Set[ClassInfo] = {
val vclasses = (classes.valuesIterator filter (isAccessible(_, prefix))).toSet
if (vclasses.isEmpty) vclasses
else vclasses union accessibleClassesUnder(vclasses)
@tailrec
def accessibleClassesUnder(prefix: Set[ClassInfo], found: Set[ClassInfo]): Set[ClassInfo] = {
val vclasses = (classes.valuesIterator.filter(isAccessible(_, prefix))).toSet
if (vclasses.isEmpty) found
else accessibleClassesUnder(vclasses, vclasses union found)
}

def isAccessible(clazz: ClassInfo, prefix: Set[ClassInfo]) = {
def isReachable = {
if (clazz.isSynthetic) false
else {
val idx = clazz.decodedName.lastIndexOf("$")
if (idx < 0) prefix.isEmpty // class name contains no $
else {
// A top-level module is compiled into a class plus a module class (for instance,
// for a top-level `object A`, scalac creates two classfiles `A.class` and `A$.class`.).
// While, for an inner module, scalac creates only one classfile, i.e., the module class
// classfile.
//
// `clazz` is an inner module if:
// 1) `clazz` is a module, and
// 2) `clazz` decoded name starts with one of the passed `prefix`.
def isInnerModule: Boolean = {
clazz.isModule &&
prefix.exists { outer =>
// this condition is to avoid looping indefinitely
clazz.decodedName != outer.decodedName &&
// checks if `outer` is a prefix of `clazz` name
clazz.decodedName.startsWith(outer.decodedName)
}
}
// class, trait, and top-level module go through this condition
(prefix exists (_.decodedName == clazz.decodedName.substring(0, idx))) || // prefix before dollar is an accessible class detected previously
isInnerModule
}
}
else if (prefix.isEmpty) clazz.isTopLevel && !clazz.bytecodeName.contains("$$")
else prefix.exists(_.innerClasses contains clazz.bytecodeName)
}
clazz.isPublic && isReachable
}

accessibleClassesUnder(Set.empty)
accessibleClassesUnder(Set.empty, Set.empty)
}

/** All implementation classes of traits (classes that end in "$" followed by "class"). */
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
object A#B does not have a correspondent in new version
object A#B does not have a correspondent in new version
object A#B#C does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
object A {
object B {
def foo: Int = 2
object C
}
}

0 comments on commit 615b35a

Please sign in to comment.