Skip to content

Commit

Permalink
Merge pull request #49 from dotta/issue/use-decoded-names-when-checki…
Browse files Browse the repository at this point in the history
…ng-accessibility-of-class-46

Use decoded names when checking if a classfile is reachable from client code
  • Loading branch information
dotta committed Sep 23, 2013
2 parents 83556da + 5bfde65 commit 7c87c2a
Show file tree
Hide file tree
Showing 17 changed files with 61 additions and 40 deletions.
28 changes: 13 additions & 15 deletions core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import com.typesafe.tools.mima.core.util.log.{ConsoleLogging, Logging}
/** A placeholder class info for a class that is not found on the classpath or in a given
* package.
*/
class SyntheticClassInfo(owner: PackageInfo, val name: String) extends ClassInfo(owner) {
class SyntheticClassInfo(owner: PackageInfo, override val bytecodeName: String) extends ClassInfo(owner) {
loaded = true
def file: AbstractFile = throw new UnsupportedOperationException
override lazy val superClasses = Nil
Expand All @@ -30,10 +30,10 @@ object NoClass extends SyntheticClassInfo(null, "<noclass>")

/** A class for which we have the classfile. */
class ConcreteClassInfo(owner: PackageInfo, val file: AbstractFile) extends ClassInfo(owner) {
def name = PackageInfo.className(file.name)
override def bytecodeName = PackageInfo.className(file.name)
}

abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with WithAccessFlags {
import ClassInfo._

def file: AbstractFile
Expand All @@ -45,12 +45,10 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
case _ => "compiler generated"
}

def name: String

lazy val fullName: String = {
assert(name != null)
if (owner.isRoot) name
else owner.fullName + "." + name
assert(bytecodeName != null)
if (owner.isRoot) bytecodeName
else owner.fullName + "." + bytecodeName
}

def formattedFullName = formatClassName(if (isObject) fullName.init else fullName)
Expand Down Expand Up @@ -115,7 +113,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
lookupClassMethods(name) ++ lookupInterfaceMethods(name)

def lookupConcreteTraitMethods(name: String): Iterator[MemberInfo] =
allTraits.toList.flatten(_.concreteMethods).filter(_.name == name).toIterator
allTraits.toList.flatten(_.concreteMethods).filter(_.bytecodeName == bytecodeName).toIterator

/** Is this class a non-trait that inherits !from a trait */
lazy val isClassInheritsTrait = !isInterface && _interfaces.exists(_.isTrait)
Expand Down Expand Up @@ -174,7 +172,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
if (this == ClassInfo.ObjectClass) Set.empty
else superClass.allTraits ++ directTraits

/** All traits inherited directly or indirectly by this class */
/** All interfaces inherited directly or indirectly by this class */
lazy val allInterfaces: Set[ClassInfo] =
if (this == ClassInfo.ObjectClass) Set.empty
else superClass.allInterfaces ++ interfaces ++ (interfaces flatMap (_.allInterfaces))
Expand All @@ -198,7 +196,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {

/** Does this class have an implementation (forwarder or accessor) of given method `m'? */
private def hasInstanceImpl(m: MemberInfo) =
methods.get(m.name) exists (_.sig == m.sig)
methods.get(m.bytecodeName) exists (_.sig == m.sig)

/** Does this implementation class have a static implementation of given method `m'? */
def hasStaticImpl(m: MemberInfo) = staticImpl(m).isDefined
Expand All @@ -209,7 +207,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
implClass match {
case impl: ConcreteClassInfo =>
assert(impl.isImplClass, impl)
impl.methods.get(m.name) find (im => hasImplSig(im.sig, m.sig))
impl.methods.get(m.bytecodeName) find (im => hasImplSig(im.sig, m.sig))

case _ => None
}
Expand All @@ -236,7 +234,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
}

/** Is this class an implementation class? */
lazy val isImplClass: Boolean = name endsWith PackageInfo.implClassSuffix
lazy val isImplClass: Boolean = bytecodeName endsWith PackageInfo.implClassSuffix

/** The implementation class corresponding to this trait */
private var _implClass: ClassInfo = NoClass
Expand All @@ -262,7 +260,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
ClassfileParser.isInterface(flags)
}

def isObject: Boolean = name.endsWith("$")
def isObject: Boolean = bytecodeName.endsWith("$")

/** Is this class public? */
/*
Expand All @@ -271,7 +269,7 @@ abstract class ClassInfo(val owner: PackageInfo) extends WithAccessFlags {
!ClassfileParser.isPrivate(flags)
}*/

override def toString = "class " + name
override def toString = "class " + bytecodeName

def shortDescription = {
// using 'description' because elsewhere objects' name are not correctly translated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ abstract class ClassfileParser(definitions: Definitions) {

/** Return true iff TraitSetter annotation found among attributes */
def parseAttributes(m: MemberInfo) {
val maybeTraitSetter = MemberInfo.maybeSetter(m.name)
val maybeTraitSetter = MemberInfo.maybeSetter(m.bytecodeName)
val attrCount = in.nextChar
for (i <- 0 until attrCount) {
val attrIndex = in.nextChar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.typesafe.tools.mima.core

import scala.reflect.NameTransformer

trait HasDeclarationName {
/** The name as found in the bytecode. */
def bytecodeName: String

/** The name as found in the original Scala source. */
final def decodedName: String = NameTransformer.decode(bytecodeName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ object MemberInfo {
def maybeSetter(name: String) = name.endsWith(setterSuffix)
}

class MemberInfo(val owner: ClassInfo, val name: String, override val flags: Int, val sig: String) extends WithAccessFlags {
override def toString = "def "+name+": "+ sig

def decodedName = NameTransformer.decode(name)
class MemberInfo(val owner: ClassInfo, val bytecodeName: String, override val flags: Int, val sig: String) extends HasDeclarationName with WithAccessFlags {
override def toString = "def " + bytecodeName + ": "+ sig

def fieldString = "field "+decodedName+" in "+owner.classString
def shortMethodString = (if(hasSyntheticName) "synthetic " else "") + (if(isDeprecated) "deprecated " else "") + "method "+decodedName + tpe
Expand Down Expand Up @@ -54,13 +52,13 @@ class MemberInfo(val owner: ClassInfo, val name: String, override val flags: Int

var codeOpt: Option[(Int, Int)] = None

def isClassConstructor = name == "<init>"
def isClassConstructor = bytecodeName == "<init>"

def needCode = isClassConstructor

import MemberInfo._

var isTraitSetter = maybeSetter(name) && setterIdx(name) >= 0
var isTraitSetter = maybeSetter(bytecodeName) && setterIdx(bytecodeName) >= 0

var isDeprecated = false

Expand All @@ -70,9 +68,9 @@ class MemberInfo(val owner: ClassInfo, val name: String, override val flags: Int

/** The name of the getter corresponding to this setter */
private def getterName: String = {
val sidx = setterIdx(name)
val sidx = setterIdx(bytecodeName)
val start = if (sidx >= 0) sidx + setterTag.length else 0
name.substring(start, name.length - setterSuffix.length)
bytecodeName.substring(start, bytecodeName.length - setterSuffix.length)
}

/** The getter that corresponds to this setter */
Expand All @@ -81,6 +79,6 @@ class MemberInfo(val owner: ClassInfo, val name: String, override val flags: Int
owner.methods.get(getterName) find (_.sig == argsig) get
}

def description: String = name+": "+sig+" from "+owner.description
def description: String = bytecodeName + ": " + sig + " from " + owner.description
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Members(val members: TraversableOnce[MemberInfo]) {
private val bindings = new mutable.HashMap[String, List[MemberInfo]] {
override def default(key: String) = List()
}
for (m <- members) bindings += m.name -> (m :: bindings(m.name))
for (m <- members) bindings += m.bytecodeName -> (m :: bindings(m.bytecodeName))

def iterator: Iterator[MemberInfo] =
for (ms <- bindings.valuesIterator; m <- ms.iterator) yield m
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ abstract class PackageInfo(val owner: PackageInfo) {
}

def isAccessible(clazz: ClassInfo, prefix: Set[ClassInfo]) = {
val idx = clazz.name.lastIndexOf("$")
val idx = clazz.decodedName.lastIndexOf("$")
lazy val isReachable =
if (idx < 0) prefix.isEmpty // class name contains no $
else (prefix exists (_.name == clazz.name.substring(0, idx))) // prefix before dollar is an accessible class detected previously
else (prefix exists (_.decodedName == clazz.decodedName.substring(0, idx))) // prefix before dollar is an accessible class detected previously
clazz.isPublic && isReachable
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ case class MissingTypesProblem(newclazz: ClassInfo, missing: Iterable[ClassInfo]

case class CyclicTypeReferenceProblem(clz: ClassInfo) extends TemplateProblem(clz) {
def description = {
"the type hierarchy of " + clz.description + " has changed in new version. Type " + clz.name + " appears to be a subtype of itself"
"the type hierarchy of " + clz.description + " has changed in new version. Type " + clz.bytecodeName + " appears to be a subtype of itself"
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
method ++()Int in class :: does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// old version
class :: {
def ++ = 42
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// new version
class ::
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
method ++()Int in class SpecialChars does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// old version
class SpecialChars {
def ++ = 42
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// new version
class SpecialChars
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class MiMaLib(classpath: JavaClassPath, val log: Logging = ConsoleLogging) {
private def comparePackages(oldpkg: PackageInfo, newpkg: PackageInfo) {
val traits = newpkg.traits // determine traits of new package first
for (oldclazz <- oldpkg.accessibleClasses) {
log.info("Analyzing class "+oldclazz.name)
newpkg.classes get oldclazz.name match {
log.info("Analyzing class "+oldclazz.bytecodeName)
newpkg.classes get oldclazz.bytecodeName match {
case None if oldclazz.isImplClass =>
// if it is missing a trait implementation class, then no error should be reported
// since there should be already errors, i.e., missing methods...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private[analyze] trait Analyzer extends Function2[ClassInfo, ClassInfo, List[Pro
analyze(oldclazz, newclazz)

def analyze(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
assert(oldclazz.name == newclazz.name)
assert(oldclazz.bytecodeName == newclazz.bytecodeName)
val templateProblems = analyzeTemplateDecl(oldclazz, newclazz)

if (templateProblems.exists(p => p.isInstanceOf[IncompatibleTemplateDefProblem] ||
Expand Down Expand Up @@ -77,7 +77,7 @@ private[analyze] class ClassAnalyzer extends Analyzer {
/** Analyze incompatibilities that may derive from methods in the `newclazz` */
override def analyzeNewClassMethods(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
for (newAbstrMeth <- newclazz.deferredMethods) yield {
oldclazz.lookupMethods(newAbstrMeth.name).find(_.sig == newAbstrMeth.sig) match {
oldclazz.lookupMethods(newAbstrMeth.bytecodeName).find(_.sig == newAbstrMeth.sig) match {
case None =>
val p = MissingMethodProblem(newAbstrMeth)
p.affectedVersion = Problem.ClassVersion.Old
Expand Down Expand Up @@ -106,7 +106,7 @@ private[analyze] class TraitAnalyzer extends Analyzer {
val res = collection.mutable.ListBuffer.empty[Problem]

for (newmeth <- newclazz.concreteMethods if !oldclazz.hasStaticImpl(newmeth)) {
if (!oldclazz.lookupMethods(newmeth.name).exists(_.sig == newmeth.sig)) {
if (!oldclazz.lookupMethods(newmeth.bytecodeName).exists(_.sig == newmeth.sig)) {
// this means that the method is brand new and therefore the implementation
// has to be injected
val problem = MissingMethodProblem(newmeth)
Expand All @@ -120,7 +120,7 @@ private[analyze] class TraitAnalyzer extends Analyzer {
}

for (newmeth <- newclazz.deferredMethods) {
val oldmeths = oldclazz.lookupMethods(newmeth.name)
val oldmeths = oldclazz.lookupMethods(newmeth.bytecodeName)
oldmeths find (_.sig == newmeth.sig) match {
case Some(oldmeth) => ()
case _ =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ private[analyze] abstract class BaseFieldChecker extends Checker[MemberInfo, Cla

def check(field: MemberInfo, in: ClassInfo): Option[Problem] = {
if (field.isAccessible) {
val newflds = in.lookupClassFields(field.name)
val newflds = in.lookupClassFields(field.bytecodeName)
if (newflds.hasNext) {
val newfld = newflds.next
if (!newfld.isPublic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ private[analyze] abstract class BaseMethodChecker extends Checker[MemberInfo, Cl
private[analyze] class ClassMethodChecker extends BaseMethodChecker {
def check(method: MemberInfo, inclazz: ClassInfo): Option[Problem] = {
if (method.isDeferred)
super.check(method, inclazz.lookupMethods(method.name))
super.check(method, inclazz.lookupMethods(method.bytecodeName))
else
super.check(method, inclazz.lookupClassMethods(method.name))
super.check(method, inclazz.lookupClassMethods(method.bytecodeName))
}
}

Expand All @@ -49,7 +49,7 @@ private[analyze] class TraitMethodChecker extends BaseMethodChecker {
if (method.owner.hasStaticImpl(method)) {
checkStaticImplMethod(method, inclazz)
} else {
super.check(method, inclazz.lookupMethods(method.name))
super.check(method, inclazz.lookupMethods(method.bytecodeName))
}
}

Expand All @@ -68,7 +68,7 @@ private[analyze] class TraitMethodChecker extends BaseMethodChecker {
// otherwise we check the all concrete trait methods and report
// either that the method is missing or that no method with the
// same signature exists. Either way, we expect that a problem is reported!
val prob = super.check(method, inclazz.lookupConcreteTraitMethods(method.name))
val prob = super.check(method, inclazz.lookupConcreteTraitMethods(method.bytecodeName))
assert(prob.isDefined)
prob
}
Expand Down

0 comments on commit 7c87c2a

Please sign in to comment.