Skip to content

Commit b20f56d

Browse files
authored
Merge pull request #647 from dwijnand/ignore-experimental
2 parents 3dac7ba + 8a6fc57 commit b20f56d

Some content is hidden

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

53 files changed

+2085
-197
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
name: ci
2+
13
on:
24
pull_request:
35
push:
46
branches: ['main']
5-
tags: ['v[0-9]']
7+
tags: ['[0-9]']
68

79
jobs:
810
build:
@@ -17,7 +19,7 @@ jobs:
1719
with:
1820
java-version: ${{ matrix.java }}
1921
- uses: coursier/cache-action@v6
20-
- run: "sbt test mimaReportBinaryIssues 'set sbtplugin/scriptedSbt := \"1.2.8\"' 'scripted sbt-mima-plugin/minimal' IntegrationTest/test"
22+
- run: "sbt test mimaReportBinaryIssues 'set sbtplugin/scriptedSbt := \"1.2.8\"' 'scripted sbt-mima-plugin/minimal'"
2123
testFunctional:
2224
needs: build
2325
strategy:
@@ -42,3 +44,11 @@ jobs:
4244
- uses: olafurpg/setup-scala@v13
4345
- uses: coursier/cache-action@v6
4446
- run: sbt "scripted sbt-mima-plugin/*${{ matrix.scripted }}"
47+
testIntegration:
48+
needs: build
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v2
52+
- uses: olafurpg/setup-scala@v13
53+
- uses: coursier/cache-action@v6
54+
- run: sbt IntegrationTest/test
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.typesafe.tools.mima.core
2+
3+
private[mima] final case class AnnotInfo(name: String)

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import java.lang.Double.longBitsToDouble
55
import java.nio.charset.StandardCharsets
66

