Merge pull request #2268 from twitter-forks/stuhood/fix-system-inner-classes

Fix support for parsing of classes located in runtime jars
This commit is contained in:
eugene yokota 2015-11-10 23:35:18 -05:00
commit be9f1cdf95
6 changed files with 61 additions and 30 deletions

View File

@ -89,10 +89,8 @@ object ClassToAPI {
}
/** TODO: over time, ClassToAPI should switch the majority of access to the classfile parser */
private[this] def classFileForClass(c: Class[_]): ClassFile = {
val file = new java.io.File(IO.classLocationFile(c), s"${c.getName.replace('.', '/')}.class")
classfile.Parser.apply(file)
}
private[this] def classFileForClass(c: Class[_]): ClassFile =
classfile.Parser.apply(IO.classfileLocation(c))
private[this] def lzyS[T <: AnyRef](t: T): xsbti.api.Lazy[T] = lzy(t)
def lzy[T <: AnyRef](t: => T): xsbti.api.Lazy[T] = xsbti.SafeLazy(t)

View File

@ -10,7 +10,6 @@ import java.io.File
private[sbt] trait ClassFile {
val majorVersion: Int
val minorVersion: Int
val fileName: String
val className: String
val superClassName: String
val interfaceNames: Array[String]

View File

@ -4,6 +4,7 @@
package sbt
package classfile
import java.net.URL
import java.io.{ DataInputStream, File, InputStream }
import scala.annotation.switch
@ -15,15 +16,19 @@ import scala.annotation.switch
import Constants._
private[sbt] object Parser {
def apply(file: File): ClassFile = Using.fileInputStream(file)(parse(file.getAbsolutePath)).right.get
private def parse(fileName: String)(is: InputStream): Either[String, ClassFile] = Right(parseImpl(fileName, is))
private def parseImpl(filename: String, is: InputStream): ClassFile =
def apply(file: File): ClassFile =
Using.fileInputStream(file)(parse(file.toString)).right.get
def apply(url: URL): ClassFile =
Using.urlInputStream(url)(parse(url.toString)).right.get
private def parse(readableName: String)(is: InputStream): Either[String, ClassFile] = Right(parseImpl(readableName, is))
private def parseImpl(readableName: String, is: InputStream): ClassFile =
{
val in = new DataInputStream(is)
new ClassFile {
assume(in.readInt() == JavaMagic, "Invalid class file: " + fileName)
assume(in.readInt() == JavaMagic, "Invalid class file: " + readableName)
val fileName = filename
new ClassFile {
val minorVersion: Int = in.readUnsignedShort()
val majorVersion: Int = in.readUnsignedShort()

View File

@ -0,0 +1,24 @@
package sbt
package classfile
import util.Try
import org.scalacheck._
import Prop._
object ParserSpecification extends Properties("Parser") {
property("able to parse all relevant classes") =
Prop.forAll(classes) { (c: Class[_]) =>
Parser(IO.classfileLocation(c)) ne null
}
implicit def classes: Gen[Class[_]] =
Gen.oneOf(
this.getClass,
classOf[java.lang.Integer],
classOf[java.util.AbstractMap.SimpleEntry[String, String]],
classOf[String],
classOf[Thread],
classOf[Properties]
)
}

View File

@ -36,39 +36,43 @@ object IO {
val utf8 = Charset.forName("UTF-8")
/**
* Returns a URL for the directory or jar containing the the class file `cl`.
* Returns a URL for the classfile containing the given class
* If the location cannot be determined, an error is generated.
*/
def classLocation(cl: Class[_]): URL = {
val codeSource = cl.getProtectionDomain.getCodeSource
if (codeSource ne null) {
codeSource.getLocation
} else {
// NB: This assumes that classes without code sources are System classes, and thus located in
// jars. It assumes that `urlAsFile` will truncate to the containing jar file.
val clsfile = s"${cl.getName.replace('.', '/')}.class"
Option(ClassLoader.getSystemClassLoader.getResource(clsfile))
.flatMap {
urlAsFile
}.getOrElse {
sys.error("No class location for " + cl)
}.toURI.toURL
def classfileLocation(cl: Class[_]): URL = {
val clsfile = s"${cl.getName.replace('.', '/')}.class"
try {
Stream(Option(cl.getClassLoader), Some(ClassLoader.getSystemClassLoader)).flatten.flatMap { classLoader =>
Option(classLoader.getResource(clsfile))
}.headOption.getOrElse {
sys.error("No class location for " + cl)
}
} catch {
case e =>
e.printStackTrace()
throw e
}
}
/**
* Returns the directory or jar file containing the the class file `cl`.
* If the location cannot be determined or it is not a file, an error is generated.
* Returns the directory or jar file containing the class file `cl`.
* If the location cannot be determined or if it is not a file, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl))
def classLocationFile(cl: Class[_]): File = {
val classURL =
Option(cl.getProtectionDomain.getCodeSource).getOrElse {
sys.error("No class location for " + cl)
}.getLocation
toFile(classURL)
}
/**
* Returns a URL for the directory or jar containing the class file for type `T` (as determined by an implicit Manifest).
* If the location cannot be determined, an error is generated.
* Note that Java standard library classes typically do not have a location associated with them.
*/
def classLocation[T](implicit mf: SManifest[T]): URL = classLocation(mf.runtimeClass)
def classfileLocation[T](implicit mf: SManifest[T]): URL = classfileLocation(mf.runtimeClass)
/**
* Returns the directory or jar file containing the the class file for type `T` (as determined by an implicit Manifest).

View File

@ -18,6 +18,7 @@ object IOSpecification extends Properties("IO") {
Gen.oneOf(
this.getClass,
classOf[java.lang.Integer],
classOf[java.util.AbstractMap.SimpleEntry[String, String]],
classOf[String],
classOf[Thread],
classOf[Properties]