pluotsorbet/parser.ts

913 строки
25 KiB
TypeScript

module J2ME {
declare var util;
import assert = J2ME.Debug.assert;
var utf8ToString = util.decodeUtf8Array;
module UTF8 {
export var Code = new Uint8Array([67, 111, 100, 101]);
export var InnerClasses = new Uint8Array([67, 111, 100, 101]);
}
function strcmp(a: Uint8Array, b: Uint8Array): boolean {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
export class Bytes {
u1: Uint8Array;
offset: number;
static arrays: string [][] = ArrayUtilities.makeArrays(128);
static getArray(length: number) {
return Reader.arrays[length];
}
constructor(buffer: Uint8Array, offset: number) {
this.u1 = buffer;
this.offset = offset;
}
clone() {
return new Bytes(this.u1, this.offset);
}
readU1() {
return this.u1[this.offset++];
}
peekU1() {
return this.u1[this.offset];
}
readU2() {
var u1 = this.u1;
var o = this.offset;
this.offset += 2;
return u1[o] << 8 | u1[o + 1];
}
peekU16() {
var u1 = this.u1;
var o = this.offset;
return u1[o] << 8 | u1[o + 1];
}
readU4() {
return this.readI32() >>> 0;
}
readI32() {
var o = this.offset;
var u1 = this.u1;
var a = u1[o + 0];
var b = u1[o + 1];
var c = u1[o + 2];
var d = u1[o + 3];
this.offset = o + 4;
return (a << 24) | (b << 16) | (c << 8) | d;
}
//readF32() {
// var data = this.view.getFloat32(this.offset, false);
// this.offset += 4;
// return data;
//}
//
//readF64() {
// var data = this.view.getFloat64(this.offset, false);
// this.offset += 8;
// return data;
//}
seek(offset: number): Bytes {
this.offset = offset;
return this;
}
skip(length: number): Bytes {
this.offset += length;
return this;
}
// Decode Java's modified UTF-8 (JVM specs, $ 4.4.7)
// http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html#7963
readStringFast(length: number): string {
var a = (length < 128) ? Reader.getArray(length) : new Array(length);
var i = 0, j = 0;
var o = this.offset;
var e = o + length;
var u1 = this.u1;
while (o < e) {
var x = u1[o++];
if (x <= 0x7f) {
// Code points in the range '\u0001' to '\u007F' are represented by a
// single byte.
// The 7 bits of data in the byte give the value of the code point
// represented.
a[j++] = String.fromCharCode(x);
} else if (x <= 0xdf) {
// The null code point ('\u0000') and code points in the range '\u0080'
// to '\u07FF' are represented by a pair of bytes x and y.
var y = u1[o++]
a[j++] = String.fromCharCode(((x & 0x1f) << 6) + (y & 0x3f));
} else {
// Code points in the range '\u0800' to '\uFFFF' are represented by 3
// bytes x, y, and z.
var y = u1[o++];
var z = u1[o++];
a[j++] = String.fromCharCode(((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f));
}
}
this.offset = o;
if (j !== a.length) {
var b = (j < 128) ? Reader.getArray(j) : new Array(j);
for (var i = 0; i < j; i++) {
b[i] = a[i];
}
a = b;
}
return a.join("");
}
readString(length) {
if (length === 1) {
var c = this.u1[this.offset];
if (c <= 0x7f) {
this.offset++;
return String.fromCharCode(c);
}
} else if (length < 128) {
return this.readStringFast(length);
}
return this.readStringSlow(length);
}
readStringSlow(length) {
// First try w/ TextDecoder, fallback to manually parsing if there was an
// error. This will handle parsing errors resulting from Java's modified
// UTF-8 implementation.
try {
// NB: no need to create a new slice.
var data = new Uint8Array(this.u1.buffer, this.offset, length);
var s = util.decodeUtf8Array(data);
this.offset += length;
return s;
} catch (e) {
return this.readStringFast(length);
}
}
readBytes(length): Uint8Array {
var data = this.u1.subarray(this.offset, this.offset + length);
this.offset += length;
return data;
}
//static readU4(u1: Uint8Array, o: number): number {
// return Bytes.readI32(u1, o) >>> 0;
//}
//
//static readI32(u1: Uint8Array, o: number): number {
// var a = u1[o + 0];
// var b = u1[o + 1];
// var c = u1[o + 2];
// var d = u1[o + 3];
// return (a << 24) | (b << 16) | (c << 8) | d;
//}
//
static readU16(u1: Uint8Array, o: number): number {
return u1[o] << 8 | u1[o + 1];
}
}
export class ExceptionHandler {
start_pc: number;
end_pc: number;
handler_pc: number;
catch_type: number;
}
export enum ACCESS_FLAGS {
ACC_PUBLIC = 0x0001,
ACC_PRIVATE = 0x0002,
ACC_PROTECTED = 0x0004,
ACC_STATIC = 0x0008,
ACC_FINAL = 0x0010,
ACC_SYNCHRONIZED = 0x0020,
ACC_VOLATILE = 0x0040,
ACC_TRANSIENT = 0x0080,
ACC_NATIVE = 0x0100,
ACC_INTERFACE = 0x0200,
ACC_ABSTRACT = 0x0400
}
export enum TAGS {
CONSTANT_Class = 7,
CONSTANT_Fieldref = 9,
CONSTANT_Methodref = 10,
CONSTANT_InterfaceMethodref = 11,
CONSTANT_String = 8,
CONSTANT_Integer = 3,
CONSTANT_Float = 4,
CONSTANT_Long = 5,
CONSTANT_Double = 6,
CONSTANT_NameAndType = 12,
CONSTANT_Utf8 = 1,
CONSTANT_Unicode = 2
}
export enum CONSTANT_Class_info {
tag = 0, // u1
name_index = 1 // u2 CONSTANT_Utf8_info
}
export class ConstantPool {
bytes: Bytes;
offset: number;
count: number;
entries: Uint32Array;
resolved: any [];
private static tagSize = new Int8Array([
-1, // ?
-1, // CONSTANT_Utf8 has a variable length and needs to be handled differently.
-1, // TODO: CONSTANT_Unicode
4, // CONSTANT_Integer
4, // CONSTANT_Float
8, // CONSTANT_Long
8, // CONSTANT_Double
2, // CONSTANT_Class
2, // CONSTANT_String
4, // CONSTANT_Fieldref,
4, // CONSTANT_Methodref
4, // CONSTANT_InterfaceMethodref
4 // CONSTANT_NameAndType
]);
constructor(bytes: Bytes) {
this.bytes = bytes.clone();
this.offset = bytes.offset;
this.scanEntries();
}
/**
* Quickly scan over the constant pool and record the position of each constant pool entry.
*/
private scanEntries() {
var s = this.bytes;
var c = this.count = s.readU2();
this.entries = new Uint32Array(this.count);
this.resolved = new Array(this.count);
var S = ConstantPool.tagSize;
var o = s.offset;
var u1 = s.u1;
var e = this.entries;
for (var i = 1; i < c; i++) {
e[i] = o;
var t = u1[o++];
if (t === TAGS.CONSTANT_Utf8) {
o += 2 + Bytes.readU16(u1, o);
} else {
o += S[t];
}
if (t === TAGS.CONSTANT_Long || t === TAGS.CONSTANT_Double) {
i++;
}
}
s.offset = o;
}
utf8(i: number): Uint8Array {
return <Uint8Array>this.resolve(i, TAGS.CONSTANT_Utf8);
}
u2(i: number, tag: TAGS, offset: number) {
var s = this.bytes.seek(this.entries[i]);
release || assert (s.peekU1() === tag);
return s.skip(offset).readU2();
}
seek(i: number, tag: TAGS) {
this.bytes.seek(this.entries[i]);
release || assert(this.bytes.peekU1() === tag);
}
resolve(i: number, tag: TAGS): any {
var b = this.bytes, r = this.resolved[i];
if (r === undefined) {
this.seek(i, tag);
switch (b.readU1()) {
case TAGS.CONSTANT_Utf8:
r = this.resolved[i] = b.readBytes(b.readU2());
break;
case TAGS.CONSTANT_Class:
r = this.resolved[i] = CLASSES.loadClass(utf8ToString(this.resolve(b.readU2(), TAGS.CONSTANT_Utf8)));
break;
default:
assert(false);
break;
}
}
return r;
}
resolveClass(index: number): ClassInfo {
return <ClassInfo>this.resolve(index, TAGS.CONSTANT_Class);
}
}
export class FieldInfo {
offset: number;
classInfo: ClassInfo;
kind: Kind;
name: string;
mangledName: string;
signature: string;
access_flags: ACCESS_FLAGS;
constructor(classInfo: ClassInfo, offset: number) {
this.offset = offset;
this.classInfo = classInfo;
}
get isStatic(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_STATIC);
}
get(object: java.lang.Object) {
return object[this.mangledName];
}
set(object: java.lang.Object, value: any) {
object[this.mangledName] = value
}
getStatic() {
return this.get(this.classInfo.getStaticObject($.ctx));
}
setStatic(value: any) {
return this.set(this.classInfo.getStaticObject($.ctx), value);
}
}
export class SourceLocation {
constructor(public className: string,
public sourceFile: string,
public lineNumber: number) {
// ...
}
toString() {
return this.sourceFile + ":" + this.lineNumber;
}
equals(other: SourceLocation): boolean {
if (!other) {
return false;
}
return this.sourceFile === other.sourceFile &&
this.lineNumber === other.lineNumber;
}
}
export class MethodInfo {
access_flags: number;
name_index: number;
descriptor_index: number;
fn: any;
classInfo: ClassInfo;
offset: number;
code: Uint8Array;
codeAttribute: CodeAttribute;
name: string;
state: MethodState;
signature: string;
mangledName: string;
mangledClassAndMethodName: string;
onStackReplacementEntryPoints: number [];
callCount: number;
bytecodeCount: number;
backwardsBranchCount: number;
interpreterCallCount: number;
argumentSlots: number;
/**
* The number of arguments to pop of the stack when calling this function.
*/
consumeArgumentSlots: number;
hasTwoSlotArguments: boolean;
// Remove these
max_locals: number;
max_stack: number;
exception_table: any [];
implKey: string;
isOptimized: boolean;
signatureDescriptor: SignatureDescriptor;
constructor(classInfo: ClassInfo, offset: number) {
this.classInfo = classInfo;
this.offset = offset;
var b = classInfo.bytes.seek(offset);
this.access_flags = b.readU2();
this.name_index = b.readU2();
this.descriptor_index = b.readU2();
this.scanMethodInfoAttributes(b);
}
scanMethodInfoAttributes(s: Bytes) {
var count = s.readU2();
for (var i = 0; i < count; i++) {
var attribute_name_index = s.readU2();
var attribute_length = s.readU4();
var o = s.offset;
var attribute_name = this.classInfo.constantPool.utf8(attribute_name_index);
if (strcmp(attribute_name, UTF8.Code)) {
this.codeAttribute = new CodeAttribute(this.classInfo, o);
}
s.seek(o + attribute_length);
}
}
public getReturnKind(): Kind {
return this.signatureDescriptor.typeDescriptors[0].kind;
}
get isNative(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_NATIVE);
}
get isFinal(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_FINAL);
}
get isPublic(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_PUBLIC);
}
get isStatic(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_STATIC);
}
get isSynchronized(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_SYNCHRONIZED);
}
get isAbstract(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_ABSTRACT);
}
getSourceLocationForPC(pc: number): SourceLocation {
return null;
}
}
enum ResolvedFlags {
None = 0,
Fields = 1,
Methods = 2,
Classes = 4,
Interfaces = 8
}
export class CodeAttribute {
max_stack: number;
max_locals: number;
code: Uint8Array;
constructor(classInfo: ClassInfo, offset: number) {
var s = classInfo.bytes;
this.max_stack = s.readU2();
this.max_locals = s.readU2();
var code_length = s.readU4();
this.code = s.readBytes(code_length);
var exception_table_length = s.readU2();
}
}
export class ClassInfo {
bytes: Bytes = null;
constantPool: ConstantPool = null;
access_flags: number = 0;
this_class: number = 0;
super_class: number= 0;
name: string = null;
superClassName: string = null;
superClass: ClassInfo = null;
subClasses: ClassInfo [] = null;
allSubClasses: ClassInfo [] = null;
staticInitializer: MethodInfo = null;
klass: Klass = null;
private resolvedFlags: ResolvedFlags = ResolvedFlags.None;
private fields: (number | FieldInfo) [] = null;
private methods: (number | MethodInfo) [] = null;
private classes: (number | ClassInfo) [] = null;
private interfaces: (number | ClassInfo) [] = null;
sourceFile: string = null;
mangledName: string = null;
private _className: Uint16Array = null;
// Delete me:
constant_pool: any = null;
thread: any = null;
constructor(buffer: Uint8Array) {
if (!buffer) {
return;
}
var b = this.bytes = new Bytes(buffer, 0);
b.readU4(); // magic
b.readU2(); // minor_version
b.readU2(); // major_version
this.constantPool = new ConstantPool(b);
b.seek(this.constantPool.bytes.offset);
this.access_flags = b.readU2();
this.this_class = b.readU2();
this.super_class = b.readU2();
this.scanInterfaces();
this.scanFields();
this.scanMethods();
this.scanClassInfoAttributes();
this.mangledName = mangleClass(this);
this.getMethods();
}
private scanInterfaces() {
var b = this.bytes;
var interfaces_count = b.readU2();
this.interfaces = new Array(interfaces_count);
for (var i = 0; i < interfaces_count; i++) {
this.interfaces[i] = b.readU2();
}
}
private scanFields() {
var b = this.bytes;
var fields_count = b.readU2();
var f = this.fields = new Array(fields_count);
for (var i = 0; i < fields_count; i++) {
f[i] = b.offset;
b.skip(6);
this.skipAttributes();
}
}
private scanMethods() {
var b = this.bytes;
var methods_count = b.readU2();
var m = this.methods = new Array(methods_count);
for (var i = 0; i < methods_count; i++) {
m[i] = b.offset;
b.skip(6);
this.skipAttributes();
}
}
private skipAttributes() {
var b = this.bytes;
var attributes_count = b.readU2();
for (var i = 0; i < attributes_count; i++) {
b.readU2();
b.skip(b.readU4());
}
}
scanClassInfoAttributes() {
var b = this.bytes;
var attributes_count = b.readU2();
for (var i = 0; i < attributes_count; i++) {
var attribute_name_index = b.readU2();
var attribute_length = b.readU4();
var o = b.offset;
var attribute_name = this.constantPool.utf8(attribute_name_index);
if (strcmp(attribute_name, UTF8.InnerClasses)) {
var number_of_classes = b.readU2();
assert (!this.classes);
this.classes = new Array(number_of_classes);
for(var i = 0; i < number_of_classes; i++) {
this.classes[i] = b.offset;
b.skip(8);
}
}
b.seek(o + attribute_length);
}
}
getMethod(i: number): MethodInfo {
if (typeof this.methods[i] === "number") {
this.methods[i] = new MethodInfo(this, <number>this.methods[i]);
}
return <MethodInfo>this.methods[i];
}
getMethods(): MethodInfo [] {
if (!this.methods) {
return null;
}
if (this.resolvedFlags & ResolvedFlags.Methods) {
return <MethodInfo []>this.methods;
}
for (var i = 0; i < this.methods.length; i++) {
this.getMethod(i);
}
this.resolvedFlags |= ResolvedFlags.Methods;
return <MethodInfo []>this.methods;
}
getField(i: number): FieldInfo {
if (typeof this.fields[i] === "number") {
this.fields[i] = new FieldInfo(this, <number>this.fields[i]);
}
return <FieldInfo>this.fields[i];
}
getFields(): FieldInfo [] {
if (!this.fields) {
return null;
}
if (this.resolvedFlags & ResolvedFlags.Fields) {
return <FieldInfo []>this.fields;
}
for (var i = 0; i < this.fields.length; i++) {
this.getField(i);
}
this.resolvedFlags |= ResolvedFlags.Fields;
return <FieldInfo []>this.fields;
}
getClass(i: number): ClassInfo {
if (typeof this.classes[i] === "number") {
this.classes[i] = new ClassInfo(null);
}
return <ClassInfo>this.classes[i];
}
getClasses(): ClassInfo [] {
if (!this.classes) {
return null;
}
if (this.resolvedFlags & ResolvedFlags.Classes) {
return <ClassInfo []>this.classes;
}
for (var i = 0; i < this.classes.length; i++) {
this.getClass(i);
}
this.resolvedFlags |= ResolvedFlags.Classes;
return <ClassInfo []>this.classes;
}
getInterface(i: number): ClassInfo {
if (typeof this.interfaces[i] === "number") {
this.interfaces[i] = this.constantPool.resolveClass(<number>this.interfaces[i]);
}
return <ClassInfo>this.interfaces[i];
}
getInterfaces(): ClassInfo [] {
if (!this.interfaces) {
return null;
}
if (this.resolvedFlags & ResolvedFlags.Interfaces) {
return <ClassInfo []>this.interfaces;
}
for (var i = 0; i < this.interfaces.length; i++) {
this.getInterface(i);
}
this.resolvedFlags |= ResolvedFlags.Interfaces;
return <ClassInfo []>this.interfaces;
}
getFieldByName(fieldKey: string): FieldInfo {
// return CLASSES.getField(this, fieldKey);
return null;
}
/**
* Object that holds static properties for this class.
*/
getStaticObject(ctx: Context): java.lang.Object {
return <java.lang.Object><any>getRuntimeKlass(ctx.runtime, this.klass);
}
getClassNameIndex(): number {
return this.constantPool.u2(this.this_class, TAGS.CONSTANT_Class, CONSTANT_Class_info.name_index);
}
getClassName(): Uint8Array {
return this.constantPool.utf8(this.getClassNameIndex());
}
getClassNameString(): string {
return util.decodeUtf8Array(this.getClassName());
}
// TODO: Remove
get className(): string {
return this.getClassNameString();
}
get isInterface(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_INTERFACE);
}
get isFinal(): boolean {
return !!(this.access_flags & ACCESS_FLAGS.ACC_FINAL);
}
implementsInterface(i: ClassInfo): boolean {
var classInfo = this;
do {
var interfaces = classInfo.interfaces;
for (var n = 0; n < interfaces.length; ++n) {
if (interfaces[n] === i)
return true;
}
classInfo = classInfo.superClass;
} while (classInfo);
return false;
}
isAssignableTo(toClass: ClassInfo): boolean {
if (this === toClass || toClass === CLASSES.java_lang_Object)
return true;
if (toClass.isInterface && this.implementsInterface(toClass))
return true;
return this.superClass ? this.superClass.isAssignableTo(toClass) : false;
}
/**
* java.lang.Class object for this class info. This is a not where static properties
* are stored for this class.
*/
getClassObject(): java.lang.Class {
return getRuntimeKlass($, this.klass).classObject;
}
resolveClass(i: number): ClassInfo {
return null;
}
resolveMethod(i: number, isStatic: boolean): MethodInfo {
return null;
}
resolveField(i: number): FieldInfo {
return null;
}
/**
* Resolves a constant pool reference.
*/
resolve(index: number, isStatic: boolean) {
// return this.constantPool.resolve(index);
//var rp = this.resolved_constant_pool;
//var constant: any = rp[index];
//if (constant !== undefined) {
// return constant;
//}
//var cp = this.constant_pool;
//var entry = this.constant_pool[index];
//switch (entry.tag) {
// case TAGS.CONSTANT_Integer:
// constant = entry.integer;
// break;
// case TAGS.CONSTANT_Float:
// constant = entry.float;
// break;
// case TAGS.CONSTANT_String:
// constant = $.newStringConstant(cp[entry.string_index].bytes);
// break;
// case TAGS.CONSTANT_Long:
// constant = Long.fromBits(entry.lowBits, entry.highBits);
// break;
// case TAGS.CONSTANT_Double:
// constant = entry.double;
// break;
// case TAGS.CONSTANT_Class:
// constant = CLASSES.getClass(cp[entry.name_index].bytes);
// break;
// case TAGS.CONSTANT_Fieldref:
// var classInfo = this.resolve(entry.class_index, isStatic);
// var fieldName = cp[cp[entry.name_and_type_index].name_index].bytes;
// var signature = cp[cp[entry.name_and_type_index].signature_index].bytes;
// constant = CLASSES.getField(classInfo, (isStatic ? "S" : "I") + "." + fieldName + "." + signature);
// if (!constant) {
// throw $.newRuntimeException(
// classInfo.className + "." + fieldName + "." + signature + " not found");
// }
// break;
// case TAGS.CONSTANT_Methodref:
// case TAGS.CONSTANT_InterfaceMethodref:
// var classInfo = this.resolve(entry.class_index, isStatic);
// var methodName = cp[cp[entry.name_and_type_index].name_index].bytes;
// var signature = cp[cp[entry.name_and_type_index].signature_index].bytes;
// constant = CLASSES.getMethod(classInfo, (isStatic ? "S" : "I") + "." + methodName + "." + signature);
// if (!constant) {
// constant = CLASSES.getMethod(classInfo, (isStatic ? "S" : "I") + "." + methodName + "." + signature);
// throw $.newRuntimeException(
// classInfo.className + "." + methodName + "." + signature + " not found");
// }
// break;
// default:
// throw new Error("not support constant type");
//}
//return rp[index] = constant;
}
}
export class PrimitiveClassInfo extends ClassInfo {
private primitiveClassName: string;
constructor(className, mangledName) {
super(null);
this.primitiveClassName = className;
this.mangledName = mangledName;
}
getClassNameString(): string {
return this.primitiveClassName;
}
static Z = new PrimitiveClassInfo("Z", "boolean");
static C = new PrimitiveClassInfo("C", "char");
static F = new PrimitiveClassInfo("F", "float");
static D = new PrimitiveClassInfo("D", "double");
static B = new PrimitiveClassInfo("B", "byte");
static S = new PrimitiveClassInfo("S", "short");
static I = new PrimitiveClassInfo("I", "int");
static J = new PrimitiveClassInfo("J", "long");
}
export class ArrayClassInfo extends ClassInfo {
elementClass: ClassInfo;
constructor(elementClass: ClassInfo) {
super(null);
this.elementClass = elementClass;
}
getClassNameString(): string {
return "[" + this.elementClass.getClassNameString();
}
isAssignableTo(toClass: ClassInfo): boolean {
if (this === toClass || toClass === CLASSES.java_lang_Object)
return true;
if (toClass.isInterface && this.implementsInterface(toClass))
return true;
if (toClass instanceof ArrayClassInfo) {
if (this.elementClass && toClass.elementClass)
return this.elementClass.isAssignableTo(toClass.elementClass);
} else {
return false;
}
return this.superClass ? this.superClass.isAssignableTo(toClass) : false;
}
}
export class PrimitiveArrayClassInfo extends ArrayClassInfo {
constructor(elementClass: ClassInfo, mangledName: string) {
super(elementClass);
this.mangledName = mangledName;
}
static Z = new PrimitiveArrayClassInfo(PrimitiveClassInfo.Z, "Uint8Array");
static C = new PrimitiveArrayClassInfo(PrimitiveClassInfo.C, "Uint16Array");
static F = new PrimitiveArrayClassInfo(PrimitiveClassInfo.F, "Float32Array");
static D = new PrimitiveArrayClassInfo(PrimitiveClassInfo.D, "Float64Array");
static B = new PrimitiveArrayClassInfo(PrimitiveClassInfo.B, "Int8Array");
static S = new PrimitiveArrayClassInfo(PrimitiveClassInfo.S, "Int16Array");
static I = new PrimitiveArrayClassInfo(PrimitiveClassInfo.I, "Int32Array");
static J = new PrimitiveArrayClassInfo(PrimitiveClassInfo.J, "Int64Array");
}
}