diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/core/classfile')
4 files changed, 1787 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala new file mode 100644 index 000000000..cad3a4132 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala @@ -0,0 +1,88 @@ +package dotty.tools +package dotc +package core +package classfile + +import java.lang.Float.intBitsToFloat +import java.lang.Double.longBitsToDouble + +import io.AbstractFile + +/** + * This class reads files byte per byte. Only used by ClassFileParser + * + * @author Philippe Altherr + * @version 1.0, 23/03/2004 + */ +class AbstractFileReader(val file: AbstractFile) { + + /** the buffer containing the file + */ + val buf: Array[Byte] = file.toByteArray + + /** the current input pointer + */ + var bp: Int = 0 + + /** return byte at offset 'pos' + */ + @throws(classOf[IndexOutOfBoundsException]) + def byteAt(pos: Int): Byte = buf(pos) + + /** read a byte + */ + @throws(classOf[IndexOutOfBoundsException]) + def nextByte: Byte = { + val b = buf(bp) + bp += 1 + b + } + + /** read some bytes + */ + def nextBytes(len: Int): Array[Byte] = { + bp += len + buf.slice(bp - len, bp) + } + + /** read a character + */ + def nextChar: Char = + (((nextByte & 0xff) << 8) + (nextByte & 0xff)).toChar + + /** read an integer + */ + def nextInt: Int = + ((nextByte & 0xff) << 24) + ((nextByte & 0xff) << 16) + + ((nextByte & 0xff) << 8) + (nextByte & 0xff) + + + /** extract a character at position bp from buf + */ + def getChar(mybp: Int): Char = + (((buf(mybp) & 0xff) << 8) + (buf(mybp + 1) & 0xff)).toChar + + /** extract an integer at position bp from buf + */ + def getInt(mybp: Int): Int = + ((buf(mybp ) & 0xff) << 24) + ((buf(mybp + 1) & 0xff) << 16) + + ((buf(mybp + 2) & 0xff) << 8) + (buf(mybp + 3) & 0xff) + + /** extract a long integer at position bp from buf + */ + def getLong(mybp: Int): Long = + (getInt(mybp).toLong << 32) + (getInt(mybp + 4) & 0xffffffffL) + + /** extract a float at position bp from buf + */ + def getFloat(mybp: Int): Float = intBitsToFloat(getInt(mybp)) + + /** extract a double at position bp from buf + */ + def getDouble(mybp: Int): Double = longBitsToDouble(getLong(mybp)) + + /** skip next 'n' bytes + */ + def skip(n: Int): Unit = { bp += n } + +} diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ByteCodecs.scala b/compiler/src/dotty/tools/dotc/core/classfile/ByteCodecs.scala new file mode 100644 index 000000000..badd9e560 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/ByteCodecs.scala @@ -0,0 +1,221 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2007-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ +package dotty.tools.dotc.core.classfile + +object ByteCodecs { + + def avoidZero(src: Array[Byte]): Array[Byte] = { + var i = 0 + val srclen = src.length + var count = 0 + while (i < srclen) { + if (src(i) == 0x7f) count += 1 + i += 1 + } + val dst = new Array[Byte](srclen + count) + i = 0 + var j = 0 + while (i < srclen) { + val in = src(i) + if (in == 0x7f) { + dst(j) = (0xc0).toByte + dst(j + 1) = (0x80).toByte + j += 2 + } else { + dst(j) = (in + 1).toByte + j += 1 + } + i += 1 + } + dst + } + + def regenerateZero(src: Array[Byte]): Int = { + var i = 0 + val srclen = src.length + var j = 0 + while (i < srclen) { + val in: Int = src(i) & 0xff + if (in == 0xc0 && (src(i + 1) & 0xff) == 0x80) { + src(j) = 0x7f + i += 2 + } else if (in == 0) { + src(j) = 0x7f + i += 1 + } else { + src(j) = (in - 1).toByte + i += 1 + } + j += 1 + } + j + } + + def encode8to7(src: Array[Byte]): Array[Byte] = { + val srclen = src.length + val dstlen = (srclen * 8 + 6) / 7 + val dst = new Array[Byte](dstlen) + var i = 0 + var j = 0 + while (i + 6 < srclen) { + var in: Int = src(i) & 0xff + dst(j) = (in & 0x7f).toByte + var out: Int = in >>> 7 + in = src(i + 1) & 0xff + dst(j + 1) = (out | (in << 1) & 0x7f).toByte + out = in >>> 6 + in = src(i + 2) & 0xff + dst(j + 2) = (out | (in << 2) & 0x7f).toByte + out = in >>> 5 + in = src(i + 3) & 0xff + dst(j + 3) = (out | (in << 3) & 0x7f).toByte + out = in >>> 4 + in = src(i + 4) & 0xff + dst(j + 4) = (out | (in << 4) & 0x7f).toByte + out = in >>> 3 + in = src(i + 5) & 0xff + dst(j + 5) = (out | (in << 5) & 0x7f).toByte + out = in >>> 2 + in = src(i + 6) & 0xff + dst(j + 6) = (out | (in << 6) & 0x7f).toByte + out = in >>> 1 + dst(j + 7) = out.toByte + i += 7 + j += 8 + } + if (i < srclen) { + var in: Int = src(i) & 0xff + dst(j) = (in & 0x7f).toByte; j += 1 + var out: Int = in >>> 7 + if (i + 1 < srclen) { + in = src(i + 1) & 0xff + dst(j) = (out | (in << 1) & 0x7f).toByte; j += 1 + out = in >>> 6 + if (i + 2 < srclen) { + in = src(i + 2) & 0xff + dst(j) = (out | (in << 2) & 0x7f).toByte; j += 1 + out = in >>> 5 + if (i + 3 < srclen) { + in = src(i + 3) & 0xff + dst(j) = (out | (in << 3) & 0x7f).toByte; j += 1 + out = in >>> 4 + if (i + 4 < srclen) { + in = src(i + 4) & 0xff + dst(j) = (out | (in << 4) & 0x7f).toByte; j += 1 + out = in >>> 3 + if (i + 5 < srclen) { + in = src(i + 5) & 0xff + dst(j) = (out | (in << 5) & 0x7f).toByte; j += 1 + out = in >>> 2 + } + } + } + } + } + if (j < dstlen) dst(j) = out.toByte + } + dst + } + + def decode7to8(src: Array[Byte], srclen: Int): Int = { + var i = 0 + var j = 0 + val dstlen = (srclen * 7 + 7) / 8 + while (i + 7 < srclen) { + var out: Int = src(i) + var in: Byte = src(i + 1) + src(j) = (out | (in & 0x01) << 7).toByte + out = in >>> 1 + in = src(i + 2) + src(j + 1) = (out | (in & 0x03) << 6).toByte + out = in >>> 2 + in = src(i + 3) + src(j + 2) = (out | (in & 0x07) << 5).toByte + out = in >>> 3 + in = src(i + 4) + src(j + 3) = (out | (in & 0x0f) << 4).toByte + out = in >>> 4 + in = src(i + 5) + src(j + 4) = (out | (in & 0x1f) << 3).toByte + out = in >>> 5 + in = src(i + 6) + src(j + 5) = (out | (in & 0x3f) << 2).toByte + out = in >>> 6 + in = src(i + 7) + src(j + 6) = (out | in << 1).toByte + i += 8 + j += 7 + } + if (i < srclen) { + var out: Int = src(i) + if (i + 1 < srclen) { + var in: Byte = src(i + 1) + src(j) = (out | (in & 0x01) << 7).toByte; j += 1 + out = in >>> 1 + if (i + 2 < srclen) { + in = src(i + 2) + src(j) = (out | (in & 0x03) << 6).toByte; j += 1 + out = in >>> 2 + if (i + 3 < srclen) { + in = src(i + 3) + src(j) = (out | (in & 0x07) << 5).toByte; j += 1 + out = in >>> 3 + if (i + 4 < srclen) { + in = src(i + 4) + src(j) = (out | (in & 0x0f) << 4).toByte; j += 1 + out = in >>> 4 + if (i + 5 < srclen) { + in = src(i + 5) + src(j) = (out | (in & 0x1f) << 3).toByte; j += 1 + out = in >>> 5 + if (i + 6 < srclen) { + in = src(i + 6) + src(j) = (out | (in & 0x3f) << 2).toByte; j += 1 + out = in >>> 6 + } + } + } + } + } + } + if (j < dstlen) src(j) = out.toByte + } + dstlen + } + + def encode(xs: Array[Byte]): Array[Byte] = avoidZero(encode8to7(xs)) + + /** + * Destructively decodes array xs and returns the length of the decoded array. + * + * Sometimes returns (length + 1) of the decoded array. Example: + * + * scala> val enc = reflect.generic.ByteCodecs.encode(Array(1,2,3)) + * enc: Array[Byte] = Array(2, 5, 13, 1) + * + * scala> reflect.generic.ByteCodecs.decode(enc) + * res43: Int = 4 + * + * scala> enc + * res44: Array[Byte] = Array(1, 2, 3, 0) + * + * However, this does not always happen. + */ + def decode(xs: Array[Byte]): Int = { + val len = regenerateZero(xs) + decode7to8(xs, len) + } +} + + + + + + + + diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala new file mode 100644 index 000000000..dd29fa49d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala @@ -0,0 +1,378 @@ +package dotty.tools.dotc +package core +package classfile + +import scala.annotation.switch + +object ClassfileConstants { + + final val JAVA_MAGIC = 0xCAFEBABE + final val JAVA_MAJOR_VERSION = 45 + final val JAVA_MINOR_VERSION = 3 + + /** (see http://java.sun.com/docs/books/jvms/second_edition/jvms-clarify.html) + * + * If the `ACC_INTERFACE` flag is set, the `ACC_ABSTRACT` flag must also + * be set (ch. 2.13.1). + * + * A class file cannot have both its `ACC_FINAL` and `ACC_ABSTRACT` flags + * set (ch. 2.8.2). + * + * A field may have at most one of its `ACC_PRIVATE`, `ACC_PROTECTED`, + * `ACC_PUBLIC` flags set (ch. 2.7.4). + * + * A field may not have both its `ACC_FINAL` and `ACC_VOLATILE` flags set + * (ch. 2.9.1). + * + * If a method has its `ACC_ABSTRACT` flag set it must not have any of its + * `ACC_FINAL`, `ACC_NATIVE`, `ACC_PRIVATE`, `ACC_STATIC`, `ACC_STRICT`, + * or `ACC_SYNCHRONIZED` flags set (ch. 2.13.3.2). + * + * All interface methods must have their `ACC_ABSTRACT` and + * `ACC_PUBLIC` flags set. + * + * Note for future reference: see this thread on ACC_SUPER and + * how its enforcement differs on the android vm. + * https://groups.google.com/forum/?hl=en#!topic/jvm-languages/jVhzvq8-ZIk + * + */ // Class Field Method + final val JAVA_ACC_PUBLIC = 0x0001 // X X X + final val JAVA_ACC_PRIVATE = 0x0002 // X X + final val JAVA_ACC_PROTECTED = 0x0004 // X X + final val JAVA_ACC_STATIC = 0x0008 // X X + final val JAVA_ACC_FINAL = 0x0010 // X X X + final val JAVA_ACC_SUPER = 0x0020 // X + final val JAVA_ACC_SYNCHRONIZED = 0x0020 // X + final val JAVA_ACC_VOLATILE = 0x0040 // X + final val JAVA_ACC_BRIDGE = 0x0040 // X + final val JAVA_ACC_TRANSIENT = 0x0080 // X + final val JAVA_ACC_VARARGS = 0x0080 // X + final val JAVA_ACC_NATIVE = 0x0100 // X + final val JAVA_ACC_INTERFACE = 0x0200 // X + final val JAVA_ACC_ABSTRACT = 0x0400 // X X + final val JAVA_ACC_STRICT = 0x0800 // X + final val JAVA_ACC_SYNTHETIC = 0x1000 // X X X + final val JAVA_ACC_ANNOTATION = 0x2000 // X + final val JAVA_ACC_ENUM = 0x4000 // X X + + // tags describing the type of a literal in the constant pool + final val CONSTANT_UTF8 = 1 + final val CONSTANT_UNICODE = 2 + final val CONSTANT_INTEGER = 3 + final val CONSTANT_FLOAT = 4 + final val CONSTANT_LONG = 5 + final val CONSTANT_DOUBLE = 6 + final val CONSTANT_CLASS = 7 + final val CONSTANT_STRING = 8 + final val CONSTANT_FIELDREF = 9 + final val CONSTANT_METHODREF = 10 + final val CONSTANT_INTFMETHODREF = 11 + final val CONSTANT_NAMEANDTYPE = 12 + + final val CONSTANT_METHODHANDLE = 15 + final val CONSTANT_METHODTYPE = 16 + final val CONSTANT_INVOKEDYNAMIC = 18 + + // tags describing the type of a literal in attribute values + final val BYTE_TAG = 'B' + final val CHAR_TAG = 'C' + final val DOUBLE_TAG = 'D' + final val FLOAT_TAG = 'F' + final val INT_TAG = 'I' + final val LONG_TAG = 'J' + final val SHORT_TAG = 'S' + final val BOOL_TAG = 'Z' + final val STRING_TAG = 's' + final val ENUM_TAG = 'e' + final val CLASS_TAG = 'c' + final val ARRAY_TAG = '[' + final val VOID_TAG = 'V' + final val TVAR_TAG = 'T' + final val OBJECT_TAG = 'L' + final val ANNOTATION_TAG = '@' + final val SCALA_NOTHING = "scala.runtime.Nothing$" + final val SCALA_NULL = "scala.runtime.Null$" + + + // tags describing the type of newarray + final val T_BOOLEAN = 4 + final val T_CHAR = 5 + final val T_FLOAT = 6 + final val T_DOUBLE = 7 + final val T_BYTE = 8 + final val T_SHORT = 9 + final val T_INT = 10 + final val T_LONG = 11 + + // JVM mnemonics + final val nop = 0x00 + final val aconst_null = 0x01 + final val iconst_m1 = 0x02 + + final val iconst_0 = 0x03 + final val iconst_1 = 0x04 + final val iconst_2 = 0x05 + final val iconst_3 = 0x06 + final val iconst_4 = 0x07 + final val iconst_5 = 0x08 + + final val lconst_0 = 0x09 + final val lconst_1 = 0x0a + final val fconst_0 = 0x0b + final val fconst_1 = 0x0c + final val fconst_2 = 0x0d + final val dconst_0 = 0x0e + final val dconst_1 = 0x0f + + final val bipush = 0x10 + final val sipush = 0x11 + final val ldc = 0x12 + final val ldc_w = 0x13 + final val ldc2_w = 0x14 + + final val iload = 0x15 + final val lload = 0x16 + final val fload = 0x17 + final val dload = 0x18 + final val aload = 0x19 + + final val iload_0 = 0x1a + final val iload_1 = 0x1b + final val iload_2 = 0x1c + final val iload_3 = 0x1d + final val lload_0 = 0x1e + final val lload_1 = 0x1f + final val lload_2 = 0x20 + final val lload_3 = 0x21 + final val fload_0 = 0x22 + final val fload_1 = 0x23 + final val fload_2 = 0x24 + final val fload_3 = 0x25 + final val dload_0 = 0x26 + final val dload_1 = 0x27 + final val dload_2 = 0x28 + final val dload_3 = 0x29 + final val aload_0 = 0x2a + final val aload_1 = 0x2b + final val aload_2 = 0x2c + final val aload_3 = 0x2d + final val iaload = 0x2e + final val laload = 0x2f + final val faload = 0x30 + final val daload = 0x31 + final val aaload = 0x32 + final val baload = 0x33 + final val caload = 0x34 + final val saload = 0x35 + + final val istore = 0x36 + final val lstore = 0x37 + final val fstore = 0x38 + final val dstore = 0x39 + final val astore = 0x3a + final val istore_0 = 0x3b + final val istore_1 = 0x3c + final val istore_2 = 0x3d + final val istore_3 = 0x3e + final val lstore_0 = 0x3f + final val lstore_1 = 0x40 + final val lstore_2 = 0x41 + final val lstore_3 = 0x42 + final val fstore_0 = 0x43 + final val fstore_1 = 0x44 + final val fstore_2 = 0x45 + final val fstore_3 = 0x46 + final val dstore_0 = 0x47 + final val dstore_1 = 0x48 + final val dstore_2 = 0x49 + final val dstore_3 = 0x4a + final val astore_0 = 0x4b + final val astore_1 = 0x4c + final val astore_2 = 0x4d + final val astore_3 = 0x4e + final val iastore = 0x4f + final val lastore = 0x50 + final val fastore = 0x51 + final val dastore = 0x52 + final val aastore = 0x53 + final val bastore = 0x54 + final val castore = 0x55 + final val sastore = 0x56 + + final val pop = 0x57 + final val pop2 = 0x58 + final val dup = 0x59 + final val dup_x1 = 0x5a + final val dup_x2 = 0x5b + final val dup2 = 0x5c + final val dup2_x1 = 0x5d + final val dup2_x2 = 0x5e + final val swap = 0x5f + + final val iadd = 0x60 + final val ladd = 0x61 + final val fadd = 0x62 + final val dadd = 0x63 + final val isub = 0x64 + final val lsub = 0x65 + final val fsub = 0x66 + final val dsub = 0x67 + final val imul = 0x68 + final val lmul = 0x69 + final val fmul = 0x6a + final val dmul = 0x6b + final val idiv = 0x6c + final val ldiv = 0x6d + final val fdiv = 0x6e + final val ddiv = 0x6f + final val irem = 0x70 + final val lrem = 0x71 + final val frem = 0x72 + final val drem = 0x73 + + final val ineg = 0x74 + final val lneg = 0x75 + final val fneg = 0x76 + final val dneg = 0x77 + + final val ishl = 0x78 + final val lshl = 0x79 + final val ishr = 0x7a + final val lshr = 0x7b + final val iushr = 0x7c + final val lushr = 0x7d + final val iand = 0x7e + final val land = 0x7f + final val ior = 0x80 + final val lor = 0x81 + final val ixor = 0x82 + final val lxor = 0x83 + final val iinc = 0x84 + + final val i2l = 0x85 + final val i2f = 0x86 + final val i2d = 0x87 + final val l2i = 0x88 + final val l2f = 0x89 + final val l2d = 0x8a + final val f2i = 0x8b + final val f2l = 0x8c + final val f2d = 0x8d + final val d2i = 0x8e + final val d2l = 0x8f + final val d2f = 0x90 + final val i2b = 0x91 + final val i2c = 0x92 + final val i2s = 0x93 + + final val lcmp = 0x94 + final val fcmpl = 0x95 + final val fcmpg = 0x96 + final val dcmpl = 0x97 + final val dcmpg = 0x98 + + final val ifeq = 0x99 + final val ifne = 0x9a + final val iflt = 0x9b + final val ifge = 0x9c + final val ifgt = 0x9d + final val ifle = 0x9e + final val if_icmpeq = 0x9f + final val if_icmpne = 0xa0 + final val if_icmplt = 0xa1 + final val if_icmpge = 0xa2 + final val if_icmpgt = 0xa3 + final val if_icmple = 0xa4 + final val if_acmpeq = 0xa5 + final val if_acmpne = 0xa6 + final val goto = 0xa7 + final val jsr = 0xa8 + final val ret = 0xa9 + final val tableswitch = 0xaa + final val lookupswitch = 0xab + final val ireturn = 0xac + final val lreturn = 0xad + final val freturn = 0xae + final val dreturn = 0xaf + final val areturn = 0xb0 + final val return_ = 0xb1 + + final val getstatic = 0xb2 + final val putstatic = 0xb3 + final val getfield = 0xb4 + final val putfield = 0xb5 + + final val invokevirtual = 0xb6 + final val invokespecial = 0xb7 + final val invokestatic = 0xb8 + final val invokeinterface = 0xb9 + final val xxxunusedxxxx = 0xba + + final val new_ = 0xbb + final val newarray = 0xbc + final val anewarray = 0xbd + final val arraylength = 0xbe + final val athrow = 0xbf + final val checkcast = 0xc0 + final val instanceof = 0xc1 + final val monitorenter = 0xc2 + final val monitorexit = 0xc3 + final val wide = 0xc4 + final val multianewarray = 0xc5 + final val ifnull = 0xc6 + final val ifnonnull = 0xc7 + final val goto_w = 0xc8 + final val jsr_w = 0xc9 + + // reserved opcodes + final val breakpoint = 0xca + final val impdep1 = 0xfe + final val impdep2 = 0xff + + import Flags._ + abstract class FlagTranslation { + + protected def baseFlags(jflags: Int) = EmptyFlags + protected def isClass: Boolean = false + + private def translateFlag(jflag: Int): FlagSet = (jflag: @switch) match { + case JAVA_ACC_PRIVATE => Private + case JAVA_ACC_PROTECTED => Protected + case JAVA_ACC_FINAL => Final + case JAVA_ACC_SYNTHETIC => Synthetic + case JAVA_ACC_STATIC => JavaStatic + case JAVA_ACC_ABSTRACT => if (isClass) Abstract else Deferred + case JAVA_ACC_INTERFACE => PureInterfaceCreationFlags | JavaDefined + case _ => EmptyFlags + } + + private def addFlag(base: FlagSet, jflag: Int): FlagSet = + if (jflag == 0) base else base | translateFlag(jflag) + + private def translateFlags(jflags: Int, baseFlags: FlagSet): FlagSet = { + val nflags = + if ((jflags & JAVA_ACC_ANNOTATION) == 0) jflags + else jflags & ~(JAVA_ACC_ABSTRACT | JAVA_ACC_INTERFACE) // annotations are neither abstract nor interfaces + var res: FlagSet = baseFlags | JavaDefined + res = addFlag(res, nflags & JAVA_ACC_PRIVATE) + res = addFlag(res, nflags & JAVA_ACC_PROTECTED) + res = addFlag(res, nflags & JAVA_ACC_FINAL) + res = addFlag(res, nflags & JAVA_ACC_SYNTHETIC) + res = addFlag(res, nflags & JAVA_ACC_STATIC) + res = addFlag(res, nflags & JAVA_ACC_ABSTRACT) + res = addFlag(res, nflags & JAVA_ACC_INTERFACE) + res + } + + def flags(jflags: Int): FlagSet = translateFlags(jflags, baseFlags(jflags)) + } + val classTranslation = new FlagTranslation { + override def isClass = true + } + val fieldTranslation = new FlagTranslation { + override def baseFlags(jflags: Int) = if ((jflags & JAVA_ACC_FINAL) == 0) Mutable else EmptyFlags + } + val methodTranslation = new FlagTranslation { + override def baseFlags(jflags: Int) = if ((jflags & JAVA_ACC_BRIDGE) != 0) Bridge else EmptyFlags + } +} diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala new file mode 100644 index 000000000..97a82e80d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -0,0 +1,1100 @@ +package dotty.tools +package dotc +package core +package classfile + +import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, Decorators._ +import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Positions._ +import ast.tpd._ +import java.io.{ File, IOException } +import java.lang.Integer.toHexString +import scala.collection.{ mutable, immutable } +import scala.collection.mutable.{ ListBuffer, ArrayBuffer } +import scala.annotation.switch +import typer.Checking.checkNonCyclic +import io.AbstractFile +import scala.util.control.NonFatal + +object ClassfileParser { + /** Marker trait for unpicklers that can be embedded in classfiles. */ + trait Embedded +} + +class ClassfileParser( + classfile: AbstractFile, + classRoot: ClassDenotation, + moduleRoot: ClassDenotation)(ictx: Context) { + + import ClassfileConstants._ + import ClassfileParser._ + + protected val in = new AbstractFileReader(classfile) + + protected val staticModule: Symbol = moduleRoot.sourceModule(ictx) + + protected val instanceScope: MutableScope = newScope // the scope of all instance definitions + protected val staticScope: MutableScope = newScope // the scope of all static definitions + protected var pool: ConstantPool = _ // the classfile's constant pool + + protected var currentClassName: Name = _ // JVM name of the current class + protected var classTParams = Map[Name,Symbol]() + + classRoot.info = (new NoCompleter).withDecls(instanceScope) + moduleRoot.info = (new NoCompleter).withDecls(staticScope).withSourceModule(_ => staticModule) + + private def currentIsTopLevel(implicit ctx: Context) = classRoot.owner is Flags.PackageClass + + private def mismatchError(c: Symbol) = + throw new IOException(s"class file '${in.file}' has location not matching its contents: contains $c") + + def run()(implicit ctx: Context): Option[Embedded] = try { + ctx.debuglog("[class] >> " + classRoot.fullName) + parseHeader + this.pool = new ConstantPool + parseClass() + } catch { + case e: RuntimeException => + if (ctx.debug) e.printStackTrace() + throw new IOException( + i"""class file $classfile is broken, reading aborted with ${e.getClass} + |${Option(e.getMessage).getOrElse("")}""") + } + + private def parseHeader(): Unit = { + val magic = in.nextInt + if (magic != JAVA_MAGIC) + throw new IOException(s"class file '${in.file}' has wrong magic number 0x${toHexString(magic)}, should be 0x${toHexString(JAVA_MAGIC)}") + val minorVersion = in.nextChar.toInt + val majorVersion = in.nextChar.toInt + if ((majorVersion < JAVA_MAJOR_VERSION) || + ((majorVersion == JAVA_MAJOR_VERSION) && + (minorVersion < JAVA_MINOR_VERSION))) + throw new IOException( + s"class file '${in.file}' has unknown version $majorVersion.$minorVersion, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION") + } + + /** Return the class symbol of the given name. */ + def classNameToSymbol(name: Name)(implicit ctx: Context): Symbol = innerClasses.get(name) match { + case Some(entry) => innerClasses.classSymbol(entry.externalName) + case None => ctx.requiredClass(name) + } + + var sawPrivateConstructor = false + + def parseClass()(implicit ctx: Context): Option[Embedded] = { + val jflags = in.nextChar + val isAnnotation = hasAnnotation(jflags) + val sflags = classTranslation.flags(jflags) + val isEnum = (jflags & JAVA_ACC_ENUM) != 0 + val nameIdx = in.nextChar + currentClassName = pool.getClassName(nameIdx) + + if (currentIsTopLevel) { + val c = pool.getClassSymbol(nameIdx) + if (c != classRoot.symbol) mismatchError(c) + } + + addEnclosingTParams() + + /** Parse parents for Java classes. For Scala, return AnyRef, since the real type will be unpickled. + * Updates the read pointer of 'in'. */ + def parseParents: List[Type] = { + val superType = if (isAnnotation) { in.nextChar; defn.AnnotationType } + else pool.getSuperClass(in.nextChar).typeRef + val ifaceCount = in.nextChar + var ifaces = for (i <- (0 until ifaceCount).toList) yield pool.getSuperClass(in.nextChar).typeRef + // Dotty deviation: was + // var ifaces = for (i <- List.range(0 until ifaceCount)) ... + // This does not typecheck because the type parameter of List is now lower-bounded by Int | Char. + // Consequently, no best implicit for the "Integral" evidence parameter of "range" + // is found. If we treat constant subtyping specially, we might be able + // to do something there. But in any case, the until should be more efficient. + + if (isAnnotation) ifaces = defn.ClassfileAnnotationType :: ifaces + superType :: ifaces + } + + val result = unpickleOrParseInnerClasses() + if (!result.isDefined) { + var classInfo: Type = TempClassInfoType(parseParents, instanceScope, classRoot.symbol) + // might be reassigned by later parseAttributes + val staticInfo = TempClassInfoType(List(), staticScope, moduleRoot.symbol) + + enterOwnInnerClasses + + classRoot.setFlag(sflags) + moduleRoot.setFlag(Flags.JavaDefined | Flags.ModuleClassCreationFlags) + setPrivateWithin(classRoot, jflags) + setPrivateWithin(moduleRoot, jflags) + setPrivateWithin(moduleRoot.sourceModule, jflags) + + for (i <- 0 until in.nextChar) parseMember(method = false) + for (i <- 0 until in.nextChar) parseMember(method = true) + classInfo = parseAttributes(classRoot.symbol, classInfo) + if (isAnnotation) addAnnotationConstructor(classInfo) + + val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, classRoot, moduleRoot) + if (companionClassMethod.exists) companionClassMethod.entered + val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, moduleRoot, classRoot) + if (companionModuleMethod.exists) companionModuleMethod.entered + + setClassInfo(classRoot, classInfo) + setClassInfo(moduleRoot, staticInfo) + } + + // eager load java enum definitions for exhaustivity check of pattern match + if (isEnum) { + instanceScope.toList.map(_.ensureCompleted()) + staticScope.toList.map(_.ensureCompleted()) + classRoot.setFlag(Flags.Enum) + moduleRoot.setFlag(Flags.Enum) + } + + result + } + + /** Add type parameters of enclosing classes */ + def addEnclosingTParams()(implicit ctx: Context): Unit = { + var sym = classRoot.owner + while (sym.isClass && !(sym is Flags.ModuleClass)) { + for (tparam <- sym.typeParams) { + classTParams = classTParams.updated(tparam.name.unexpandedName, tparam) + } + sym = sym.owner + } + } + + def parseMember(method: Boolean)(implicit ctx: Context): Unit = { + val start = indexCoord(in.bp) + val jflags = in.nextChar + val sflags = + if (method) Flags.Method | methodTranslation.flags(jflags) + else fieldTranslation.flags(jflags) + val name = pool.getName(in.nextChar) + if (!(sflags is Flags.Private) || name == nme.CONSTRUCTOR || ctx.settings.optimise.value) { + val member = ctx.newSymbol( + getOwner(jflags), name, sflags, memberCompleter, coord = start) + getScope(jflags).enter(member) + } + // skip rest of member for now + in.nextChar // info + skipAttributes + } + + val memberCompleter = new LazyType { + + def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { + val oldbp = in.bp + try { + in.bp = denot.symbol.coord.toIndex + val sym = denot.symbol + val jflags = in.nextChar + val isEnum = (jflags & JAVA_ACC_ENUM) != 0 + val name = pool.getName(in.nextChar) + val isConstructor = name eq nme.CONSTRUCTOR + + /** Strip leading outer param from constructor. + * Todo: Also strip trailing access tag for private inner constructors? + */ + def stripOuterParamFromConstructor() = innerClasses.get(currentClassName) match { + case Some(entry) if !isStatic(entry.jflags) => + val mt @ MethodType(paramnames, paramtypes) = denot.info + denot.info = mt.derivedMethodType(paramnames.tail, paramtypes.tail, mt.resultType) + case _ => + } + + /** Make return type of constructor be the enclosing class type, + * and make constructor type polymorphic in the type parameters of the class + */ + def normalizeConstructorInfo() = { + val mt @ MethodType(paramnames, paramtypes) = denot.info + val rt = classRoot.typeRef appliedTo (classRoot.typeParams map (_.typeRef)) + denot.info = mt.derivedMethodType(paramnames, paramtypes, rt) + addConstructorTypeParams(denot) + } + + denot.info = pool.getType(in.nextChar) + if (isEnum) denot.info = ConstantType(Constant(sym)) + if (isConstructor) stripOuterParamFromConstructor() + setPrivateWithin(denot, jflags) + denot.info = translateTempPoly(parseAttributes(sym, denot.info)) + if (isConstructor) normalizeConstructorInfo() + + if ((denot is Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0) + denot.info = arrayToRepeated(denot.info) + + // seal java enums + if (isEnum) { + val enumClass = sym.owner.linkedClass + if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.makeChild(sym)) + } + } finally { + in.bp = oldbp + } + } + } + + /** Map direct references to Object to references to Any */ + final def objToAny(tp: Type)(implicit ctx: Context) = + if (tp.isDirectRef(defn.ObjectClass) && !ctx.phase.erasedTypes) defn.AnyType else tp + + private def sigToType(sig: TermName, owner: Symbol = null)(implicit ctx: Context): Type = { + var index = 0 + val end = sig.length + def accept(ch: Char): Unit = { + assert(sig(index) == ch, (sig(index), ch)) + index += 1 + } + def subName(isDelimiter: Char => Boolean): TermName = { + val start = index + while (!isDelimiter(sig(index))) { index += 1 } + sig.slice(start, index) + } + // Warning: sigToType contains nested completers which might be forced in a later run! + // So local methods need their own ctx parameters. + def sig2type(tparams: immutable.Map[Name,Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { + val tag = sig(index); index += 1 + (tag: @switch) match { + case BYTE_TAG => defn.ByteType + case CHAR_TAG => defn.CharType + case DOUBLE_TAG => defn.DoubleType + case FLOAT_TAG => defn.FloatType + case INT_TAG => defn.IntType + case LONG_TAG => defn.LongType + case SHORT_TAG => defn.ShortType + case VOID_TAG => defn.UnitType + case BOOL_TAG => defn.BooleanType + case 'L' => + def processInner(tp: Type): Type = tp match { + case tp: TypeRef if !(tp.symbol.owner is Flags.ModuleClass) => + TypeRef(processInner(tp.prefix.widen), tp.name) + case _ => + tp + } + def processClassType(tp: Type): Type = tp match { + case tp: TypeRef => + if (sig(index) == '<') { + accept('<') + var tp1: Type = tp + var formals = tp.typeParamSymbols + while (sig(index) != '>') { + sig(index) match { + case variance @ ('+' | '-' | '*') => + index += 1 + val bounds = variance match { + case '+' => objToAny(TypeBounds.upper(sig2type(tparams, skiptvs))) + case '-' => + val tp = sig2type(tparams, skiptvs) + // sig2type seems to return AnyClass regardless of the situation: + // we don't want Any as a LOWER bound. + if (tp.isDirectRef(defn.AnyClass)) TypeBounds.empty + else TypeBounds.lower(tp) + case '*' => TypeBounds.empty + } + tp1 = RefinedType(tp1, formals.head.name, bounds) + case _ => + tp1 = RefinedType(tp1, formals.head.name, TypeAlias(sig2type(tparams, skiptvs))) + } + formals = formals.tail + } + accept('>') + tp1 + } else tp + case tp => + assert(sig(index) != '<', tp) + tp + } + + val classSym = classNameToSymbol(subName(c => c == ';' || c == '<')) + var tpe = processClassType(processInner(classSym.typeRef)) + while (sig(index) == '.') { + accept('.') + val name = subName(c => c == ';' || c == '<' || c == '.').toTypeName + val clazz = tpe.member(name).symbol + tpe = processClassType(processInner(clazz.typeRef)) + } + accept(';') + tpe + case ARRAY_TAG => + while ('0' <= sig(index) && sig(index) <= '9') index += 1 + var elemtp = sig2type(tparams, skiptvs) + // make unbounded Array[T] where T is a type variable into Ar ray[T with Object] + // (this is necessary because such arrays have a representation which is incompatible + // with arrays of primitive types. + // NOTE that the comparison to Object only works for abstract types bounded by classes that are strict subclasses of Object + // if the bound is exactly Object, it will have been converted to Any, and the comparison will fail + // see also RestrictJavaArraysMap (when compiling java sources directly) + if (elemtp.typeSymbol.isAbstractType && !(elemtp.derivesFrom(defn.ObjectClass))) { + elemtp = AndType(elemtp, defn.ObjectType) + } + defn.ArrayOf(elemtp) + case '(' => + // we need a method symbol. given in line 486 by calling getType(methodSym, ..) + val paramtypes = new ListBuffer[Type]() + var paramnames = new ListBuffer[TermName]() + while (sig(index) != ')') { + paramnames += nme.syntheticParamName(paramtypes.length) + paramtypes += objToAny(sig2type(tparams, skiptvs)) + } + index += 1 + val restype = sig2type(tparams, skiptvs) + JavaMethodType(paramnames.toList, paramtypes.toList)(_ => restype) + case 'T' => + val n = subName(';'.==).toTypeName + index += 1 + //assert(tparams contains n, s"classTparams = $classTParams, tparams = $tparams, key = $n") + if (skiptvs) defn.AnyType else tparams(n).typeRef + } + } // sig2type(tparams, skiptvs) + + def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { + val ts = new ListBuffer[Type] + while (sig(index) == ':') { + index += 1 + if (sig(index) != ':') // guard against empty class bound + ts += objToAny(sig2type(tparams, skiptvs)) + } + TypeBounds.upper(((NoType: Type) /: ts)(_ & _) orElse defn.AnyType) + } + + var tparams = classTParams + + def typeParamCompleter(start: Int) = new LazyType { + def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { + val savedIndex = index + try { + index = start + denot.info = + checkNonCyclic( // we need the checkNonCyclic call to insert LazyRefs for F-bounded cycles + denot.symbol, + sig2typeBounds(tparams, skiptvs = false), + reportErrors = false) + } finally { + index = savedIndex + } + } + } + + val newTParams = new ListBuffer[Symbol]() + if (sig(index) == '<') { + assert(owner != null) + index += 1 + val start = index + while (sig(index) != '>') { + val tpname = subName(':'.==).toTypeName + val expname = if (owner.isClass) tpname.expandedName(owner) else tpname + val s = ctx.newSymbol( + owner, expname, owner.typeParamCreationFlags, + typeParamCompleter(index), coord = indexCoord(index)) + if (owner.isClass) owner.asClass.enter(s) + tparams = tparams + (tpname -> s) + sig2typeBounds(tparams, skiptvs = true) + newTParams += s + } + index += 1 + } + val ownTypeParams = newTParams.toList.asInstanceOf[List[TypeSymbol]] + val tpe = + if ((owner == null) || !owner.isClass) + sig2type(tparams, skiptvs = false) + else { + classTParams = tparams + val parents = new ListBuffer[Type]() + while (index < end) { + parents += sig2type(tparams, skiptvs = false) // here the variance doesnt'matter + } + TempClassInfoType(parents.toList, instanceScope, owner) + } + if (ownTypeParams.isEmpty) tpe else TempPolyType(ownTypeParams, tpe) + } // sigToType + + def parseAnnotArg(skip: Boolean = false)(implicit ctx: Context): Option[Tree] = { + val tag = in.nextByte.toChar + val index = in.nextChar + tag match { + case STRING_TAG => + if (skip) None else Some(Literal(Constant(pool.getName(index).toString))) + case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | INT_TAG | + LONG_TAG | FLOAT_TAG | DOUBLE_TAG => + if (skip) None else Some(Literal(pool.getConstant(index))) + case CLASS_TAG => + if (skip) None else Some(Literal(Constant(pool.getType(index)))) + case ENUM_TAG => + val t = pool.getType(index) + val n = pool.getName(in.nextChar) + val module = t.typeSymbol.companionModule + val s = module.info.decls.lookup(n) + if (skip) { + None + } else if (s != NoSymbol) { + Some(Literal(Constant(s))) + } else { + ctx.warning(s"""While parsing annotations in ${in.file}, could not find $n in enum $module.\nThis is likely due to an implementation restriction: an annotation argument cannot refer to a member of the annotated class (SI-7014).""") + None + } + case ARRAY_TAG => + val arr = new ArrayBuffer[Tree]() + var hasError = false + for (i <- 0 until index) + parseAnnotArg(skip) match { + case Some(c) => arr += c + case None => hasError = true + } + if (hasError) None + else if (skip) None + else { + val elems = arr.toList + val elemType = + if (elems.isEmpty) defn.ObjectType + else ctx.typeComparer.lub(elems.tpes).widen + Some(JavaSeqLiteral(elems, TypeTree(elemType))) + } + case ANNOTATION_TAG => + parseAnnotation(index, skip) map (_.tree) + } + } + + /** Parse and return a single annotation. If it is malformed, + * return None. + */ + def parseAnnotation(attrNameIndex: Char, skip: Boolean = false)(implicit ctx: Context): Option[Annotation] = try { + val attrType = pool.getType(attrNameIndex) + val nargs = in.nextChar + val argbuf = new ListBuffer[Tree] + var hasError = false + for (i <- 0 until nargs) { + val name = pool.getName(in.nextChar) + parseAnnotArg(skip) match { + case Some(arg) => argbuf += NamedArg(name, arg) + case None => hasError = !skip + } + } + if (hasError || skip) None + else Some(Annotation.deferredResolve(attrType, argbuf.toList)) + } catch { + case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found + case NonFatal(ex) => + // We want to be robust when annotations are unavailable, so the very least + // we can do is warn the user about the exception + // There was a reference to ticket 1135, but that is outdated: a reference to a class not on + // the classpath would *not* end up here. A class not found is signaled + // with a `FatalError` exception, handled above. Here you'd end up after a NPE (for example), + // and that should never be swallowed silently. + ctx.warning("Caught: " + ex + " while parsing annotations in " + in.file) + if (ctx.debug) ex.printStackTrace() + + None // ignore malformed annotations + } + + def parseAttributes(sym: Symbol, symtype: Type)(implicit ctx: Context): Type = { + def convertTo(c: Constant, pt: Type): Constant = { + if (pt == defn.BooleanType && c.tag == IntTag) + Constant(c.value != 0) + else + c convertTo pt + } + var newType = symtype + + def parseAttribute(): Unit = { + val attrName = pool.getName(in.nextChar).toTypeName + val attrLen = in.nextInt + val end = in.bp + attrLen + attrName match { + case tpnme.SignatureATTR => + val sig = pool.getExternalName(in.nextChar) + newType = sigToType(sig, sym) + if (ctx.debug && ctx.verbose) + println("" + sym + "; signature = " + sig + " type = " + newType) + case tpnme.SyntheticATTR => + sym.setFlag(Flags.SyntheticArtifact) + case tpnme.BridgeATTR => + sym.setFlag(Flags.Bridge) + case tpnme.DeprecatedATTR => + val msg = Literal(Constant("see corresponding Javadoc for more information.")) + val since = Literal(Constant("")) + sym.addAnnotation(Annotation(defn.DeprecatedAnnot, msg, since)) + case tpnme.ConstantValueATTR => + val c = pool.getConstant(in.nextChar) + val c1 = convertTo(c, symtype) + if (c1 ne null) newType = ConstantType(c1) + else println("failure to convert " + c + " to " + symtype); //debug + case tpnme.AnnotationDefaultATTR => + sym.addAnnotation(Annotation(defn.AnnotationDefaultAnnot, Nil)) + // Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME + case tpnme.RuntimeAnnotationATTR => + parseAnnotations(attrLen) + + // TODO 1: parse runtime visible annotations on parameters + // case tpnme.RuntimeParamAnnotationATTR + + // TODO 2: also parse RuntimeInvisibleAnnotation / RuntimeInvisibleParamAnnotation, + // i.e. java annotations with RetentionPolicy.CLASS? + + case tpnme.ExceptionsATTR => + parseExceptions(attrLen) + + case tpnme.CodeATTR => + if (sym.owner is Flags.JavaTrait) { + sym.resetFlag(Flags.Deferred) + sym.owner.resetFlag(Flags.PureInterface) + ctx.log(s"$sym in ${sym.owner} is a java8+ default method.") + } + in.skip(attrLen) + + case _ => + } + in.bp = end + } + + /** + * Parse the "Exceptions" attribute which denotes the exceptions + * thrown by a method. + */ + def parseExceptions(len: Int): Unit = { + val nClasses = in.nextChar + for (n <- 0 until nClasses) { + // FIXME: this performs an equivalent of getExceptionTypes instead of getGenericExceptionTypes (SI-7065) + val cls = pool.getClassSymbol(in.nextChar.toInt) + sym.addAnnotation(ThrowsAnnotation(cls.asClass)) + } + } + + /** Parse a sequence of annotations and attaches them to the + * current symbol sym, except for the ScalaSignature annotation that it returns, if it is available. */ + def parseAnnotations(len: Int): Unit = { + val nAttr = in.nextChar + for (n <- 0 until nAttr) + parseAnnotation(in.nextChar) match { + case Some(annot) => + sym.addAnnotation(annot) + case None => + } + } + + // begin parseAttributes + for (i <- 0 until in.nextChar) { + parseAttribute() + } + newType + } + + /** Add synthetic constructor(s) and potentially also default getters which + * reflects the fields of the annotation with given `classInfo`. + * Annotations in Scala are assumed to get all their arguments as constructor + * parameters. For Java annotations we need to fake it by making up the constructor. + * Note that default getters have type Nothing. That's OK because we need + * them only to signal that the corresponding parameter is optional. + */ + def addAnnotationConstructor(classInfo: Type, tparams: List[TypeSymbol] = Nil)(implicit ctx: Context): Unit = { + def addDefaultGetter(attr: Symbol, n: Int) = + ctx.newSymbol( + owner = moduleRoot.symbol, + name = nme.CONSTRUCTOR.defaultGetterName(n), + flags = attr.flags & Flags.AccessFlags, + info = defn.NothingType).entered + + classInfo match { + case classInfo @ TempPolyType(tparams, restpe) if tparams.isEmpty => + addAnnotationConstructor(restpe, tparams) + case classInfo: TempClassInfoType => + val attrs = classInfo.decls.toList.filter(_.isTerm) + val targs = tparams.map(_.typeRef) + val paramNames = attrs.map(_.name.asTermName) + val paramTypes = attrs.map(_.info.resultType) + + def addConstr(ptypes: List[Type]) = { + val mtype = MethodType(paramNames, ptypes, classRoot.typeRef.appliedTo(targs)) + val constrType = if (tparams.isEmpty) mtype else TempPolyType(tparams, mtype) + val constr = ctx.newSymbol( + owner = classRoot.symbol, + name = nme.CONSTRUCTOR, + flags = Flags.Synthetic, + info = constrType + ).entered + for ((attr, i) <- attrs.zipWithIndex) + if (attr.hasAnnotation(defn.AnnotationDefaultAnnot)) { + constr.setFlag(Flags.HasDefaultParams) + addDefaultGetter(attr, i) + } + } + + addConstr(paramTypes) + + // The code below added an extra constructor to annotations where the + // last parameter of the constructor is an Array[X] for some X, the + // array was replaced by a vararg argument. Unfortunately this breaks + // inference when doing: + // @Annot(Array()) + // The constructor is overloaded so the expected type of `Array()` is + // WildcardType, and the type parameter of the Array apply method gets + // instantiated to `Nothing` instead of `X`. + // I'm leaving this commented out in case we improve inference to make this work. + // Note that if this is reenabled then JavaParser will also need to be modified + // to add the extra constructor (this was not implemented before). + /* + if (paramTypes.nonEmpty) + paramTypes.last match { + case defn.ArrayOf(elemtp) => + addConstr(paramTypes.init :+ defn.RepeatedParamType.appliedTo(elemtp)) + case _ => + } + */ + } + } + + /** Enter own inner classes in the right scope. It needs the scopes to be set up, + * and implicitly current class' superclasses. + */ + private def enterOwnInnerClasses()(implicit ctx: Context): Unit = { + def className(name: Name): Name = name.drop(name.lastIndexOf('.') + 1) + + def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile, jflags: Int) = { + ctx.base.loaders.enterClassAndModule( + getOwner(jflags), + entry.originalName, + new ClassfileLoader(file), + classTranslation.flags(jflags), + getScope(jflags)) + } + + for (entry <- innerClasses.values) { + // create a new class member for immediate inner classes + if (entry.outerName == currentClassName) { + val file = ctx.platform.classPath.findSourceFile(entry.externalName.toString) getOrElse { + throw new AssertionError(entry.externalName) + } + enterClassAndModule(entry, file, entry.jflags) + } + } + } + + /** Parse inner classes. Expects `in.bp` to point to the superclass entry. + * Restores the old `bp`. + * @return true iff classfile is from Scala, so no Java info needs to be read. + */ + def unpickleOrParseInnerClasses()(implicit ctx: Context): Option[Embedded] = { + val oldbp = in.bp + try { + skipSuperclasses() + skipMembers() // fields + skipMembers() // methods + val attrs = in.nextChar + val attrbp = in.bp + + def scan(target: TypeName): Boolean = { + in.bp = attrbp + var i = 0 + while (i < attrs && pool.getName(in.nextChar).toTypeName != target) { + val attrLen = in.nextInt + in.skip(attrLen) + i += 1 + } + i < attrs + } + + def unpickleScala(bytes: Array[Byte]): Some[Embedded] = { + val unpickler = new unpickleScala2.Scala2Unpickler(bytes, classRoot, moduleRoot)(ctx) + unpickler.run()(ctx.addMode(Mode.Scala2Unpickling)) + Some(unpickler) + } + + def unpickleTASTY(bytes: Array[Byte]): Some[Embedded] = { + val unpickler = new tasty.DottyUnpickler(bytes) + unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule)) + Some(unpickler) + } + + def parseScalaSigBytes: Array[Byte] = { + val tag = in.nextByte.toChar + assert(tag == STRING_TAG, tag) + pool getBytes in.nextChar + } + + def parseScalaLongSigBytes: Array[Byte] = { + val tag = in.nextByte.toChar + assert(tag == ARRAY_TAG, tag) + val stringCount = in.nextChar + val entries = + for (i <- 0 until stringCount) yield { + val stag = in.nextByte.toChar + assert(stag == STRING_TAG, stag) + in.nextChar.toInt + } + pool.getBytes(entries.toList) + } + + if (scan(tpnme.TASTYATTR)) { + val attrLen = in.nextInt + return unpickleTASTY(in.nextBytes(attrLen)) + } + + if (scan(tpnme.RuntimeAnnotationATTR)) { + val attrLen = in.nextInt + val nAnnots = in.nextChar + var i = 0 + while (i < nAnnots) { + val attrClass = pool.getType(in.nextChar).typeSymbol + val nArgs = in.nextChar + var j = 0 + while (j < nArgs) { + val argName = pool.getName(in.nextChar) + if (argName == nme.bytes) + if (attrClass == defn.ScalaSignatureAnnot) + return unpickleScala(parseScalaSigBytes) + else if (attrClass == defn.ScalaLongSignatureAnnot) + return unpickleScala(parseScalaLongSigBytes) + else if (attrClass == defn.TASTYSignatureAnnot) + return unpickleTASTY(parseScalaSigBytes) + else if (attrClass == defn.TASTYLongSignatureAnnot) + return unpickleTASTY(parseScalaLongSigBytes) + parseAnnotArg(skip = true) + j += 1 + } + i += 1 + } + } + + if (scan(tpnme.InnerClassesATTR)) { + val attrLen = in.nextInt + val entries = in.nextChar.toInt + for (i <- 0 until entries) { + val innerIndex = in.nextChar + val outerIndex = in.nextChar + val nameIndex = in.nextChar + val jflags = in.nextChar + if (innerIndex != 0 && outerIndex != 0 && nameIndex != 0) { + val entry = InnerClassEntry(innerIndex, outerIndex, nameIndex, jflags) + innerClasses(pool.getClassName(innerIndex)) = entry + } + } + } + None + } finally in.bp = oldbp + } + + /** An entry in the InnerClasses attribute of this class file. */ + case class InnerClassEntry(external: Int, outer: Int, name: Int, jflags: Int) { + def externalName = pool.getClassName(external) + def outerName = pool.getClassName(outer) + def originalName = pool.getName(name) + + override def toString = + originalName + " in " + outerName + "(" + externalName + ")" + } + + object innerClasses extends scala.collection.mutable.HashMap[Name, InnerClassEntry] { + /** Return the Symbol of the top level class enclosing `name`, + * or 'name's symbol if no entry found for `name`. + */ + def topLevelClass(name: Name)(implicit ctx: Context): Symbol = { + val tlName = if (isDefinedAt(name)) { + var entry = this(name) + while (isDefinedAt(entry.outerName)) + entry = this(entry.outerName) + entry.outerName + } else + name + classNameToSymbol(tlName) + } + + /** Return the class symbol for `externalName`. It looks it up in its outer class. + * Forces all outer class symbols to be completed. + * + * If the given name is not an inner class, it returns the symbol found in `defn`. + */ + def classSymbol(externalName: Name)(implicit ctx: Context): Symbol = { + /** Return the symbol of `innerName`, having the given `externalName`. */ + def innerSymbol(externalName: Name, innerName: Name, static: Boolean): Symbol = { + def getMember(sym: Symbol, name: Name): Symbol = + if (static) + if (sym == classRoot.symbol) staticScope.lookup(name) + else sym.companionModule.info.member(name).symbol + else + if (sym == classRoot.symbol) instanceScope.lookup(name) + else sym.info.member(name).symbol + + innerClasses.get(externalName) match { + case Some(entry) => + val outerName = entry.outerName.stripModuleClassSuffix + val owner = classSymbol(outerName) + val result = ctx.atPhaseNotLaterThanTyper { implicit ctx => + getMember(owner, innerName.toTypeName) + } + assert(result ne NoSymbol, + i"""failure to resolve inner class: + |externalName = $externalName, + |outerName = $outerName, + |innerName = $innerName + |owner.fullName = ${owner.showFullName} + |while parsing ${classfile}""") + result + + case None => + classNameToSymbol(externalName) + } + } + + get(externalName) match { + case Some(entry) => + innerSymbol(entry.externalName, entry.originalName, isStatic(entry.jflags)) + case None => + classNameToSymbol(externalName) + } + } + } + + def skipAttributes(): Unit = { + val attrCount = in.nextChar + for (i <- 0 until attrCount) { + in.skip(2); in.skip(in.nextInt) + } + } + + def skipMembers(): Unit = { + val memberCount = in.nextChar + for (i <- 0 until memberCount) { + in.skip(6); skipAttributes() + } + } + + def skipSuperclasses(): Unit = { + in.skip(2) // superclass + val ifaces = in.nextChar + in.skip(2 * ifaces) + } + + protected def getOwner(flags: Int): Symbol = + if (isStatic(flags)) moduleRoot.symbol else classRoot.symbol + + protected def getScope(flags: Int): MutableScope = + if (isStatic(flags)) staticScope else instanceScope + + private def setPrivateWithin(denot: SymDenotation, jflags: Int)(implicit ctx: Context): Unit = { + if ((jflags & (JAVA_ACC_PRIVATE | JAVA_ACC_PUBLIC)) == 0) + denot.privateWithin = denot.enclosingPackageClass + } + + private def isPrivate(flags: Int) = (flags & JAVA_ACC_PRIVATE) != 0 + private def isStatic(flags: Int) = (flags & JAVA_ACC_STATIC) != 0 + private def hasAnnotation(flags: Int) = (flags & JAVA_ACC_ANNOTATION) != 0 + + class ConstantPool { + private val len = in.nextChar + private val starts = new Array[Int](len) + private val values = new Array[AnyRef](len) + private val internalized = new Array[TermName](len) + + { var i = 1 + while (i < starts.length) { + starts(i) = in.bp + i += 1 + (in.nextByte.toInt: @switch) match { + case CONSTANT_UTF8 | CONSTANT_UNICODE => + in.skip(in.nextChar) + case CONSTANT_CLASS | CONSTANT_STRING | CONSTANT_METHODTYPE => + in.skip(2) + case CONSTANT_METHODHANDLE => + in.skip(3) + case CONSTANT_FIELDREF | CONSTANT_METHODREF | CONSTANT_INTFMETHODREF + | CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT + | CONSTANT_INVOKEDYNAMIC => + in.skip(4) + case CONSTANT_LONG | CONSTANT_DOUBLE => + in.skip(8) + i += 1 + case _ => + errorBadTag(in.bp - 1) + } + } + } + + /** Return the name found at given index. */ + def getName(index: Int): TermName = { + if (index <= 0 || len <= index) + errorBadIndex(index) + + values(index) match { + case name: TermName => name + case null => + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) + val name = termName(in.buf, start + 3, in.getChar(start + 1)) + values(index) = name + name + } + } + + /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */ + def getExternalName(index: Int): TermName = { + if (index <= 0 || len <= index) + errorBadIndex(index) + + if (internalized(index) == null) + internalized(index) = getName(index).replace('/', '.') + + internalized(index) + } + + def getClassSymbol(index: Int)(implicit ctx: Context): Symbol = { + if (index <= 0 || len <= index) errorBadIndex(index) + var c = values(index).asInstanceOf[Symbol] + if (c eq null) { + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) + val name = getExternalName(in.getChar(start + 1)) + if (name.isModuleClassName && (name ne nme.nothingRuntimeClass) && (name ne nme.nullRuntimeClass)) + // Null$ and Nothing$ ARE classes + c = ctx.requiredModule(name.sourceModuleName) + else c = classNameToSymbol(name) + values(index) = c + } + c + } + + /** Return the external name of the class info structure found at 'index'. + * Use 'getClassSymbol' if the class is sure to be a top-level class. + */ + def getClassName(index: Int): TermName = { + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) + getExternalName(in.getChar(start + 1)) + } + + /** Return a name and a type at the given index. + */ + private def getNameAndType(index: Int, ownerTpe: Type)(implicit ctx: Context): (Name, Type) = { + if (index <= 0 || len <= index) errorBadIndex(index) + var p = values(index).asInstanceOf[(Name, Type)] + if (p eq null) { + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_NAMEANDTYPE) errorBadTag(start) + val name = getName(in.getChar(start + 1).toInt) + var tpe = getType(in.getChar(start + 3).toInt) + // fix the return type, which is blindly set to the class currently parsed + if (name == nme.CONSTRUCTOR) + tpe match { + case tp: MethodType => + tp.derivedMethodType(tp.paramNames, tp.paramTypes, ownerTpe) + } + p = (name, tpe) + values(index) = p + } + p + } + + /** Return the type of a class constant entry. Since + * arrays are considered to be class types, they might + * appear as entries in 'newarray' or 'cast' opcodes. + */ + def getClassOrArrayType(index: Int)(implicit ctx: Context): Type = { + if (index <= 0 || len <= index) errorBadIndex(index) + val value = values(index) + var c: Type = null + if (value eq null) { + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) + val name = getExternalName(in.getChar(start + 1)) + if (name(0) == ARRAY_TAG) { + c = sigToType(name) + values(index) = c + } else { + val sym = classNameToSymbol(name) + values(index) = sym + c = sym.typeRef + } + } else c = value match { + case tp: Type => tp + case cls: Symbol => cls.typeRef + } + c + } + + def getType(index: Int)(implicit ctx: Context): Type = + sigToType(getExternalName(index)) + + def getSuperClass(index: Int)(implicit ctx: Context): Symbol = { + assert(index != 0, "attempt to parse java.lang.Object from classfile") + getClassSymbol(index) + } + + def getConstant(index: Int)(implicit ctx: Context): Constant = { + if (index <= 0 || len <= index) errorBadIndex(index) + var value = values(index) + if (value eq null) { + val start = starts(index) + value = (in.buf(start).toInt: @switch) match { + case CONSTANT_STRING => + Constant(getName(in.getChar(start + 1).toInt).toString) + case CONSTANT_INTEGER => + Constant(in.getInt(start + 1)) + case CONSTANT_FLOAT => + Constant(in.getFloat(start + 1)) + case CONSTANT_LONG => + Constant(in.getLong(start + 1)) + case CONSTANT_DOUBLE => + Constant(in.getDouble(start + 1)) + case CONSTANT_CLASS => + getClassOrArrayType(index).typeSymbol + case _ => + errorBadTag(start) + } + values(index) = value + } + value match { + case ct: Constant => ct + case cls: Symbol => Constant(cls.typeRef) + case arr: Type => Constant(arr) + } + } + + private def getSubArray(bytes: Array[Byte]): Array[Byte] = { + val decodedLength = ByteCodecs.decode(bytes) + val arr = new Array[Byte](decodedLength) + System.arraycopy(bytes, 0, arr, 0, decodedLength) + arr + } + + def getBytes(index: Int): Array[Byte] = { + if (index <= 0 || len <= index) errorBadIndex(index) + var value = values(index).asInstanceOf[Array[Byte]] + if (value eq null) { + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) + val len = in.getChar(start + 1) + val bytes = new Array[Byte](len) + System.arraycopy(in.buf, start + 3, bytes, 0, len) + value = getSubArray(bytes) + values(index) = value + } + value + } + + def getBytes(indices: List[Int]): Array[Byte] = { + assert(!indices.isEmpty, indices) + var value = values(indices.head).asInstanceOf[Array[Byte]] + if (value eq null) { + val bytesBuffer = ArrayBuffer.empty[Byte] + for (index <- indices) { + if (index <= 0 || ConstantPool.this.len <= index) errorBadIndex(index) + val start = starts(index) + if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) + val len = in.getChar(start + 1) + bytesBuffer ++= in.buf.view(start + 3, start + 3 + len) + } + value = getSubArray(bytesBuffer.toArray) + values(indices.head) = value + } + value + } + + /** Throws an exception signaling a bad constant index. */ + private def errorBadIndex(index: Int) = + throw new RuntimeException("bad constant pool index: " + index + " at pos: " + in.bp) + + /** Throws an exception signaling a bad tag at given address. */ + private def errorBadTag(start: Int) = + throw new RuntimeException("bad constant pool tag " + in.buf(start) + " at byte " + start) + } +} + |