开发者

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.

How can I tell what Scala version a .class file was compiled with?

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.

How can I tell what Scala version a .class file was compiled with?

  • 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 the scala.tools.nsc.symtab.PickleFormat source file (now 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 $*
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