How can I tell what Scala version a .class file was compiled with?
How can I tell what Scala version开发者_开发百科 a .class file was compiled with?
I suppose the information is stored in the "pickled" part of the .class
file, according to the 2008 "Reflecting Scala" rapport, from Yohann Coppel, under the supervision of Prof. Martin Odersky.
During the compilation process (represented on fig. 2), the Scala compiler generates two types of data.
- The first one is some classic Java bytecode, which can be read and executed by a standard Java virtual machine.
- The second one is what is called “Pickled data”, and represents the basic structure of the original source file.
This information is enclosed in a.class
file.
The Java bytecode specification allows the compiler to “define and emit class files containing new attributes in the attributes tables of class file structures”. These attributes are silently ignored by JVMs if they do not recognize them.
The Scala compiler generates pickled data for about any data structure in a Scala program, called symbols in the pickler context.
Symbols are stored linearly with the format shown on Fig. 3.
- The tag represents the type of data stored,
- then the length gives the length of the following data block.
- The data block can contain multiple information, such as the name of a symbol.
ScalaSig = "ScalaSig" Version Symtab
Version = Major_Nat Minor_Nat <====
Symtab = numberOfEntries_Nat {Entry}
The ScalaSig attribute definition.
A more complete definition can be found in thesource file (nowscala.tools.nsc.symtab.PickleFormat
scala.reflect.internal.pickling.PickleFormat
).
You can also see how to read the Pickled data in scala.tools.nsc.util.ShowPickled
.
This page shows a script (not tested) which will display the pickled data:
#!/bin/sh
#
# Shows the pickled scala data in a classfile.
if [ $# == 0 ] ; then
echo "Usage: $0 [--bare] [-cp classpath] <class*>"
exit 1
fi
TOOLSDIR=`dirname $0`
CPOF="$TOOLSDIR/cpof"
PACK="$TOOLSDIR/../build/pack/lib"
QUICK="$TOOLSDIR/../build/quick/classes"
STARR="$TOOLSDIR/../lib"
CP=""
if [ -f "${PACK}/scala-library.jar" ] ; then
CP=`${TOOLSDIR}/packcp`
elif [ -d "${QUICK}/library" ] ; then
CP=`${TOOLSDIR}/quickcp`
else
CP=`${TOOLSDIR}/starrcp`
fi
if [ "$1" == "-cp" ] ; then
shift
CP="${1}:${CP}"
shift
fi
java -cp "$CP" scala.tools.nsc.util.ShowPickled $*
You can see the Scala Major/Minor version in the class file if you use javap
with the verbose option. For example, the following is shown for a file compiled using scala 2.8.0 final:
javap -private -verbose T
Compiled from "SomeTest.scala"
public interface T
SourceFile: "SomeTest.scala"
ScalaSig: length = 0x3
05 00 00
RuntimeVisibleAnnotations: length = 0xB
00 01 00 06 00 01 00 07 73 00 08
minor version: 0
major version: 49
Constant pool:
const #1 = Asciz SourceFile;
const #2 = Asciz SomeTest.scala;
const #3 = Asciz s;
const #4 = Asciz ()Ljava/lang/String;;
const #5 = Asciz ScalaSig;
//etc etc...
while the following is the output of a file compiled using scala 2.7.7:
javap -verbose T2
Compiled from "SomeTest2.scala"
public interface T2
SourceFile: "SomeTest2.scala"
ScalaSig: length = 0x87
04 01 1B 06 08 01 02 FFFFFF84 FFFFFF90 FFFFFF80 FFFFFF91 00 05 02 02 54
32 0A 01 03 01 07 3C 65 6D 70 74 79 3E 03 00 13
02 00 06 10 02 07 0C 0D 01 08 0A 02 09 0A 01 04
6C 61 6E 67 0A 01 0B 01 04 6A 61 76 61 09 02 0D
08 02 06 4F 62 6A 65 63 74 08 05 0F 00 FFFFFF86 00 10
01 01 73 15 01 11 10 02 12 18 0E 02 13 16 0D 01
14 0A 01 15 01 05 73 63 61 6C 61 09 02 17 14 01
06 50 72 65 64 65 66 09 02 19 1A 02 06 53 74 72
69 6E 67 0A 02 17 14
minor version: 0
major version: 49
Constant pool:
const #1 = Asciz SourceFile;
const #2 = Asciz SomeTest2.scala;
//etc etc...
The first two bytes of the ScalaSig constant entry should represent the scala Major/Minor version, I believe, which are defined in PickleFormat. The 2.7.7 version of PickleFormat can be found here, and shows that the major/minor version differs from the 2.8.0 version.
I checked the 2.7.1 version of this class as well, but here the Major/Minor version is the same as the 2.7.7 one, so you may not be able to distinguish between minor scala versions by using this method.
Most probably you could parse the .class file and read the version from an attribute attached from the scala compiler onto the class file.
To learn more about the existance of such an attribute you might start at the sources of the scala compiler ( http://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala ).
To learn how to parse a .class file you might read in the spec ( http://jcp.org/aboutJava/communityprocess/final/jsr202/index.html ).
The example code I posted here ( Java Illegal class modifiers Exception code 0x209 ) might help at the implementation, too .
FWIW, Here's a version of VonC's script that sets the classpath to scala-library.jar and scala-compiler.jar
Tested under cygwin and linux, with scala 2.11.8 and 2.12.1, Should work under OSX. Doesn't seem to like --bare argument, however.
(requires scala to be in your PATH.)
#!/bin/bash
# Shows the pickled scala data in a classfile.
if [ $# == 0 ] ; then
echo "Usage: $0 [--bare] [-cp classpath] <class*>"
exit 1
fi
unset JAVA_TOOL_OPTIONS
[ -z "$SCALA_HOME" ] && SCALA_HOME=$(which scala | sed -e 's#/bin/scala##')
export OSTYPE=$(uname | tr '[A-Z]' '[a-z]' | sed -e 's#[_0-9].*##')
case $OSTYPE in
cygwin) SEP=";" ;;
*) SEP=":" ;;
esac
CP="${SCALA_HOME}/lib/scala-library.jar${SEP}${SCALA_HOME}/lib/scala-compiler.jar${SEP}${SCALA_HOME}/lib/scala-reflect.jar"
if [ "$1" == "-cp" ] ; then
shift
CP="${1}${SEP}${CP}"
shift
fi
java -cp "$CP" scala.tools.nsc.util.ShowPickled $*
精彩评论