Skip to content

Commit 8d93547

Browse files
committed
Unpickle "privateWith", aka "package private"
Anything which is package private is now excluded from the problem analysis MiMa does. This includes: * classes/traits/objects, * methods/fields, * method overloads, * as well as classes becoming private/public. * case classes, or more generally class/object companions * nested classes/objects (tested up to 3 levels)
1 parent 3e9f383 commit 8d93547

File tree

97 files changed

+1464
-159
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1464
-159
lines changed

core/src/main/scala/com/typesafe/tools/mima/core/BufferReader.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@ private[core] sealed class BytesReader(buf: Array[Byte]) {
1818
final def getDouble(idx: Int): Double = longBitsToDouble(getLong(idx))
1919

2020
final def getString(idx: Int, len: Int): String = new String(buf, idx, len, StandardCharsets.UTF_8)
21+
22+
final def getBytes(idx: Int, bytes: Array[Byte]): Unit = System.arraycopy(buf, idx, bytes, 0, bytes.length)
2123
}
2224

2325
/** A BytesReader which also holds a mutable pointer to where it will read next. */
24-
private[core] final class BufferReader(buf: Array[Byte]) extends BytesReader(buf) {
26+
private[core] final class BufferReader(buf: Array[Byte], val path: String) extends BytesReader(buf) {
2527
/** the buffer pointer */
2628
var bp: Int = 0
2729

2830
def nextByte: Byte = { val b = getByte(bp); bp += 1; b }
2931
def nextChar: Char = { val c = getChar(bp); bp += 2; c } // Char = unsigned 2-bytes, aka u16
3032
def nextInt: Int = { val i = getInt(bp); bp += 4; i }
3133

34+
def acceptByte(exp: Byte, ctx: => String = "") = { val obt = nextByte; assert(obt == exp, s"Expected $exp, obtained $obt$ctx"); obt }
35+
def acceptChar(exp: Char, ctx: => String = "") = { val obt = nextChar; assert(obt == exp, s"Expected $exp, obtained $obt$ctx"); obt }
36+
3237
def skip(n: Int): Unit = bp += n
3338
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.typesafe.tools.mima.core
2+
3+
/**
4+
* Helper methods to serialize a byte array as String
5+
* that can be written as "modified" UTF-8 to classfiles.
6+
*
7+
* Modified UTF-8 is the same as UTF-8, except for 0x00,
8+
* which is represented as the "overlong" 0xC0 0x80.
9+
* Constant strings in classfiles use this encoding.
10+
*
11+
* Encoding (according to SID-10):
12+
* - The 8-bit bytes are split into 7-bit bytes, e.g., 0xff 0x0f becomes 0x7f 0x1f 0x00
13+
* - Every bit is incremented by 1 (modulo 0x80), in the example we get 0x00, 0x20 0x01
14+
* - 0x00 is mapped to the overlong encoding, so we get 0xC0 0x80 0x20 0x01
15+
*
16+
* The +1 increment should reduce the number of (overlong) zeros in the resulting string,
17+
* as 0x7f is (hoped to be) more common than 0x00.
18+
*/
19+
object ByteCodecs {
20+
/** Map 0xC0 0x80 to 0x00, then subtract 1 from each element. In-place. */
21+
def regenerateZero(src: Array[Byte]): Int = {
22+
var i = 0
23+
val srclen = src.length
24+
var j = 0
25+
while (i < srclen) {
26+
val in: Int = src(i) & 0xff
27+
if (in == 0xc0 && (src(i + 1) & 0xff) == 0x80) {
28+
src(j) = 0x7f
29+
i += 2
30+
} else if (in == 0) {
31+
src(j) = 0x7f
32+
i += 1
33+
} else {
34+
src(j) = (in - 1).toByte
35+
i += 1
36+
}
37+
j += 1
38+
}
39+
j
40+
}
41+
42+
def decode7to8(src: Array[Byte], srclen: Int): Int = {
43+
var i = 0
44+
var j = 0
45+
val dstlen = (srclen * 7 + 7) / 8
46+
while (i + 7 < srclen) {
47+
var out: Int = src(i).toInt
48+
var in: Byte = src(i + 1)
49+
src(j) = (out | (in & 0x01) << 7).toByte
50+
out = in >>> 1
51+
in = src(i + 2)
52+
src(j + 1) = (out | (in & 0x03) << 6).toByte
53+
out = in >>> 2
54+
in = src(i + 3)
55+
src(j + 2) = (out | (in & 0x07) << 5).toByte
56+
out = in >>> 3
57+
in = src(i + 4)
58+
src(j + 3) = (out | (in & 0x0f) << 4).toByte
59+
out = in >>> 4
60+
in = src(i + 5)
61+
src(j + 4) = (out | (in & 0x1f) << 3).toByte
62+
out = in >>> 5
63+
in = src(i + 6)
64+
src(j + 5) = (out | (in & 0x3f) << 2).toByte
65+
out = in >>> 6
66+
in = src(i + 7)
67+
src(j + 6) = (out | in << 1).toByte
68+
i += 8
69+
j += 7
70+
}
71+
if (i < srclen) {
72+
var out: Int = src(i).toInt
73+
if (i + 1 < srclen) {
74+
var in: Byte = src(i + 1)
75+
src(j) = (out | (in & 0x01) << 7).toByte; j += 1
76+
out = in >>> 1
77+
if (i + 2 < srclen) {
78+
in = src(i + 2)
79+
src(j) = (out | (in & 0x03) << 6).toByte; j += 1
80+
out = in >>> 2
81+
if (i + 3 < srclen) {
82+
in = src(i + 3)
83+
src(j) = (out | (in & 0x07) << 5).toByte; j += 1
84+
out = in >>> 3
85+
if (i + 4 < srclen) {
86+
in = src(i + 4)
87+
src(j) = (out | (in & 0x0f) << 4).toByte; j += 1
88+
out = in >>> 4
89+
if (i + 5 < srclen) {
90+
in = src(i + 5)
91+
src(j) = (out | (in & 0x1f) << 3).toByte; j += 1
92+
out = in >>> 5
93+
if (i + 6 < srclen) {
94+
in = src(i + 6)
95+
src(j) = (out | (in & 0x3f) << 2).toByte; j += 1
96+
out = in >>> 6
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
if (j < dstlen) src(j) = out.toByte
104+
}
105+
dstlen
106+
}
107+
108+
/**
109+
* Destructively decodes array xs and returns the length of the decoded array.
110+
*
111+
* Sometimes returns (length+1) of the decoded array. Example:
112+
*
113+
* scala> val enc = scala.reflect.internal.pickling.ByteCodecs.encode(Array(1,2,3))
114+
* enc: Array[Byte] = Array(2, 5, 13, 1)
115+
*
116+
* scala> scala.reflect.internal.pickling.ByteCodecs.decode(enc)
117+
* res43: Int = 4
118+
*
119+
* scala> enc
120+
* res44: Array[Byte] = Array(1, 2, 3, 0)
121+
*
122+
* However, this does not always happen.
123+
*/
124+
def decode(xs: Array[Byte]): Int = decode7to8(xs, regenerateZero(xs))
125+
}

core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ private[core] final class ConcreteClassInfo(owner: PackageInfo, val file: AbsFil
3838
private var loaded: Boolean = false
3939

4040
protected def afterLoading[A](x: => A) = {
41-
if (!loaded)
42-
try {
43-
ConsoleLogging.verbose(s"parsing $file")
44-
ClassfileParser.parseInPlace(this, file)
45-
} finally {
46-
loaded = true
47-
}
41+
if (!loaded) {
42+
loaded = true
43+
ConsoleLogging.verbose(s"parsing $file")
44+
ClassfileParser.parseInPlace(this, file)
45+
}
4846
x
4947
}
5048
}
@@ -60,10 +58,14 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
6058
final var _fields: Members[FieldInfo] = NoMembers
6159
final var _methods: Members[MethodInfo] = NoMembers
6260
final var _flags: Int = 0
61+
final var _scopedPrivate: Boolean = false
6362
final var _implClass: ClassInfo = NoClass
63+
final var _moduleClass: ClassInfo = NoClass
64+
final var _module: ClassInfo = NoClass
6465

6566
protected def afterLoading[A](x: => A): A
6667

68+
final def forceLoad: this.type = afterLoading(this)
6769
final def innerClasses: Seq[String] = afterLoading(_innerClasses)
6870
final def isLocalClass: Boolean = afterLoading(_isLocalClass)
6971
final def isTopLevel: Boolean = afterLoading(_isTopLevel)
@@ -72,20 +74,23 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
7274
final def fields: Members[FieldInfo] = afterLoading(_fields)
7375
final def methods: Members[MethodInfo] = afterLoading(_methods)
7476
final def flags: Int = afterLoading(_flags)
77+
final def isScopedPrivate: Boolean = afterLoading(_scopedPrivate)
7578
final def implClass: ClassInfo = { owner.setImplClasses; _implClass } // returns NoClass if this is not a trait
79+
final def moduleClass: ClassInfo = { owner.setModules; if (_moduleClass == NoClass) this else _moduleClass }
80+
final def module: ClassInfo = { owner.setModules; if (_module == NoClass) this else _module }
7681

77-
final def isTrait: Boolean = implClass ne NoClass // trait with some concrete methods or fields
78-
final def isModule: Boolean = bytecodeName.endsWith("$") // super scuffed
79-
final def isImplClass: Boolean = bytecodeName.endsWith("$class")
80-
final def isInterface: Boolean = ClassfileParser.isInterface(flags) // java interface or trait w/o impl methods
81-
final def isClass: Boolean = !isTrait && !isInterface // class, object or trait's impl class
82+
final def isTrait: Boolean = implClass ne NoClass // trait with some concrete methods or fields
83+
final def isModuleClass: Boolean = bytecodeName.endsWith("$") // super scuffed
84+
final def isImplClass: Boolean = bytecodeName.endsWith("$class")
85+
final def isInterface: Boolean = ClassfileParser.isInterface(flags) // java interface or trait w/o impl methods
86+
final def isClass: Boolean = !isTrait && !isInterface // class, object or trait's impl class
8287

8388
final def accessModifier: String = if (isProtected) "protected" else if (isPrivate) "private" else ""
84-
final def declarationPrefix: String = if (isModule) "object" else if (isTrait) "trait" else if (isInterface) "interface" else "class"
89+
final def declarationPrefix: String = if (isModuleClass) "object" else if (isTrait) "trait" else if (isInterface) "interface" else "class"
8590
final lazy val fullName: String = if (owner.isRoot) bytecodeName else s"${owner.fullName}.$bytecodeName"
86-
final def formattedFullName: String = formatClassName(if (isModule) fullName.init else fullName)
91+
final def formattedFullName: String = formatClassName(if (isModuleClass) fullName.init else fullName)
8792
final def description: String = s"$declarationPrefix $formattedFullName"
88-
final def classString: String = s"$accessModifier $declarationPrefix $formattedFullName".trim
93+
final def classString: String = s"$accessModifier $description".trim
8994

9095
lazy val superClasses: Set[ClassInfo] = {
9196
if (this == ClassInfo.ObjectClass) Set.empty

core/src/main/scala/com/typesafe/tools/mima/core/ClassfileConstants.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private[core] object ClassfileConstants {
88
final val JAVA_ACC_PROTECTED = 0x0004
99
final val JAVA_ACC_STATIC = 0x0008
1010
final val JAVA_ACC_FINAL = 0x0010
11+
final val JAVA_ACC_BRIDGE = 0x0040
1112
final val JAVA_ACC_INTERFACE = 0x0200
1213
final val JAVA_ACC_ABSTRACT = 0x0400
1314
final val JAVA_ACC_SYNTHETIC = 0x1000
@@ -43,4 +44,8 @@ private[core] object ClassfileConstants {
4344
final val VOID_TAG = 'V'
4445
final val OBJECT_TAG = 'L'
4546
final val ANNOTATION_TAG = '@'
47+
48+
final val STRING_TAG = 's'
49+
final val ENUM_TAG = 'e'
50+
final val CLASS_TAG = 'c'
4651
}

0 commit comments

Comments
 (0)