Skip to content

Commit

Permalink
Merge pull request #142 from retronym/topic/java-statics
Browse files Browse the repository at this point in the history
Add support for Java-defined static members
  • Loading branch information
szeiger committed Nov 29, 2016
2 parents ad7be47 + 87db82f commit f9cd0c1
Show file tree
Hide file tree
Showing 18 changed files with 98 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ abstract class ClassInfo(val owner: PackageInfo) extends HasDeclarationName with
def methods: Members = { ensureLoaded(); _methods }
override def flags: Int = _flags

/** currently not set! */
def isScala: Boolean = { ensureLoaded(); _isScala }
def isScalaUnsafe: Boolean = { _isScala }

def superClass_=(x: ClassInfo) = _superClass = x
def interfaces_=(x: List[ClassInfo]) = _interfaces = x
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,33 +192,27 @@ abstract class ClassfileParser(definitions: Definitions) {
throw new RuntimeException("bad constant pool tag " + in.buf(start) + " at byte " + start)
}

def parseFields(clazz: ClassInfo): Unit =
if (readFields(clazz)) clazz.fields = parseMembers(clazz)
else if (readMethods(clazz)) skipMembers()

def parseMethods(clazz: ClassInfo) =
if (readMethods(clazz)) clazz.methods = parseMembers(clazz)

def parseMembers(clazz: ClassInfo): Members = {
val takeStatics = clazz.isImplClass
val memberCount = in.nextChar
var members = new ArrayBuffer[MemberInfo]
for (i <- 0 until memberCount) {
val jflags = in.nextChar.toInt
if (isPrivate(jflags) || takeStatics != isStatic(jflags)) {
in.skip(4); skipAttributes()
if (isPrivate(jflags)) {
in.skip(4)
skipAttributes()
} else {
members += parseMember(clazz, jflags)
}
}
new Members(members)
}

def skipMembers() {
def skipMembers(): Members = {
val memberCount = in.nextChar
for (i <- 0 until memberCount) {
in.skip(6); skipAttributes()
}
NoMembers
}

def parseMember(clazz: ClassInfo, jflags: Int): MemberInfo = {
Expand Down Expand Up @@ -246,9 +240,12 @@ abstract class ClassfileParser(definitions: Definitions) {

clazz.superClass = parseSuperClass()
clazz.interfaces = parseInterfaces()
parseFields(clazz)
parseMethods(clazz)
if (readFields(clazz)) clazz.fields = parseMembers(clazz) else skipMembers()
val methods =
if (readMethods(clazz)) parseMembers(clazz) else skipMembers()
parseAttributes(clazz)
clazz.methods =
if (clazz.isScalaUnsafe && !clazz.isImplClass) methods.withoutStatic else methods
}

def skipAttributes() {
Expand All @@ -261,18 +258,19 @@ abstract class ClassfileParser(definitions: Definitions) {
def parseAttributes(c: ClassInfo) {
val attrCount = in.nextChar
for (i <- 0 until attrCount) {
val attrIndex = in.nextChar
val attrName = pool.getName(attrIndex)
val attrLen = in.nextInt
val attrEnd = in.bp + attrLen
if (attrName == "SourceFile") {
if(in.bp + 1 <= attrEnd) {
val attrNameIndex = in.nextChar
c.sourceFileName = pool.getName(attrNameIndex)
}
}

in.bp = attrEnd
val attrIndex = in.nextChar
val attrName = pool.getName(attrIndex)
val attrLen = in.nextInt
val attrEnd = in.bp + attrLen
if (attrName == "SourceFile") {
if (in.bp + 1 <= attrEnd) {
val attrNameIndex = in.nextChar
c.sourceFileName = pool.getName(attrNameIndex)
}
} else if (attrName == "Scala" || attrName == "ScalaSig") {
this.parsedClass.isScala = true
}
in.bp = attrEnd
}
}

Expand Down Expand Up @@ -356,7 +354,7 @@ object ClassfileParser {
(flags & JAVA_ACC_PROTECTED) != 0
@inline final def isPrivate(flags: Int) =
(flags & JAVA_ACC_PRIVATE) != 0
@inline final private def isStatic(flags: Int) =
@inline final def isStatic(flags: Int) =
(flags & JAVA_ACC_STATIC) != 0
@inline final private def hasAnnotation(flags: Int) =
(flags & JAVA_ACC_ANNOTATION) != 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class MemberInfo(val owner: ClassInfo, val bytecodeName: String, override val fl
(if(hasSyntheticName) (if(isExtensionMethod) "extension " else "synthetic ") else "") +
(if(isDeprecated) "deprecated " else "") + "method "+decodedName + tpe
def methodString = shortMethodString + " in " + owner.classString
def memberString = if (isMethod) methodString else fieldString
def defString = (if(isDeprecated) "@deprecated " else "") + "def " + decodedName + params.mkString("(", ",", ")") + ": " + tpe.resultType + " = "
def applyString = decodedName + params.mkString("(", ",", ")")

Expand Down Expand Up @@ -78,6 +79,8 @@ class MemberInfo(val owner: ClassInfo, val bytecodeName: String, override val fl

def nonAccessible: Boolean = !isAccessible

def isStatic: Boolean = ClassfileParser.isStatic(flags)

/** The name of the getter corresponding to this setter */
private def getterName: String = {
val sidx = setterIdx(bytecodeName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.typesafe.tools.mima.core
import collection.mutable
import collection.TraversableOnce

class Members(val members: TraversableOnce[MemberInfo]) {
class Members(members: TraversableOnce[MemberInfo]) {

private val bindings = new mutable.HashMap[String, List[MemberInfo]] {
override def default(key: String) = List()
Expand All @@ -13,7 +13,9 @@ class Members(val members: TraversableOnce[MemberInfo]) {
def iterator: Iterator[MemberInfo] =
for (ms <- bindings.valuesIterator; m <- ms.iterator) yield m
def get(name: String): Iterator[MemberInfo] = bindings(name).iterator

def withoutStatic: Members = new Members(iterator.filterNot(_.isStatic))
}


object NoMembers extends Members(Nil)
object NoMembers extends Members(Nil)
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ case class DirectAbstractMethodProblem(newmeth: MemberInfo) extends AbstractMeth
def description = affectedVersion => "abstract " + newmeth.methodString + " does not have a correspondent in " + affectedVersion + " version"
}

case class StaticVirtualMemberProblem(newmeth: MemberInfo) extends AbstractMethodProblem(newmeth) {
def description = affectedVersion => "non-static " + newmeth.memberString + " is static in " + affectedVersion + " version"
}
case class VirtualStaticMemberProblem(newmeth: MemberInfo) extends AbstractMethodProblem(newmeth) {
def description = affectedVersion => "static " + newmeth.memberString + " is non-static in " + affectedVersion + " version"
}

case class ReversedAbstractMethodProblem(newmeth: MemberInfo) extends MemberProblem(newmeth) {
def description = affectedVersion => "in " + affectedVersion + " version there is abstract " + newmeth.methodString + ", which does not have a correspondent"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.typesafe.tools.mima.lib

import com.typesafe.tools.mima.core.{Config, Settings, PathResolver}
import java.io.{BufferedInputStream, FileInputStream}

import com.typesafe.tools.mima.core.{Config, PathResolver, Settings}

import scala.io.Source
import scala.tools.nsc.util._

Expand All @@ -21,7 +24,10 @@ class CollectProblemsTest {
val problems = mima.collectProblems(oldJarPath, newJarPath).map(_.description("new"))

// load oracle
var expectedProblems = Source.fromFile(oraclePath).getLines.toList
val inputStream = new BufferedInputStream(new FileInputStream(oraclePath))
var expectedProblems = try {
Source.fromInputStream(inputStream).getLines.toList
} finally inputStream.close()

// diff between the oracle and the collected problems
val unexpectedProblems = problems.filterNot(expectedProblems.contains)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
method foo()Int in class A does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public class A {
public static int foo() { return 42; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
public class A {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
method foo(A)Int in class A does not have a correspondent in new version
non-static method bar()Int in class A is static in new version
non-static field fld in class A is static in new version
method this()Unit in class A#Nested does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {
public static int fld;
public static int foo(A a) { return 42; }
public static int bar() { return 42; }
public static class Nested {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {
public int fld;
public int foo() { return 42; }
public int bar() { return 42; }
public class Nested {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
static field fld in class A is non-static in new version
static method bar()Int in class A is non-static in new version
method foo()Int in class A does not have a correspondent in new version
method this(A)Unit in class A#Nested does not have a correspondent in new version
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {
public int fld;
public int foo() { return 42; }
public int bar() { return 42; }
public class Nested {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {
public static int fld;
public static int foo(A a) { return 42; }
public static int bar() { return 42; }
public static class Nested {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ private[analyze] abstract class BaseFieldChecker extends Checker[MemberInfo, Cla
Some(InaccessibleFieldProblem(newfld))
else if(field.sig != newfld.sig)
Some(IncompatibleFieldTypeProblem(field, newfld))
else if (field.isStatic && !newfld.isStatic)
Some(StaticVirtualMemberProblem(field))
else if (!field.isStatic && newfld.isStatic)
Some(VirtualStaticMemberProblem(field))
else
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.typesafe.tools.mima.lib.analyze.Checker
private[analyze] abstract class BaseMethodChecker extends Checker[MemberInfo, ClassInfo] {
import MethodRules._

protected val rules = Seq(AccessModifier, FinalModifier, AbstractModifier)
protected val rules = Seq(AccessModifier, FinalModifier, AbstractModifier, JavaStatic)

protected def check(method: MemberInfo, in: TraversableOnce[MemberInfo]): Option[Problem] = {
val meths = (in filter (method.params.size == _.params.size)).toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ private[method] object MethodRules {
}
}

object JavaStatic extends MethodRule {
def apply(thisMember: MemberInfo, thatMember: MemberInfo): Option[Problem] = {
if (thisMember.isStatic && !thatMember.isStatic) Some(StaticVirtualMemberProblem(thatMember))
else if (!thisMember.isStatic && thatMember.isStatic) Some(VirtualStaticMemberProblem(thatMember))
else None
}
}

}

0 comments on commit f9cd0c1

Please sign in to comment.