77
/** Reads and interprets the bytes in a class file byte-buffer. */
8-
private[core] sealed class BytesReader(buf: Array[Byte]) {
8+
private[core] sealed abstract class BytesReader(buf: Array[Byte]) {
9+
def pos: Int
10+
def file: AbsFile
11+
912
final def getByte(idx: Int): Byte = buf(idx)
1013
final def getChar(idx: Int): Char = (((buf(idx) & 0xff) << 8) + (buf(idx + 1) & 0xff)).toChar
1114

@@ -17,15 +20,17 @@ private[core] sealed class BytesReader(buf: Array[Byte]) {
1720
final def getFloat(idx: Int): Float = intBitsToFloat(getInt(idx))
1821
final def getDouble(idx: Int): Double = longBitsToDouble(getLong(idx))
1922

23+
//final def getString(idx: Int, len: Int): String = new java.io.DataInputStream(new java.io.ByteArrayInputStream(buf, idx, len)).readUTF
2024
final def getString(idx: Int, len: Int): String = new String(buf, idx, len, StandardCharsets.UTF_8)
2125

2226
final def getBytes(idx: Int, bytes: Array[Byte]): Unit = System.arraycopy(buf, idx, bytes, 0, bytes.length)
2327
}
2428

2529
/** A BytesReader which also holds a mutable pointer to where it will read next. */
26-
private[core] final class BufferReader(buf: Array[Byte], val path: String) extends BytesReader(buf) {
30+
private[core] final class BufferReader(val file: AbsFile) extends BytesReader(file.toByteArray) {
2731
/** the buffer pointer */
2832
var bp: Int = 0
33+
def pos = bp
2934

3035
def nextByte: Byte = { val b = getByte(bp); bp += 1; b }
3136
def nextChar: Char = { val c = getChar(bp); bp += 2; c } // Char = unsigned 2-bytes, aka u16
@@ -35,4 +40,10 @@ private[core] final class BufferReader(buf: Array[Byte], val path: String) exten
3540
def acceptChar(exp: Char, ctx: => String = "") = { val obt = nextChar; assert(obt == exp, s"Expected $exp, obtained $obt$ctx"); obt }
3641

3742
def skip(n: Int): Unit = bp += n
43+
44+
def atIndex[T](i: Int)(body: => T): T = {
45+
val saved = bp
46+
bp = i
47+
try body finally bp = saved
48+
}
3849
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
5959
final var _methods: Members[MethodInfo] = NoMembers
6060
final var _flags: Int = 0
6161
final var _scopedPrivate: Boolean = false
62+
final var _annotations: List[AnnotInfo] = Nil
6263
final var _implClass: ClassInfo = NoClass
6364
final var _moduleClass: ClassInfo = NoClass
6465
final var _module: ClassInfo = NoClass
@@ -75,6 +76,7 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
7576
final def methods: Members[MethodInfo] = afterLoading(_methods)
7677
final def flags: Int = afterLoading(_flags)
7778
final def isScopedPrivate: Boolean = afterLoading(_scopedPrivate)
79+
final def annotations: List[AnnotInfo] = afterLoading(_annotations)
7880
final def implClass: ClassInfo = { owner.setImplClasses; _implClass } // returns NoClass if this is not a trait
7981
final def moduleClass: ClassInfo = { owner.setModules; if (_moduleClass == NoClass) this else _moduleClass }
8082
final def module: ClassInfo = { owner.setModules; if (_module == NoClass) this else _module }

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,13 @@ import java.nio.file._
44

55
import scala.collection.JavaConverters._
66

7-
private object AbsFile {
8-
def apply(p: Path): AbsFile = {
9-
val name = p.getFileName.toString.stripPrefix("/")
10-
val path = p.toString.stripPrefix("/")
11-
AbsFile(name)(path, () => Files.readAllBytes(p))
12-
}
13-
}
14-
15-
private[core] final case class AbsFile(name: String)(path: String, bytes: () => Array[Byte]) {
7+
private[core] final case class AbsFile(name: String)(val jpath: Path) {
168
// Not defined as a simple wrapper of java.nio.file.Path, because Path#equals uses its FileSystem,
179
// differently to scala-reflect's AbstractFile, which breaks things like `distinct`.
10+
def this(path: Path) = this(path.getFileName.toString.stripPrefix("/"))(path)
1811

19-
def toByteArray = bytes()
12+
val path = jpath.toString.stripPrefix("/")
13+
def toByteArray = Files.readAllBytes(jpath)
2014
override def toString = path
2115
}
2216

@@ -74,7 +68,7 @@ private[mima] object ClassPath {
7468

7569
private final case class JrtCp(fs: FileSystem) extends ClassPath {
7670
def packages(pkg: String) = packageToModules.keys.toStream.filter(pkgContains(pkg, _)).sorted
77-
def classes(pkg: String) = packageToModules(pkg).flatMap(pkgClasses(_, pkg)).sortBy(_.toString).map(AbsFile(_))
71+
def classes(pkg: String) = packageToModules(pkg).flatMap(pkgClasses(_, pkg)).sortBy(_.toString).map(new AbsFile(_))
7872
def asClassPathString = fs.toString
7973

8074
private val packageToModules = listDir(fs.getPath("/packages"))
@@ -84,7 +78,7 @@ private[mima] object ClassPath {
8478

8579
private final case class PathCp(src: Path)(root: Path) extends ClassPath {
8680
def packages(pkg: String) = listDir(pkgResolve(root, pkg)).filter(isPackage).map(pkgEntry(pkg, _))
87-
def classes(pkg: String) = listDir(pkgResolve(root, pkg)).filter(isClass).map(AbsFile(_))
81+
def classes(pkg: String) = listDir(pkgResolve(root, pkg)).filter(isClass).map(new AbsFile(_))
8882
def asClassPathString = src.toString
8983
}
9084

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.typesafe.tools.mima.core
22

33
import java.io.IOException
4+
import java.nio.file.Files
45

56
import ClassfileConstants._
67

@@ -56,14 +57,11 @@ final class ClassfileParser private (in: BufferReader, pool: ConstantPool) {
5657
case ScalaSignatureATTR => isScala = true
5758
case EnclosingMethodATTR => clazz._isLocalClass = true
5859
case InnerClassesATTR => clazz._innerClasses = parseInnerClasses(clazz)
60+
case TASTYATTR => parseTasty(clazz)
5961
case _ =>
6062
}
61-
if (isScala) {
62-
val end = in.bp
63-
in.bp = runtimeAnnotStart
64-
parsePickle(clazz)
65-
in.bp = end
66-
}
63+
if (isScala)
64+
in.atIndex(runtimeAnnotStart)(parsePickle(clazz))
6765
}
6866

6967
private def parseMemberAttributes(member: MemberInfo) = {
@@ -100,7 +98,7 @@ final class ClassfileParser private (in: BufferReader, pool: ConstantPool) {
10098
private def parsePickle(clazz: ClassInfo) = {
10199
def parseScalaSigBytes() = {
102100
in.acceptByte(STRING_TAG, s" for ${clazz.description}")
103-
pool.getBytes(in.nextChar, in.bp)
101+
pool.getBytes(in.nextChar)
104102
}
105103

106104
def parseScalaLongSigBytes() = {
@@ -109,7 +107,7 @@ final class ClassfileParser private (in: BufferReader, pool: ConstantPool) {
109107
in.acceptByte(STRING_TAG, s" for ${clazz.description}")
110108
in.nextChar.toInt
111109
}
112-
pool.getBytes(entries.toList, in.bp)
110+
pool.getBytes(entries.toList)
113111
}
114112

115113
def checkScalaSigAnnotArg() = {
@@ -142,7 +140,14 @@ final class ClassfileParser private (in: BufferReader, pool: ConstantPool) {
142140
}
143141
i += 1
144142
}
145-
MimaUnpickler.unpickleClass(new PickleBuffer(bytes), clazz, in.path)
143+
MimaUnpickler.unpickleClass(new PickleBuffer(bytes), clazz, in.file.path)
144+
}
145+
146+
private def parseTasty(clazz: ClassInfo) = {
147+
// TODO: sanity check UUIDs
148+
val tpath = pool.file.jpath.resolveSibling(pool.file.name.stripSuffix(".class") + ".tasty")
149+
val bytes = Files.readAllBytes(tpath)
150+
TastyUnpickler.unpickleClass(new TastyReader(bytes), clazz, tpath.toString)
146151
}
147152

148153
private final val ScalaSignatureAnnot = "Lscala.reflect.ScalaSignature;"
@@ -154,12 +159,13 @@ final class ClassfileParser private (in: BufferReader, pool: ConstantPool) {
154159
private final val RuntimeAnnotationATTR = "RuntimeVisibleAnnotations"
155160
private final val ScalaSignatureATTR = "ScalaSig"
156161
private final val SignatureATTR = "Signature"
162+
private final val TASTYATTR = "TASTY"
157163
}
158164

159165
object ClassfileParser {
160166
private[core] def parseInPlace(clazz: ClassInfo, file: AbsFile): Unit = {
161-
val in = new BufferReader(file.toByteArray, file.toString)
162-
parseHeader(in, file.toString)
167+
val in = new BufferReader(file)
168+
parseHeader(in, file)
163169
val pool = ConstantPool.parseNew(clazz.owner.definitions, in)
164170
val parser = new ClassfileParser(in, pool)
165171
parser.parseClass(clazz)
@@ -176,7 +182,7 @@ object ClassfileParser {
176182
def isSynthetic(flags: Int) = 0 != (flags & JAVA_ACC_SYNTHETIC)
177183
def isAnnotation(flags: Int) = 0 != (flags & JAVA_ACC_ANNOTATION)
178184

179-
private def parseHeader(in: BufferReader, file: String) = {
185+
private def parseHeader(in: BufferReader, file: AbsFile) = {
180186
val magic = in.nextInt
181187
if (magic != JAVA_MAGIC)
182188
throw new IOException(

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

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@ private[core] object ConstantPool {
1212
starts(i) = in.bp
1313
i += 1
1414
(in.nextByte.toInt: @switch) match {
15-
case CONSTANT_UTF8 | CONSTANT_UNICODE => in.skip(in.nextChar)
16-
case CONSTANT_CLASS | CONSTANT_STRING
17-
| CONSTANT_METHODTYPE
18-
| CONSTANT_MODULE | CONSTANT_PACKAGE => in.skip(2)
19-
case CONSTANT_METHODHANDLE => in.skip(3)
20-
case CONSTANT_INTEGER | CONSTANT_FLOAT
21-
| CONSTANT_FIELDREF | CONSTANT_METHODREF
22-
| CONSTANT_INTFMETHODREF
23-
| CONSTANT_NAMEANDTYPE
24-
| CONSTANT_INVOKEDYNAMIC => in.skip(4)
25-
case CONSTANT_LONG | CONSTANT_DOUBLE => in.skip(8); i += 1
26-
case tag => errorBadTag(tag, in.bp - 1)
15+
case CONSTANT_UTF8 | CONSTANT_UNICODE => in.skip(in.nextChar)
16+
case CONSTANT_CLASS | CONSTANT_STRING | CONSTANT_METHODTYPE => in.skip(2)
17+
case CONSTANT_MODULE | CONSTANT_PACKAGE => in.skip(2)
18+
case CONSTANT_METHODHANDLE => in.skip(3)
19+
case CONSTANT_FIELDREF | CONSTANT_METHODREF | CONSTANT_INTFMETHODREF => in.skip(4)
20+
case CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT => in.skip(4)
21+
case CONSTANT_INVOKEDYNAMIC => in.skip(4)
22+
case CONSTANT_LONG | CONSTANT_DOUBLE => in.skip(8); i += 1
23+
case tag => errorBadTag(tag, in.bp - 1)
2724
}
2825
}
2926
new ConstantPool(definitions, in, starts)
@@ -43,6 +40,8 @@ private[core]
4340
final class ConstantPool private (definitions: Definitions, in: BytesReader, starts: Array[Int]) {
4441
import ConstantPool._, ClassInfo.ObjectClass
4542

43+
def file: AbsFile = in.file
44+
4645
private val length = starts.length
4746
private val values = new Array[AnyRef](length)
4847
private val internalized = new Array[String](length)
@@ -76,35 +75,30 @@ final class ConstantPool private (definitions: Definitions, in: BytesReader, sta
7675

7776
def getSuperClass(index: Int): ClassInfo = if (index == 0) ObjectClass else getClassInfo(index)
7877

79-
def getBytes(index: Int, pos: Int): Array[Byte] = {
80-
if (index <= 0 || length <= index) errorBadIndex(index, pos: Int)
78+
def getBytes(index: Int): Array[Byte] = {
79+
if (index <= 0 || length <= index) errorBadIndex(index, in.pos)
8180
else values(index) match {
8281
case xs: Array[Byte] => xs
83-
case _ =>
84-
val start = firstExpecting(index, CONSTANT_UTF8)
85-
val len = in.getChar(start).toInt
86-
val bytes = new Array[Byte](len)
87-
in.getBytes(start + 2, bytes)
88-
recordAtIndex(getSubArray(bytes), index)
82+
case _ => recordAtIndex(getSubArray(readBytes(index)), index)
8983
}
9084
}
9185

92-
def getBytes(indices: List[Int], pos: Int): Array[Byte] = {
93-
val head = indices.head
94-
values(head) match {
86+
def getBytes(indices: List[Int]): Array[Byte] = {
87+
for (index <- indices) if (index <= 0 || length <= index) errorBadIndex(index, in.pos)
88+
val index = indices.head
89+
values(index) match {
9590
case xs: Array[Byte] => xs
96-
case _ =>
97-
val arr: Array[Byte] = indices.toArray.flatMap { index =>
98-
if (index <= 0 || length <= index) errorBadIndex(index, pos)
99-
val start = firstExpecting(index, CONSTANT_UTF8)
100-
val result = new Array[Byte](in.getChar(start).toInt)
101-
in.getBytes(start + 2, result)
102-
result
103-
}
104-
recordAtIndex(getSubArray(arr), head)
91+
case _ => recordAtIndex(getSubArray(indices.flatMap(readBytes).toArray), index)
10592
}
10693
}
10794

95+
private def readBytes(index: Int) = {
96+
val start = firstExpecting(index, CONSTANT_UTF8)
97+
val bytes = new Array[Byte](in.getChar(start).toInt)
98+
in.getBytes(start + 2, bytes)
99+
bytes
100+
}
101+
108102
private def indexedOrUpdate[A <: AnyRef, R <: A](arr: Array[A], index: Int)(mk: => R): R = {
109103
if (index <= 0 || index >= length)
110104
throw new RuntimeException(s"bad constant pool index: $index, length: $length")
@@ -120,7 +114,7 @@ final class ConstantPool private (definitions: Definitions, in: BytesReader, sta
120114
val start = starts(index)
121115
val tag = in.getByte(start).toInt
122116
if (tag == expectedTag) start + 1
123-
else ConstantPool.errorBadTag(tag, start)
117+
else errorBadTag(tag, start)
124118
}
125119

126120
private def getSubArray(bytes: Array[Byte]): Array[Byte] = {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ private[mima] final class FieldInfo(owner: ClassInfo, bytecodeName: String, flag
3636
private[mima] final class MethodInfo(owner: ClassInfo, bytecodeName: String, flags: Int, descriptor: String)
3737
extends MemberInfo(owner, bytecodeName, flags, descriptor)
3838
{
39+
final var _annotations: List[AnnotInfo] = Nil
40+
final def annotations: List[AnnotInfo] = _annotations
41+
3942
def methodString: String = s"$shortMethodString in ${owner.classString}"
4043
def shortMethodString: String = {
4144
val prefix = if (hasSyntheticName) if (isExtensionMethod) "extension " else "synthetic " else ""

0 commit comments

Comments
 (0)