Skip to content

Instantly share code, notes, and snippets.

@uehaj
Created July 6, 2011 14:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uehaj/1067380 to your computer and use it in GitHub Desktop.
Save uehaj/1067380 to your computer and use it in GitHub Desktop.
GVM: JVM written in Groovy
/*
* gvm.groovy: Java VM written in Groovy
*/
import org.objectweb.asm.*
import org.objectweb.asm.util.*
import org.objectweb.asm.tree.*
import static GVM.*
class GVMClass {
def methods = [:]
def fields = [:]
def createInstance() {
def result = [:]
fields.each {
result += [fields]
}
return result
}
}
class GVMFrame {
def locals
def labels
def pc
GVMFrame(int maxLocals, labels) {
this.locals = new Object[maxLocals]
this.labels = labels
}
}
class GVM {
static verbose
static log(msg) {
if (verbose) {
println msg
}
}
def classes = [
// pre loaded classes
"java/lang/Object":new GVMClass(methods:["<init>":{param->[:]}]), // simplified version of Object class
"java/io/PrintStream":new GVMClass(
methods:["println":
{param->println(param.head())},
"print":
{param->print(param.head())}
]
) // simplified version of PrintStream
]
def frames = []
def getCurrentFrame() { frames[-1] }
def opeStack = []
def loadClass(InputStream is) {
ClassReader cr = new ClassReader(is)
ClassNode cn = new ClassNode()
if (verbose) {
cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG)
}
cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG)
def cls = new GVMClass()
cn.methods.each {
cls.methods[it.name] = it
log "method register [class(${cn.name}).methods[${it.name}]]=$it"
}
cn.fields.each {
cls.fields[it.name] = it
}
classes[cn.name] = cls
}
def calcArgCount(desc) {
desc = desc.replaceAll(/\[/, '')
desc = desc.replaceAll(/\(/, '')
desc = desc.replaceAll(/\).*/, '')
desc = desc.replaceAll(/L[^;]+;/, '*').size()
return desc
}
//assert calcArgCount("III") == 3
//assert calcArgCount("Ljava/lang/String;II") == 3
//assert calcArgCount("DLjava/lang/String;IILjava/lang/String;J") == 6
def invokeMethod(String owner, String name, env, boolean isStatic, Object[] args) {
args.each {
env.opeStack.push(it);
}
invokeMethod(owner, name, env, isStatic)
}
def invokeMethod(String owner, String name, env, boolean isStatic) {
log "invokeMethod $owner.$name(isStatic=$isStatic)"
def method = env.classes[owner].methods[name]
if (method instanceof Closure) {
log "## method is closure(gvm only shorthand..)"
def result = method.call(env.opeStack)
method.getParameterTypes().size().times {
// TODO if !static, pop this object
env.opeStack.pop()
}
return result
}
log "# ${env.opeStack}"
env.frames += new GVMFrame(method.maxLocals, method.labels)
def n = 0
if (!isStatic) {
env.currentFrame.locals[method.maxLocals-1-n] = env.opeStack.pop()
n++;
}
(0 ..< calcArgCount(method.desc)).eachWithIndex { it, idx->
env.currentFrame.locals[method.maxLocals-1-n] = env.opeStack.pop()
n++;
}
env.currentFrame.pc = 0
try {
while (env.currentFrame.pc < method.instructions.size()) {
log "# env.currentFrame.pc=$env.currentFrame.pc"
log "# opeStack = ${env.opeStack}"
log "# locals = ${env.currentFrame.locals}"
def insn = method.instructions.get(env.currentFrame.pc)
if (insn.eval(env) == -1) { // *RETURN
log "#[2] opeStack = ${env.opeStack}"
return
}
env.currentFrame.pc++;
}
}
finally {
env.frames.pop();
log "return from invokeMethod $owner.$name($isStatic)"
}
}
}
MethodNode.metaClass.getLabels = {
def result = [:]
delegate.instructions.eachWithIndex { insn, idx ->
if (insn instanceof LabelNode) {
result[insn.label] = idx
}
}
return result
}
TypeInsnNode.metaClass.eval = { env ->
log "TypeInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.NEW:
def cls = env.classes[delegate.desc]
if (cls == null) {
throw new Error("Unknown class ${delegate.desc}")
}
env.opeStack += cls.createInstance()
break;
// case Opcodes.ANEWARRAY: break;
// case Opcodes.CHECKCAST: break;
// case Opcodes.INSTANCEOF: break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
InsnNode.metaClass.eval = { env ->
log "InsnNode(${delegate.opcode}).metaClass.eval"
switch (delegate.opcode) {
// case Opcodes.NOP: break;
// case Opcodes.ACONST_NULL: break;
// case Opcodes.ICONST_M1: break;
case Opcodes.ICONST_0:
log "iconst 0";
env.opeStack.push(0);
break;
case Opcodes.ICONST_1:
log "iconst 1";
env.opeStack.push(1);
break;
case Opcodes.ICONST_2:
log "iconst 2";
env.opeStack.push(2);
break;
case Opcodes.ICONST_3:
log "iconst 3";
env.opeStack.push(3);
break;
case Opcodes.ICONST_4:
log "iconst 4";
env.opeStack.push(4);
break;
case Opcodes.ICONST_5:
log "iconst 5";
env.opeStack.push(5);
break;
// case Opcodes.LCONST_0: break;
// case Opcodes.LCONST_1: break;
// case Opcodes.FCONST_0: break;
// case Opcodes.FCONST_1: break;
// case Opcodes.FCONST_2: break;
// case Opcodes.DCONST_0: break;
// case Opcodes.DCONST_1: break;
// case Opcodes.IALOAD: break;
// case Opcodes.LALOAD: break;
// case Opcodes.FALOAD: break;
// case Opcodes.DALOAD: break;
// case Opcodes.AALOAD: break;
// case Opcodes.BALOAD: break;
// case Opcodes.CALOAD: break;
// case Opcodes.SALOAD: break;
// case Opcodes.IASTORE: break;
// case Opcodes.LASTORE: break;
// case Opcodes.FASTORE: break;
// case Opcodes.DASTORE: break;
// case Opcodes.AASTORE: break;
// case Opcodes.BASTORE: break;
// case Opcodes.CASTORE: break;
// case Opcodes.SASTORE: break;
case Opcodes.POP:
log "pop";
env.opeStack.pop()
break;
// case Opcodes.POP2: break;
case Opcodes.DUP:
log "dup"
env.opeStack += env.opeStack[-1]
break;
// case Opcodes.DUP_X1: break;
// case Opcodes.DUP_X2: break;
// case Opcodes.DUP2: break;
// case Opcodes.DUP2_X1: break;
// case Opcodes.DUP2_X2: break;
// case Opcodes.SWAP: break;
case Opcodes.IADD:
log "iadd";
env.opeStack += env.opeStack.pop()+env.opeStack.pop()
break;
// case Opcodes.LADD: break;
// case Opcodes.FADD: break;
// case Opcodes.DADD: break;
// case Opcodes.ISUB: break;
// case Opcodes.LSUB: break;
// case Opcodes.FSUB: break;
// case Opcodes.DSUB: break;
// case Opcodes.IMUL: break;
// case Opcodes.LMUL: break;
// case Opcodes.FMUL: break;
// case Opcodes.DMUL: break;
// case Opcodes.IDIV: break;
// case Opcodes.LDIV: break;
// case Opcodes.FDIV: break;
// case Opcodes.DDIV: break;
// case Opcodes.IREM: break;
// case Opcodes.LREM: break;
// case Opcodes.FREM: break;
// case Opcodes.DREM: break;
// case Opcodes.INEG: break;
// case Opcodes.LNEG: break;
// case Opcodes.FNEG: break;
// case Opcodes.DNEG: break;
// case Opcodes.ISHL: break;
// case Opcodes.LSHL: break;
// case Opcodes.ISHR: break;
// case Opcodes.LSHR: break;
// case Opcodes.IUSHR: break;
// case Opcodes.LUSHR: break;
// case Opcodes.IAND: break;
// case Opcodes.LAND: break;
// case Opcodes.IOR: break;
// case Opcodes.LOR: break;
// case Opcodes.IXOR: break;
// case Opcodes.LXOR: break;
// case Opcodes.I2L: break;
// case Opcodes.I2F: break;
// case Opcodes.I2D: break;
// case Opcodes.L2I: break;
// case Opcodes.L2F: break;
// case Opcodes.L2D: break;
// case Opcodes.F2I: break;
// case Opcodes.F2L: break;
// case Opcodes.F2D: break;
// case Opcodes.D2I: break;
// case Opcodes.D2L: break;
// case Opcodes.D2F: break;
// case Opcodes.I2B: break;
// case Opcodes.I2C: break;
// case Opcodes.I2S: break;
// case Opcodes.LCMP: break;
// case Opcodes.FCMPL: break;
// case Opcodes.FCMPG: break;
// case Opcodes.DCMPL: break;
// case Opcodes.DCMPG: break;
case Opcodes.IRETURN:
log "ireturn"
return -1;
break;
// case Opcodes.LRETURN: break;
// case Opcodes.FRETURN: break;
// case Opcodes.DRETURN: break;
// case Opcodes.ARETURN: break;
case Opcodes.RETURN:
log "return"
return -1
break;
// case Opcodes.ARRAYLENGTH: break;
// case Opcodes.ATHROW: break;
// case Opcodes.MONITORENTER: break;
// case Opcodes.MONITOREXIT: break;
default: throw new Error("Not implemented opecode:"+delegate.opcode);
}
}
MethodInsnNode.metaClass.eval = { env ->
log "MethodInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.INVOKEVIRTUAL:
log "invokevirtual"
env.invokeMethod(delegate.owner, delegate.name, env, false)
break;
case Opcodes.INVOKESPECIAL:
log "invokespecial(${delegate.owner}, ${delegate.name})";
env.invokeMethod(delegate.owner, delegate.name, env, false)
break;
case Opcodes.INVOKESTATIC:
log "invokestatic";
env.invokeMethod(delegate.owner, delegate.name, env, true)
break;
// case Opcodes.INVOKEINTERFACE: break;
// case Opcodes.INVOKEDYNAMIC: break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
VarInsnNode.metaClass.eval = { env ->
log "VarInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.ILOAD:
log "iload";
env.opeStack.push(env.currentFrame.locals[delegate.var]);
break;
// case Opcodes.LLOAD: break;
// case Opcodes.FLOAD: break;
// case Opcodes.DLOAD: break;
case Opcodes.ALOAD:
log "aload"
env.opeStack.push(env.currentFrame.locals[delegate.var]);
break;
case Opcodes.ISTORE:
log "istore";
env.currentFrame.locals[delegate.var] = env.opeStack.pop();
break;
// case Opcodes.LSTORE: break;
// case Opcodes.FSTORE: break;
// case Opcodes.DSTORE: break;
case Opcodes.ASTORE:
log "astore";
env.currentFrame.locals[delegate.var] = env.opeStack.pop();
break;
case Opcodes.RET:
log "return";
break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
FieldInsnNode.metaClass.eval = { env ->
log "FieldInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.GETSTATIC:
log "getstatic";
break;
// case Opcodes.PUTSTATIC: break;
// case Opcodes.GETFIELD: break;
// case Opcodes.PUTFIELD: break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
LdcInsnNode.metaClass.eval = { env ->
log "LdcInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.LDC:
log "ldc";
env.opeStack.push(delegate.cst);
break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
LabelNode.metaClass.eval = { env ->
log "LabelNode.metaClass.eval"
}
FrameNode.metaClass.eval = { env ->
}
IntInsnNode.metaClass.eval = { env ->
log "IntInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.BIPUSH:
case Opcodes.SIPUSH:
case Opcodes.NEWARRAY:
env.opeStack.push(delegate.operand);
break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
JumpInsnNode.metaClass.eval = { env ->
log "JumpInsnNode.metaClass.eval"
def dest = env.currentFrame.labels[delegate.label.label]
if (dest == null) {
throw new Error("Label not registered:"+delegate.label.label)
}
switch (delegate.opcode) {
// case Opcodes.IFEQ: break;
// case Opcodes.IFNE: break;
// case Opcodes.IFLT: break;
// case Opcodes.IFGE: break;
// case Opcodes.IFGT: break;
// case Opcodes.IFLE: break;
// case Opcodes.IF_ICMPEQ: break;
// case Opcodes.IF_ICMPNE: break;
// case Opcodes.IF_ICMPLT: break;
case Opcodes.IF_ICMPGE:
value1 = env.opeStack.pop()
value2 = env.opeStack.pop()
log "IF_ICMPGE("+value2+">="+value1+")"
if (value2 >= value1) {
env.currentFrame.pc = dest
}
break;
// case Opcodes.IF_ICMPGT: println "IF_ICMPGT"; break;
// case Opcodes.IF_ICMPLE: println "IF_ICMPLE"; break;
// case Opcodes.IF_ACMPEQ: println "IF_ACMPEQ"; break;
// case Opcodes.IF_ACMPNE: println "IF_ACMPNE"; break;
case Opcodes.GOTO:
log "GOTO:"+dest
env.currentFrame.pc = dest
break
// case Opcodes.JSR: println "JSR"; break;
// case Opcodes.IFNULL: println "IFNULL"; break;
// case Opcodes.IFNONNULL: println "IFNONNULL"; break;
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
IincInsnNode.metaClass.eval = { env ->
log "IincInsnNode.metaClass.eval"
switch (delegate.opcode) {
case Opcodes.IINC:
env.currentFrame.locals[delegate.var] += delegate.incr
break
default: throw new Error("Unknown instruction:"+delegate.opcode);
}
}
def loadAndRun(is, fqcn, args) {
def env = new GVM()
env.loadClass(is)
env.invokeMethod(fqcn.replaceAll(/\./, "/"), 'main', env, true, [args] as Object[])
}
def scriptMain(args) {
def cli = new CliBuilder(usage:'groovy gvm.groovy <class FQCN>')
cli.v(longOpt:'verbose', 'verbose')
cli.cp(longOpt:'classpath', args:1, 'classpath')
cli.demo(longOpt:'demo', 'demo')
cli.h(longOpt:'help', 'usage')
opt = cli.parse(args)
if (!opt) {System.exit 1}
verbose = opt.v
if (opt.demo) {
demo()
return
}
if (opt.h || opt.arguments().size() < 1) {
cli.usage()
return
}
def classPath = opt.cp ?: '.'
def className = opt.arguments()[0]
def classFileName = className.replaceAll(/\./, "/")+".class"
classFile = classPath.split(System.properties."path.separator").collect {
new File(it + System.properties."file.separator" + classFileName)
}.find {it.exists()}
if (classFile) {
classFile.withInputStream {
loadAndRun(it, className, new String[0])
}
}
else {
System.err.println("class not found:"+classFileName)
}
}
def demo() {
/*
* class file data compiled from following java source:
* <code>
package sample;
public class Hello {
public static void main(String[] args){
Hello h = new Hello();
h.hello();
System.out.println(h.plus(1,2));
for (int i=0; i<10; i++) {
System.out.print("i=");
System.out.println(i);
}
}
void hello() {
System.out.println("Hello World");
}
int plus(int i, int j) {
return i+j;
}
}
* </code>
*/
data = """\
yv66vgAAADIALwoADAAaBwAbCgACABoKAAIAHAkAHQAeCgACAB8KACAAIQgAIgoA
IAAjCAAkCgAgACUHACYBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJl
clRhYmxlAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAA1TdGFja01h
cFRhYmxlBwAbAQAFaGVsbG8BAARwbHVzAQAFKElJKUkBAApTb3VyY2VGaWxlAQAK
SGVsbG8uamF2YQwADQAOAQAMc2FtcGxlL0hlbGxvDAAVAA4HACcMACgAKQwAFgAX
BwAqDAArACwBAAJpPQwALQAuAQALSGVsbG8gV29ybGQMACsALgEAEGphdmEvbGFu
Zy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJp
bnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BAAQoSSlW
AQAFcHJpbnQBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQACAAwAAAAAAAQAAQAN
AA4AAQAPAAAAHQABAAEAAAAFKrcAAbEAAAABABAAAAAGAAEAAAACAAkAEQASAAEA
DwAAAHwABAADAAAANrsAAlm3AANMK7YABLIABSsEBbYABrYABwM9HBAKogAYsgAF
Egi2AAmyAAUctgAHhAIBp//osQAAAAIAEAAAACIACAAAAAQACAAFAAwABgAYAAcA
IAAIACgACQAvAAcANQALABMAAAAMAAL9ABoHABQB+gAaAAAAFQAOAAEADwAAACUA
AgABAAAACbIABRIKtgALsQAAAAEAEAAAAAoAAgAAAA0ACAAOAAAAFgAXAAEADwAA
ABwAAgADAAAABBscYKwAAAABABAAAAAGAAEAAAAQAAEAGAAAAAIAGQ==""".decodeBase64()
loadAndRun(new ByteArrayInputStream(data), "sample.Hello", new String[0])
}
if (args.size() == 0) {
scriptMain(["-demo"])
}
else {
scriptMain(args)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment