[PATCH] full bytecode verification
sluncho at mirizma.org
sluncho at mirizma.org
Thu Aug 2 17:10:00 PDT 2001
The current Kaffe bytecode verification code can verify only the
basic Java types. It does not properly handle reference types and
arrays of references. The lack of full bytecode verification
allows an attacker to circumvent all Java security.
Attached is a patch that adds full bytecode verification to Kaffe.
The patch applies cleanly to the CVS code from Aug 03, 2001 and
can be easily applied to Kaffe 1.0.6 manualy (one hunk fails
because of whitespace)
It was written by Dr. Joel Jones <jjones at uiuc.edu> for Kaffe 0.9.2
and ported to the 1.0.6 and the latest CVS version by me.
The patch has been tested on Linux/i386 and Solaris/Sparc.
We would appreciate any feedback and additional testing results.
Alexander Sotirov
sluncho at mirizma.org
-------------- next part --------------
diff -ru kaffe-cvs.orig/ChangeLog kaffe-cvs/ChangeLog
--- kaffe-cvs.orig/ChangeLog Wed Jun 20 17:59:30 2001
+++ kaffe-cvs/ChangeLog Thu Aug 2 18:15:34 2001
@@ -1,3 +1,12 @@
+2001-08-03 Alexander Sotirov <sluncho at mirizma.org>
+
+ * kaffe/kaffevm/itypes.h, kaffe/kaffevm/itypes.c: Array types
+
+ * kaffe/kaffevm/baseClasses.c: Initialize array types
+
+ * kaffe/kaffevm/code-analyse.h, kaffe/kaffevm/code-analyse.c:
+ Full bytecode verifier, originally written by Joel Jones <jjones at uiuc.edu>
+
2001-06-20 Edouard G. Parmelan <egp at free.fr>
* libraries/javalib/kjc.jar: Update from kjc-suite-1.5A-bin.jar.
diff -ru kaffe-cvs.orig/kaffe/kaffevm/baseClasses.c kaffe-cvs/kaffe/kaffevm/baseClasses.c
--- kaffe-cvs.orig/kaffe/kaffevm/baseClasses.c Sat Apr 21 14:03:48 2001
+++ kaffe-cvs/kaffe/kaffevm/baseClasses.c Thu Aug 2 18:02:40 2001
@@ -264,6 +264,9 @@
finishTypes();
processClass(StringClass, CSTATE_COMPLETE, &einfo);
+ /* Initialize array types supported by the instruction set */
+ initArrayClasses(&einfo);
+
/*
* To make sure we have some ground to stand on, we doublecheck
* that we really got Kaffe's java.lang.Cloneable class here.
diff -ru kaffe-cvs.orig/kaffe/kaffevm/code-analyse.c kaffe-cvs/kaffe/kaffevm/code-analyse.c
--- kaffe-cvs.orig/kaffe/kaffevm/code-analyse.c Fri Jun 1 14:30:05 2001
+++ kaffe-cvs/kaffe/kaffevm/code-analyse.c Thu Aug 2 18:06:37 2001
@@ -5,7 +5,10 @@
* Transvirtual Technologies, Inc. All rights reserved.
*
* See the file "license.terms" for information on usage and redistribution
- * of this file.
+ * of this file.
+ *
+ * Full bytecode verifier by Joel Jones <jjones at uiuc.edu>, ported by
+ * Alexander Sotirov <sluncho at mirizma.org>
*/
#define IDBG(s)
@@ -21,6 +24,7 @@
#include "access.h"
#include "classMethod.h"
#include "code-analyse.h"
+#include "baseClasses.h"
#include "lookup.h"
#include "exception.h"
#include "icode.h"
@@ -32,6 +36,8 @@
#include "soft.h"
#include "md.h"
#include "gc.h"
+#include "hashtab.h"
+#include "stringSupport.h"
const uint8 insnLen[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -56,6 +62,66 @@
static bool verifyBasicBlock(codeinfo*, Method*, int32, errorInfo*);
static void updateLocals(codeinfo*, int32, frameElement*);
static bool verifyCatchClause(jexceptionEntry*, Hjava_lang_Class*, errorInfo*);
+static Hjava_lang_Class* getRefReturnClass(Method* meth, errorInfo *einfo);
+static Hjava_lang_Class* nca(Hjava_lang_Class* u, Hjava_lang_Class* v);
+int issubclass(Hjava_lang_Class* super, Hjava_lang_Class* sub);
+int issubinterface(Hjava_lang_Class* super, Hjava_lang_Class* sub);
+int nca_hashfunc(const void *ptr1);
+int nca_compfunc(const void *ptr1, const void *ptr2);
+
+/* Data-flow analyzer part of bytecode verifier from the JVM Specification,
+ * section 4.9.2. */
+/* 0. Data-flow analyzer is initialized.
+ * A. For the first instruction of the method, the local variables
+ * which represent parameters contain values of the types indicated
+ * by the method's type descriptor;
+ * B. the operand stack is empty.
+ * C. All other local variables contain an illegal value
+ * D. For the other instructions, which have not been examined yet, no
+ * information is available regarding the operand stack or local
+ * variables
+ * E. Initially, the "changed" bit is only set for the first instruction.
+ * 1. Select a virtual machine instruction whose "changed" bit is set.
+ * A. If no instruction remains whose "changed" bit is set, the method has
+ * successfully been verified.
+ * B. Otherwise, turn off the "changed" bit of the selected instruction.
+ * 2. Model the effect of the instruction on the operand stack and local
+ * variables
+ * A. If the instruction uses values from the operand stack, ensure that
+ * there are a sufficient number of values on the stack and that the
+ * top values on the stack are of an appropriate type. Otherwise,
+ * verification fails.
+ * B. If the instruction uses a local variable, ensure that the specified
+ * local variable contains a value of the appropriate type. Otherwise,
+ * verification fails.
+ * C. If the instruction pushes values onto the operand stack, ensure that
+ * there is a sufficient room on the operand stack for the new values.
+ * Add the indicated types to the top of the modeled operand stack.
+ * D. If the instruction modifies a local variable, record that the local
+ * variable now contains the new type.
+ * 3. Determine the instructions that can follow the current instruction.
+ * Successor instruction can be one of the following:
+ * A. The next instruction, if the current instruction is not
+ * an unconditional control transfer instruction (for instance
+ * goto, return, or athrow). Verification fails if it is possible to
+ * "fall off" the last instruction of the method.
+ * B. The target(s) of a conditional or unconditional branch or switch
+ * C. Any exception handler for this instruction
+ * 4. Merge the state of the operand stack and local variables at the end of
+ * the execution of the current instruction into each of the successor
+ * instructions. In the special case of control transfer to an exception
+ * handler, the operand stack is set to contain a single object of the
+ * exception type indicated by the exception handler information.
+ * A. If this is the first time the successor instruction has been visited,
+ * record that the operand stack and local variables values calculated
+ * in steps 2 and 3 are the state of the operand stack and local
+ * variables prior to executing the successor instruction. Set the
+ * "changed" bit for the successor instruction.
+ * B. If the successor instruction has been seen before, merge the operand
+ * stack and local variable values calculated in steps 2 and 3 into the
+ * values already there. Set the "changed" bit if there is any
+ * modification to the values.
+ * 5. Continue at step 1. */
bool
verifyMethod(Method* meth, codeinfo **pcodeinfo, errorInfo *einfo)
@@ -74,7 +140,10 @@
bool wide;
codeinfo *codeInfo;
localUse* localuse;
-
+ Hjava_lang_Class* class;
+ char buf[200];
+ const char* strp;
+
DBG(CODEANALYSE,
dprintf(__FUNCTION__ " %p: %s.%s\n", THREAD_NATIVE(),
meth->class->name->data, meth->name->data);
@@ -254,6 +323,7 @@
for (lcl = 0; lcl < meth->exception_table->length; lcl++) {
bool succ;
jexceptionEntry *entry;
+ Hjava_lang_Class** expClass;
entry = &(meth->exception_table->entry[lcl]);
@@ -268,7 +338,16 @@
SET_STARTOFEXCEPTION(pc);
SET_STACKPOINTER(pc, sp);
SET_NEWFRAME(pc);
- STACKINIT(0, TOBJ);
+ /* set stack to contain only the declared Throwable */
+ expClass = &meth->exception_table->entry[lcl].catch_type;
+ if (*expClass == 0) {
+ if (meth->exception_table->entry[lcl].catch_idx == 0) {
+ *expClass = javaLangThrowable;
+ } else {
+ *expClass = getClass(meth->exception_table->entry[lcl].catch_idx, meth->class, einfo);
+ }
+ }
+ STACKINIT(0, *expClass);
}
}
@@ -284,7 +363,7 @@
*/
idx = 0;
if ((meth->accflags & ACC_STATIC) == 0) {
- LOCALINIT(0, TOBJ);
+ LOCALINIT(0, meth->class);
idx++;
}
@@ -292,7 +371,13 @@
switch (*METHOD_ARG_TYPE(meth, count)) {
case 'L':
case '[':
- LOCALINIT(idx, TOBJ);
+ strp = METHOD_ARG_TYPE(meth, count);
+ strp = getNextArg(strp, buf);
+ class = getClassFromSignature(buf, meth->class->loader, einfo);
+ if (class == 0) {
+ return false;
+ }
+ LOCALINIT(idx, class);
idx += 1;
break;
case 'I':
@@ -371,6 +456,10 @@
bool wide;
bool failed;
bool firsttime;
+ Hjava_lang_Class* class;
+ char buf[200];
+ const char* strp;
+ Utf8Const* name;
opc = pc;
assert(pc == 0 || IS_STARTOFBASICBLOCK(pc) || IS_STARTOFEXCEPTION(pc));
@@ -434,7 +523,7 @@
case ACONST_NULL:
STKPUSH(1);
- STACKOUT(0, TOBJ);
+ STACKOUT(0, TNULL);
INCPC(1);
break;
@@ -638,10 +727,31 @@
break;
case IALOAD:
+ STACKIN(1, TINTARR);
+ STACKIN(0, TINT);
+ STKPOP(1);
+ STACKOUT(0, TINT);
+ INCPC(1);
+ break;
+
case BALOAD:
+ STACKIN(1, TBYTEARR);
+ STACKIN(0, TINT);
+ STKPOP(1);
+ STACKOUT(0, TINT);
+ INCPC(1);
+ break;
+
case CALOAD:
+ STACKIN(1, TCHARARR);
+ STACKIN(0, TINT);
+ STKPOP(1);
+ STACKOUT(0, TINT);
+ INCPC(1);
+ break;
+
case SALOAD:
- STACKIN(1, TOBJ);
+ STACKIN(1, TSHORTARR);
STACKIN(0, TINT);
STKPOP(1);
STACKOUT(0, TINT);
@@ -649,7 +759,7 @@
break;
case LALOAD:
- STACKIN(1, TOBJ);
+ STACKIN(1, TLONGARR);
STACKIN(0, TINT);
STACKOUT(0, TLONG);
STACKOUT(1, TVOID);
@@ -657,7 +767,7 @@
break;
case FALOAD:
- STACKIN(1, TOBJ);
+ STACKIN(1, TFLOATARR);
STACKIN(0, TINT);
STKPOP(1);
STACKOUT(0, TFLOAT);
@@ -665,7 +775,7 @@
break;
case DALOAD:
- STACKIN(1, TOBJ);
+ STACKIN(1, TDOUBLEARR);
STACKIN(0, TINT);
STACKOUT(0, TDOUBLE);
STACKOUT(1, TVOID);
@@ -673,10 +783,10 @@
break;
case AALOAD:
- STACKIN(1, TOBJ);
+ STACKIN_SUBCLASS(1, TOBJARR);
STACKIN(0, TINT);
STKPOP(1);
- STACKOUT(0, TOBJ);
+ STACKOUT(0, CLASS_ELEMENT_TYPE(activeFrame[sp].type));
INCPC(1);
break;
@@ -684,6 +794,7 @@
case ISTORE_1:
case ISTORE_2:
case ISTORE_3:
+ STACKIN(0, TINT);
lcl = INSN(pc) - ISTORE_0;
LOCALOUT_STACK(lcl, TINT, 0);
STKPOP(1);
@@ -691,6 +802,7 @@
break;
case ISTORE:
+ STACKIN(0, TINT);
if (wide) {
LOCALOUT_STACK(WORD(pc+1), TINT, 0);
INCPC(1);
@@ -707,6 +819,8 @@
case LSTORE_1:
case LSTORE_2:
case LSTORE_3:
+ STACKIN(0, TLONG);
+ STACKIN(1, TVOID);
lcl = INSN(pc) - LSTORE_0;
LOCALOUT_STACK(lcl, TLONG, 0);
LOCALOUT_STACK(lcl+1, TVOID, 1);
@@ -715,6 +829,8 @@
break;
case LSTORE:
+ STACKIN(0, TLONG);
+ STACKIN(1, TVOID);
if (wide) {
lcl = WORD(pc+1);
LOCALOUT_STACK(lcl, TLONG, 0);
@@ -790,20 +906,22 @@
case ASTORE_1:
case ASTORE_2:
case ASTORE_3:
+ STACKIN_ISREF(0);
lcl = INSN(pc) - ASTORE_0;
- LOCALOUT_STACK(lcl, TOBJ, 0);
+ LOCALOUT_STACK(lcl, TOBJ, 0); /* TOBJ isn't used */
STKPOP(1);
INCPC(1);
break;
case ASTORE:
+ STACKIN_ISREF(0);
if (wide) {
- LOCALOUT_STACK(WORD(pc+1), TOBJ, 0);
+ LOCALOUT_STACK(WORD(pc+1), TOBJ, 0); /* TOBJ isn't used */
INCPC(1);
wide = false;
}
else {
- LOCALOUT_STACK(BYTE(pc+1), TOBJ, 0);
+ LOCALOUT_STACK(BYTE(pc+1), TOBJ, 0); /* TOBJ isn't used */
}
STKPOP(1);
INCPC(2);
@@ -812,7 +930,7 @@
case BASTORE:
STACKIN(0, TINT);
STACKIN(1, TINT);
- STACKIN(2, TOBJ);
+ STACKIN(2, TBYTEARR);
STKPOP(3);
INCPC(1);
break;
@@ -820,22 +938,29 @@
case IASTORE:
STACKIN(0, TINT);
STACKIN(1, TINT);
- STACKIN(2, TOBJ);
+ STACKIN(2, TINTARR);
STKPOP(3);
INCPC(1);
break;
case CASTORE:
+ STACKIN(0, TINT);
+ STACKIN(1, TINT);
+ STACKIN(2, TCHARARR);
+ STKPOP(3);
+ INCPC(1);
+ break;
+
case SASTORE:
STACKIN(0, TINT);
STACKIN(1, TINT);
- STACKIN(2, TOBJ);
+ STACKIN(2, TSHORTARR);
STKPOP(3);
INCPC(1);
break;
case LASTORE:
- STACKIN(3, TOBJ);
+ STACKIN(3, TLONGARR);
STACKIN(2, TINT);
STACKIN(0, TLONG);
STACKIN(1, TVOID);
@@ -844,7 +969,7 @@
break;
case FASTORE:
- STACKIN(2, TOBJ);
+ STACKIN(2, TFLOATARR);
STACKIN(1, TINT);
STACKIN(0, TFLOAT);
STKPOP(3);
@@ -852,7 +977,7 @@
break;
case DASTORE:
- STACKIN(3, TOBJ);
+ STACKIN(3, TDOUBLEARR);
STACKIN(2, TINT);
STACKIN(0, TDOUBLE);
STACKIN(1, TVOID);
@@ -861,9 +986,9 @@
break;
case AASTORE:
- STACKIN(2, TOBJ);
+ STACKIN_SUBCLASS(2, TOBJARR);
STACKIN(1, TINT);
- STACKIN(0, TOBJ);
+ STACKIN_SUBCLASS(0, CLASS_ELEMENT_TYPE(activeFrame[sp+2].type));
STKPOP(3);
INCPC(1);
break;
@@ -1280,8 +1405,8 @@
case IF_ACMPEQ:
case IF_ACMPNE:
- STACKIN(0, TOBJ);
- STACKIN(1, TOBJ);
+ STACKIN_ISREF(0);
+ STACKIN_ISREF(1);
STKPOP(2);
FRAMEMERGE(pc + WORD(pc+1), sp);
FRAMEMERGE(pc + 3, sp);
@@ -1372,7 +1497,12 @@
break;
case ARETURN:
- STACKIN(0, TOBJ);
+ class = getRefReturnClass(meth, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKIN_SUBCLASS(0, class);
STKPOP(1);
INCPC(1);
break;
@@ -1388,7 +1518,11 @@
}
if (!FIELD_ISPRIM(finfo.field)) {
STKPUSH(1);
- STACKOUT(0, TOBJ);
+ if ((class = resolveFieldType(finfo.field, finfo.class, einfo)) == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
}
else switch (CLASS_PRIM_SIG(FIELD_TYPE(finfo.field))){
case 'I':
@@ -1426,7 +1560,11 @@
goto done;
}
if (!FIELD_ISPRIM(finfo.field)) {
- STACKIN(0, TOBJ);
+ if ((class = resolveFieldType(finfo.field, finfo.class, einfo)) == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKIN_SUBCLASS(0, class);
STKPOP(1);
}
else switch (CLASS_PRIM_SIG(FIELD_TYPE(finfo.field))){
@@ -1464,9 +1602,13 @@
failed = true;
goto done;
}
- STACKIN(0, TOBJ);
+ STACKIN_SUBCLASS(0, finfo.class);
if (!FIELD_ISPRIM(finfo.field)) {
- STACKOUT(0, TOBJ);
+ if ((class = resolveFieldType(finfo.field, finfo.class, einfo)) == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
}
else switch (CLASS_PRIM_SIG(FIELD_TYPE(finfo.field))){
case 'I':
@@ -1502,8 +1644,12 @@
goto done;
}
if (!FIELD_ISPRIM(finfo.field)) {
- STACKIN(0, TOBJ);
- STACKIN(1, TOBJ);
+ if ((class = resolveFieldType(finfo.field, finfo.class, einfo)) == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKIN_SUBCLASS(0, class);
+ STACKIN_SUBCLASS(1, finfo.class);
STKPOP(2);
}
else switch (CLASS_PRIM_SIG(FIELD_TYPE(finfo.field))){
@@ -1513,24 +1659,24 @@
case 'B':
case 'C':
STACKIN(0, TINT);
- STACKIN(1, TOBJ);
+ STACKIN_SUBCLASS(1, finfo.class);
STKPOP(2);
break;
case 'F':
STACKIN(0, TFLOAT);
- STACKIN(1, TOBJ);
+ STACKIN_SUBCLASS(1, finfo.class);
STKPOP(2);
break;
case 'J':
STACKIN(0, TLONG);
STACKIN(1, TVOID);
- STACKIN(2, TOBJ);
+ STACKIN_SUBCLASS(2, finfo.class);
STKPOP(3);
break;
case 'D':
STACKIN(0, TDOUBLE);
STACKIN(1, TVOID);
- STACKIN(2, TOBJ);
+ STACKIN_SUBCLASS(2, finfo.class);
STKPOP(3);
break;
default:
@@ -1549,37 +1695,30 @@
}
}
+ class = call.class;
+
sig = call.signature->data;
assert(sig[0] == '(');
sig++;
idx = call.in;
- STACKIN(idx, TOBJ);
+ STACKIN_SUBCLASS(idx, class);
idx -= 1;
- while (sig[0] != ')') {
- switch (sig[0]) {
+ sig = getNextArg(sig, buf); /* initialize buf */
+ for ( ; *buf != ')'; sig = getNextArg(sig, buf)) {
+ switch (*buf) {
case '[':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] == '[') {
- sig++;
- }
- if (sig[0] == 'L') {
- while (sig[0] != ';') {
- sig++;
- }
- }
- sig++;
- break;
case 'L':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] != ';') {
- sig++;
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
}
- sig++;
+ STACKIN_SUBCLASS(idx, class);
+ idx -= 1;
break;
case 'I':
case 'Z':
@@ -1588,24 +1727,20 @@
case 'C':
STACKIN(idx, TINT);
idx -= 1;
- sig++;
break;
case 'J':
STACKIN(idx-1, TLONG);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
case 'F':
STACKIN(idx, TFLOAT);
idx -= 1;
- sig++;
break;
case 'D':
STACKIN(idx-1, TDOUBLE);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
default:
assert("Signature character unknown" == 0);
@@ -1614,10 +1749,18 @@
STKPOP(call.in+1);
STKPUSH(call.out);
- switch (call.rettype) {
+ assert(*buf == ')');
+ sig = getNextArg(sig, buf);
+ switch (*buf) {
case '[':
case 'L':
- STACKOUT(0, TOBJ);
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
break;
case 'I':
case 'Z':
@@ -1638,7 +1781,9 @@
STACKOUT(1, TVOID);
break;
case 'V':
+ break;
default:
+ assert("Signature character unknown" == 0);
break;
}
INCPC(3);
@@ -1657,31 +1802,24 @@
sig++;
idx = call.in;
- STACKIN(idx, TOBJ);
+ assert(idx == BYTE(pc+3) - 1);
+ assert(0 == BYTE(pc+4));
+ STACKIN_ISREF(idx);
idx -= 1;
- while (sig[0] != ')') {
- switch (sig[0]) {
+ sig = getNextArg(sig, buf); /* initialize buf */
+ for ( ; *buf != ')'; sig = getNextArg(sig, buf)) {
+ switch (*buf) {
case '[':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] == '[') {
- sig++;
- }
- if (sig[0] == 'L') {
- while (sig[0] != ';') {
- sig++;
- }
- }
- sig++;
- break;
case 'L':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] != ';') {
- sig++;
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
}
- sig++;
+ STACKIN_SUBCLASS(idx, class);
+ idx -= 1;
break;
case 'I':
case 'Z':
@@ -1690,24 +1828,20 @@
case 'C':
STACKIN(idx, TINT);
idx -= 1;
- sig++;
break;
case 'J':
STACKIN(idx-1, TLONG);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
case 'F':
STACKIN(idx, TFLOAT);
idx -= 1;
- sig++;
break;
case 'D':
STACKIN(idx-1, TDOUBLE);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
default:
assert("Signature character unknown" == 0);
@@ -1716,10 +1850,18 @@
STKPOP(call.in+1);
STKPUSH(call.out);
- switch (call.rettype) {
+ assert(*buf == ')');
+ sig = getNextArg(sig, buf);
+ switch (*buf) { /* call.rettype */
case '[':
case 'L':
- STACKOUT(0, TOBJ);
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
break;
case 'I':
case 'Z':
@@ -1740,7 +1882,9 @@
STACKOUT(1, TVOID);
break;
case 'V':
+ break;
default:
+ assert("Signature character unknown" == 0);
break;
}
INCPC(5);
@@ -1760,28 +1904,19 @@
idx = call.in - 1;
- while (sig[0] != ')') {
- switch (sig[0]) {
+ sig = getNextArg(sig, buf); /* initialize buf */
+ for ( ; *buf != ')'; sig = getNextArg(sig, buf)) {
+ switch (*buf) {
case '[':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] == '[') {
- sig++;
- }
- if (sig[0] == 'L') {
- while (sig[0] != ';') {
- sig++;
- }
- }
- sig++;
- break;
case 'L':
- STACKIN(idx, TOBJ);
- idx -= 1;
- while (sig[0] != ';') {
- sig++;
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
}
- sig++;
+ STACKIN_SUBCLASS(idx, class);
+ idx -= 1;
break;
case 'I':
case 'Z':
@@ -1790,24 +1925,20 @@
case 'C':
STACKIN(idx, TINT);
idx -= 1;
- sig++;
break;
case 'J':
STACKIN(idx-1, TLONG);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
case 'F':
STACKIN(idx, TFLOAT);
idx -= 1;
- sig++;
break;
case 'D':
STACKIN(idx-1, TDOUBLE);
STACKIN(idx, TVOID);
idx -= 2;
- sig++;
break;
default:
assert("Signature character unknown" == 0);
@@ -1816,10 +1947,18 @@
STKPOP(call.in);
STKPUSH(call.out);
- switch (call.rettype) {
+ assert(*buf == ')');
+ sig = getNextArg(sig, buf);
+ switch (*buf) { /* call.rettype */
case '[':
case 'L':
- STACKOUT(0, TOBJ);
+ strp = buf;
+ class = getClassFromSignature(strp, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
break;
case 'I':
case 'Z':
@@ -1840,7 +1979,9 @@
STACKOUT(1, TVOID);
break;
case 'V':
+ break;
default:
+ assert("Signature character unknown" == 0);
break;
}
INCPC(3);
@@ -1854,30 +1995,68 @@
}
}
STKPUSH(1);
- STACKOUT(0, TOBJ);
+ class = getClass(WORD(pc+1), meth->class, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ STACKOUT(0, class);
INCPC(3);
break;
case NEWARRAY:
STACKIN(0, TINT);
- STACKOUT(0, TOBJ);
+ switch (BYTE(pc+1)) {
+ case TYPE_Boolean:
+ STACKOUT(0, TBYTEARR);
+ break;
+ case TYPE_Char:
+ STACKOUT(0, TCHARARR);
+ break;
+ case TYPE_Float:
+ STACKOUT(0, TFLOATARR);
+ break;
+ case TYPE_Double:
+ STACKOUT(0, TDOUBLEARR);
+ break;
+ case TYPE_Byte:
+ STACKOUT(0, TBYTEARR);
+ break;
+ case TYPE_Short:
+ STACKOUT(0, TSHORTARR);
+ break;
+ case TYPE_Int:
+ STACKOUT(0, TINTARR);
+ break;
+ case TYPE_Long:
+ STACKOUT(0, TLONGARR);
+ break;
+ default:
+ assert("bad type in newarray" == 0);
+ break;
+ }
INCPC(2);
break;
case ANEWARRAY:
- if (getClass(WORD(pc+1), meth->class, einfo) == 0) {
+ if ((class = getClass(WORD(pc+1), meth->class, einfo)) == 0) {
if (!checkNoClassDefFoundError(einfo)) {
failed = true;
goto done;
}
}
+ // create an array sig for the specified type
+ if ((class = lookupArray(class, einfo)) == 0) {
+ failed = true;
+ goto done;
+ }
STACKIN(0, TINT);
- STACKOUT(0, TOBJ);
+ STACKOUT(0, class);
INCPC(3);
break;
case MULTIANEWARRAY:
- if (getClass(WORD(pc+1), meth->class, einfo) == 0) {
+ if ((class = getClass(WORD(pc+1), meth->class, einfo)) == 0) {
if (!checkNoClassDefFoundError(einfo)) {
failed = true;
goto done;
@@ -1887,32 +2066,39 @@
STACKIN(idx, TINT);
}
STKPOP(INSN(pc+3) - 1);
- STACKOUT(0, TOBJ);
+ STACKOUT(0, class);
INCPC(4);
break;
case ARRAYLENGTH:
- STACKIN(0, TOBJ);
+ STACKIN_ISARRAY(0);
STACKOUT(0, TINT);
INCPC(1);
break;
case ATHROW:
- STACKIN(0, TOBJ);
+ name = utf8ConstNew("java/lang/Throwable", strlen("java/lang/Throwable"));
+ class = loadClass(name, meth->class->loader, einfo);
+ if (class == 0) {
+ failed = true;
+ goto done;
+ }
+ utf8ConstRelease(name);
+ STACKIN_SUBCLASS(0, class);
STKPOP(1);
INCPC(1);
break;
case CHECKCAST:
- if (getClass(WORD(pc+1), meth->class, einfo) == 0) {
+ if ((class = getClass(WORD(pc+1), meth->class, einfo)) == 0) {
if (!checkNoClassDefFoundError(einfo)) {
failed = true;
goto done;
}
}
/* SET_INSN(pc, CHECKCAST_FAST); */
- STACKIN(0, TOBJ);
- STACKOUT(0, TOBJ);
+ STACKIN_ISREF(0);
+ STACKOUT(0, class);
INCPC(3);
break;
@@ -1924,21 +2110,21 @@
}
}
/* SET_INSN(pc, INSTANCEOF_FAST); */
- STACKIN(0, TOBJ);
+ STACKIN_ISREF(0);
STACKOUT(0, TINT);
INCPC(3);
break;
case MONITORENTER:
case MONITOREXIT:
- STACKIN(0, TOBJ);
+ STACKIN_ISREF(0);
STKPOP(1);
INCPC(1);
break;
case IFNULL:
case IFNONNULL:
- STACKIN(0, TOBJ);
+ STACKIN_ISREF(0);
STKPOP(1);
FRAMEMERGE(pc + WORD(pc+1), sp);
FRAMEMERGE(pc + 3, sp);
@@ -1983,36 +2169,72 @@
mergeFrame(codeinfo* codeInfo, int pc, int sp, frameElement* from, Method* meth)
{
int m;
- frameElement* to;
+ frameElement* to, oldTo;
to = FRAME(pc);
assert(to != 0);
/* Merge locals */
+ /* from the JVM spec, p. 130
+ * 4. Merge the state of the operand stack and local variables at the end
+ * of the execution of the current instruction into each of the successor
+ * instructions. [exception handler info ellided] */
for (m = 0; m < meth->localsz; m++) {
- if (from[m].type != TUNASSIGNED && from[m].type != to[m].type && to[m].type != TUNSTABLE) {
+ /* o If this is the first time the successor instruction has been
+ * visited, record that the operand stack and local variables values
+ * calculated in steps 2 and 3 are the state of the operand stack
+ * and local varaibles prior to executing the successor instruction.
+ * Set the "changed" bit for the successor instruction */
+ if (!IS_VISITED(pc)) {
+ to[m].type = from[m].type;
SET_NEEDVERIFY(pc);
- if (to[m].type == TUNASSIGNED) {
- to[m].type = from[m].type;
- }
- else {
- to[m].type = TUNSTABLE;
- }
+ continue;
+ }
+ if (to[m].type == TUNASSIGNED) {
+ to[m].type = from[m].type;
+ continue;
+ }
+ if (from[m].type == TUNASSIGNED) continue;
+
+ /* o If the successor instruction has been seen before, merge the operand
+ * stack and local variable values calculated in steps 2 and 3 into the
+ * values already there. Set the "changed" bit if there is any
+ * modification to the values.
+ * To merge two local variable states, corresponding pairs of local
+ * variables are compared. If the two types are not identical, then unless
+ * both contain reference values, the verifier records that the local
+ * variable contains an unusable value. If both of the pair of local
+ * variables contain reference values, the merge state contains a reference
+ * to an instance of the first common superclass of the two types. */
+
+ if (to[m].type == from[m].type) continue;
+
+ if(to[m].type != TUNSTABLE && CLASS_ISREF(to[m].type) && from[m].type != TUNSTABLE && CLASS_ISREF(from[m].type)) {
+ oldTo.type = to[m].type;
+ to[m].type = nca(to[m].type, from[m].type);
+ if (to[m].type != oldTo.type) SET_NEEDVERIFY(pc);
+ } else {
+ to[m].type = TUNSTABLE;
}
}
/* Merge stacks */
for (m = sp; m < meth->localsz + meth->stacksz; m++) {
- if (from[m].type != TUNASSIGNED && from[m].type != to[m].type && to[m].type != TUNSTABLE) {
+ if (from[m].type != TUNASSIGNED && from[m].type != to[m].type) {
SET_NEEDVERIFY(pc);
if (to[m].type == TUNASSIGNED) {
to[m].type = from[m].type;
}
else {
- to[m].type = TUNSTABLE;
+ if(CLASS_ISREF(to[m].type) && CLASS_ISREF(from[m].type)) {
+ to[m].type = nca(to[m].type, from[m].type);
+ } else
+ to[m].type = TUNSTABLE;
}
}
}
+
+ SET_VISITED(pc);
}
/*
@@ -2117,4 +2339,149 @@
/* Catch clause is okay. */
return true;
+}
+
+/* given the inheritance tree, find the nearest common ancestor of u and v.
+ * There is a lot of literature on this general topic, but as long as we
+ * have a O(1) hash function, the asymptotic complexity of this algorithm is
+ * as good as any, i.e. O(lg n) */
+static Hjava_lang_Class* nca(Hjava_lang_Class* u, Hjava_lang_Class* v)
+{
+ hashtab_t htb;
+ Hjava_lang_Class* retVal = 0;
+/* Hjava_lang_Class* origU = u;
+ Hjava_lang_Class* origV = v;
+ char buf[512];*/
+
+ /*printf("before asserts: u: %p v: %p\n", u, v); fflush(stdout);*/
+ assert(CLASS_ISREF(u));
+ assert(CLASS_ISREF(v));
+ /*printf("after asserts\n"); fflush(stdout);*/
+
+ /* handle the common case of two equal classes quickly, without building the
+ * paths to the root. */
+ if (u == v) return u;
+
+ if (u == TNULL) return v;
+ if (v == TNULL) return u;
+
+ htb = hashInit(nca_hashfunc, nca_compfunc, NULL, NULL);
+ /* insert the path from u to the root into htb */
+ for ( ; u; u = u->superclass) {
+ hashAdd(htb, u->name);
+ }
+ /* return the first element of the path from v to the root that is a member
+ * of the path from u to the root */
+ for ( ; v; v = v->superclass) {
+ if (hashFind(htb, v->name)) {
+ retVal = v;
+ break;
+ }
+ }
+ hashDestroy(htb);
+ assert(retVal != 0);
+ /*printNCA(buf, origU, origV, retVal);*/
+ return retVal;
+}
+
+/* return value: 0 -- 2^24-1 */
+int nca_hashfunc(const void *ptr1)
+{
+ const char* p;
+ unsigned int h = 0, g;
+ const char* end_p;
+
+ end_p = ((Utf8Const*)ptr1)->data + strlen(((Utf8Const*)ptr1)->data);
+
+ for(p = ((Utf8Const*)ptr1)->data; p < end_p; p++) {
+ h = (h << 4) + (*p);
+ if (g = h&0xf0000000) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ return h;
+}
+
+int nca_compfunc(const void *ptr1, const void *ptr2)
+{
+ /* can not use a direct pointer to equalUtf8Consts
+ * because it might be defined as a macro */
+ return utf8ConstEqual(ptr1, ptr2);
+}
+
+int issubclass(Hjava_lang_Class* super, Hjava_lang_Class* sub)
+{
+ Hjava_lang_Class* origSub = sub;
+
+ if (sub == TNULL) return 1;
+ if (CLASS_IS_ARRAY(super) && CLASS_IS_ARRAY(sub)) {
+ return issubclass(CLASS_ELEMENT_TYPE(super), CLASS_ELEMENT_TYPE(sub));
+ }
+ for ( ; sub && sub != super; sub = sub->superclass);
+ return sub != 0 ? 1 : issubinterface(super, origSub);
+}
+
+int issubinterface(Hjava_lang_Class* super, Hjava_lang_Class* sub)
+{
+ int i;
+
+ if (super == sub) return 1;
+ for (i = 0; i < sub->interface_len; i++) {
+ if (issubinterface(super, sub->interfaces[i])) return 1;
+ }
+ return 0;
+}
+
+static Hjava_lang_Class* getRefReturnClass(Method* meth, errorInfo *einfo)
+{
+/* constants* pool = CLASS_CONSTANTS (meth->class);*/
+ Utf8Const* sig;
+/* Utf8Const* name;*/
+ char* lptr;
+/* int len;*/
+
+ sig = meth->parsed_sig->signature;
+ lptr = strchr(sig->data, ')') + 1;
+ return getClassFromSignature(lptr, meth->class->loader, einfo);
+
+ /*len = &sig->data[sig->length] - (lptr + 1);*/
+ /*name = makeUtf8Const(lptr + 1, len);*/
+ /*return loadClass(name, meth->class->loader);*/
+}
+
+/* parses the next argument from sig into buf, returning pointer beyond arg */
+const char* getNextArg(const char* sig, char* buf)
+{
+ switch(*sig) {
+ case '[':
+ while (*sig == '[') *buf++ = *sig++;
+ if (*sig == 'L') {
+ while (*sig != ';') *buf++ = *sig++;
+ *buf++ = *sig++;
+ } else
+ *buf++ = *sig++;
+ *buf = '\0';
+ return sig;
+ case 'L':
+ while (*sig != ';') *buf++ = *sig++;
+ *buf++ = *sig++;
+ *buf = '\0';
+ return sig;
+ case 'I':
+ case 'Z':
+ case 'S':
+ case 'B':
+ case 'C':
+ case 'J':
+ case 'F':
+ case 'D':
+ *buf++ = *sig++;
+ *buf = '\0';
+ return sig;
+ default:
+ *buf++ = *sig++;
+ *buf = '\0';
+ return sig;
+ }
}
diff -ru kaffe-cvs.orig/kaffe/kaffevm/code-analyse.h kaffe-cvs/kaffe/kaffevm/code-analyse.h
--- kaffe-cvs.orig/kaffe/kaffevm/code-analyse.h Mon Mar 6 18:18:28 2000
+++ kaffe-cvs/kaffe/kaffevm/code-analyse.h Thu Aug 2 18:05:51 2001
@@ -5,7 +5,11 @@
* Transvirtual Technologies, Inc. All rights reserved.
*
* See the file "license.terms" for information on usage and redistribution
- * of this file.
+ * of this file.
+ *
+ * Written by Tim Wilkinson <tim at tjwassoc.co.uk>
+ * Full bytecode verifier by Joel Jones <jjones at uiuc.edu>, ported by
+ * Alexander Sotirov <sluncho at mirizma.org>
*/
#ifndef __code_analyse_h
@@ -47,11 +51,21 @@
#define TUNSTABLE ((Hjava_lang_Class*)1)
#define TADDR ((Hjava_lang_Class*)2)
#define TOBJ ((Hjava_lang_Class*)3)
+#define TNULL ((Hjava_lang_Class*)4) /* for aconst_null */
#define TVOID (voidClass)
#define TINT (intClass)
#define TLONG (longClass)
#define TFLOAT (floatClass)
#define TDOUBLE (doubleClass)
+#define TSTRING (StringClass)
+#define TINTARR (intArrClass)
+#define TBYTEARR (byteArrClass)
+#define TCHARARR (charArrClass)
+#define TSHORTARR (shortArrClass)
+#define TLONGARR (longArrClass)
+#define TFLOATARR (floatArrClass)
+#define TDOUBLEARR (doubleArrClass)
+#define TOBJARR (objectArrClass)
#define CONSTANTTYPE(VAL, LCL) \
switch (CLASS_CONST_TAG(meth->class, (LCL))) { \
@@ -69,7 +83,7 @@
break; \
case CONSTANT_String: \
case CONSTANT_ResolvedString: \
- VAL = TOBJ; \
+ VAL = TSTRING; \
break; \
default: \
VAL = TUNSTABLE; \
@@ -85,6 +99,7 @@
#define FLAG_NEEDVERIFY 0x0040
#define FLAG_DONEVERIFY 0x0080
#define FLAG_STARTOFINSTRUCTION 0x0100
+#define FLAG_VISITED 0x0200
#define FLAGS(_pc) codeInfo->perPC[_pc].flags
#define STACKPOINTER(_pc) codeInfo->perPC[_pc].stackPointer
@@ -135,6 +150,7 @@
#define SET_DONEVERIFY(pc) FLAGS(pc) &= ~FLAG_NEEDVERIFY; \
FLAGS(pc) |= FLAG_DONEVERIFY
#define SET_STARTOFINSTRUCTION(pc) FLAGS(pc) |= FLAG_STARTOFINSTRUCTION
+#define SET_VISITED(pc) FLAGS(pc) |= FLAG_VISITED
#define IS_STARTOFBASICBLOCK(pc) (FLAGS(pc) & FLAG_STARTOFBASICBLOCK)
#define IS_STACKPOINTERSET(pc) (FLAGS(pc) & FLAG_STACKPOINTERSET)
@@ -143,6 +159,7 @@
#define IS_NEEDVERIFY(pc) (FLAGS(pc) & FLAG_NEEDVERIFY)
#define IS_DONEVERIFY(pc) (FLAGS(pc) & FLAG_DONEVERIFY)
#define IS_STARTOFINSTRUCTION(pc) (FLAGS(pc) & FLAG_STARTOFINSTRUCTION)
+#define IS_VISITED(pc) (FLAGS(pc) & FLAG_VISITED)
#define IS_UNREACHABLE(pc) ((IS_STARTOFBASICBLOCK(pc) || \
IS_STARTOFEXCEPTION(pc)) && \
!IS_DONEVERIFY(pc))
@@ -192,7 +209,7 @@
#define STACKINIT(S, T) FRAME(pc)[sp+(S)].type = (T)
-#ifdef notyet
+/*#ifdef notyet*/
#define STACK_CHECKRANGE(S) if ((S)+sp < meth->localsz || (S)+sp > meth->localsz+meth->stacksz) { \
failed = true; \
postExceptionMessage(einfo, JAVA_LANG(VerifyError), "sp out of range: %d <%d> %d\n", meth->localsz, (S)+sp, meth->localsz + meth->stacksz); \
@@ -211,18 +228,46 @@
failed = true; \
postExceptionMessage(einfo, JAVA_LANG(VerifyError), "pc %d: lcl %d (is %d, want %d)\n", pc, (L), LCL(L).type, T); \
}
-#else
+/*#else
#define STACK_CHECKRANGE(S)
#define STACK_CHECKTYPE(S, T)
#define LOCAL_CHECKRANGE(L)
#define LOCAL_CHECKTYPE(L, T)
#endif
-
+*/
#define SF(S) activeFrame[sp+(S)]
#define LCL(L) activeFrame[(L)]
#define STACKIN(S, T) STACK_CHECKRANGE(S); \
STACK_CHECKTYPE(S, T)
+
+/* the input object is allowed to be a subclass (or subinterface) */
+#define STACKIN_SUBCLASS(S, T) STACK_CHECKRANGE(S); \
+ if (!issubclass((T), SF(S).type)) { \
+ failed = true; \
+ postExceptionMessage(einfo, JAVA_LANG(VerifyError), "pc %d: stk %d (is %s, want %s)\n", \
+ pc, sp+(S), SF(S).type->name->data, T->name->data); \
+ }
+
+/* the input object must be a reference type */
+#define STACKIN_ISREF(S) STACK_CHECKRANGE(S); \
+ if ((SF(S).type != TADDR) && \
+ (SF(S).type != TNULL) && \
+ (SF(S).type != TSTRING) && \
+ CLASS_IS_PRIMITIVE(SF(S).type)) { \
+ failed = true; \
+ postExceptionMessage(einfo, JAVA_LANG(VerifyError), "pc %d: stk %d (is %d, want ref type)\n", \
+ pc, sp+(S), SF(S)); \
+ }
+/* the input object must be an array type */
+#define STACKIN_ISARRAY(S) STACK_CHECKRANGE(S); \
+ if (!CLASS_IS_ARRAY(SF(S).type)) { \
+ failed = true; \
+ postExceptionMessage(einfo, JAVA_LANG(VerifyError), "pc %d: stk %d (is %d, want array type)\n", pc, sp+(S), SF(S)); \
+ }
+/* see page 130 of the JVM Specification for explanation of why nca is used */
+/*#define STACKOUT_NCA(S, T) SF(S) = nca(SF(S), T);*/
+
#define STACKOUT(S, T) SF(S).type = (T)
#define STACKOUT_CONST(S, T, V) SF(S).type = (T)
#define STACKOUT_LOCAL(S, T, L) SF(S) = LCL(L); \
@@ -250,10 +295,17 @@
#define LOCALIN(L, T) LOCAL_CHECKTYPE(L, T); \
LCL(L).used = 1
+#define CLASS_ISREF(CL) (((CL) == TNULL) || !(CLASS_IS_PRIMITIVE(CL)))
+
struct _methods;
bool verifyMethod(struct _methods*, codeinfo**, errorInfo*);
void tidyVerifyMethod(codeinfo**);
extern const uint8 insnLen[];
+
+/* nearest common ancestor (also known as {least | lowest} common ancestor */
+/*frameElement nca(frameElement u, frameElement v);*/
+
+const char* getNextArg(const char* sig, char* buf);
#endif
diff -ru kaffe-cvs.orig/kaffe/kaffevm/itypes.c kaffe-cvs/kaffe/kaffevm/itypes.c
--- kaffe-cvs.orig/kaffe/kaffevm/itypes.c Sat Sep 9 13:06:34 2000
+++ kaffe-cvs/kaffe/kaffevm/itypes.c Thu Aug 2 18:02:40 2001
@@ -31,6 +31,15 @@
Hjava_lang_Class* shortClass;
Hjava_lang_Class* voidClass;
+Hjava_lang_Class* intArrClass;
+Hjava_lang_Class* byteArrClass;
+Hjava_lang_Class* charArrClass;
+Hjava_lang_Class* shortArrClass;
+Hjava_lang_Class* longArrClass;
+Hjava_lang_Class* floatArrClass;
+Hjava_lang_Class* doubleArrClass;
+Hjava_lang_Class* objectArrClass;
+
Hjava_lang_Class* types[MAXTYPES];
static
@@ -128,6 +137,26 @@
floatClass->head.dtable = ClassClass->dtable;
doubleClass->head.dtable = ClassClass->dtable;
voidClass->head.dtable = ClassClass->dtable;
+}
+
+/*
+ * Initialize the array classes
+ */
+void
+initArrayClasses(errorInfo *einfo)
+{
+ static bool calledp = 0;
+ if (!calledp) {
+ calledp = 1;
+ intArrClass = lookupArray(intClass, einfo);
+ byteArrClass = lookupArray(byteClass, einfo);
+ charArrClass = lookupArray(charClass, einfo);
+ shortArrClass = lookupArray(shortClass, einfo);
+ longArrClass = lookupArray(longClass, einfo);
+ floatArrClass = lookupArray(floatClass, einfo);
+ doubleArrClass = lookupArray(doubleClass, einfo);
+ objectArrClass = lookupArray(ObjectClass, einfo);
+ }
}
static
diff -ru kaffe-cvs.orig/kaffe/kaffevm/itypes.h kaffe-cvs/kaffe/kaffevm/itypes.h
--- kaffe-cvs.orig/kaffe/kaffevm/itypes.h Thu Nov 4 14:29:12 1999
+++ kaffe-cvs/kaffe/kaffevm/itypes.h Thu Aug 2 18:02:40 2001
@@ -13,6 +13,7 @@
#define __itypes_h
#include "gtypes.h"
+#include "errors.h"
#define TYPE_Boolean 4
#define TYPE_Char 5
@@ -39,9 +40,19 @@
extern struct Hjava_lang_Class* shortClass;
extern struct Hjava_lang_Class* voidClass;
+extern struct Hjava_lang_Class* intArrClass;
+extern struct Hjava_lang_Class* byteArrClass;
+extern struct Hjava_lang_Class* charArrClass;
+extern struct Hjava_lang_Class* shortArrClass;
+extern struct Hjava_lang_Class* longArrClass;
+extern struct Hjava_lang_Class* floatArrClass;
+extern struct Hjava_lang_Class* doubleArrClass;
+extern struct Hjava_lang_Class* objectArrClass;
+
#define TYPE_CLASS(t) types[t]
extern void finishTypes(void);
extern void initTypes(void);
+extern void initArrayClasses(errorInfo *einfo);
#endif
More information about the kaffe
mailing list