Last active
November 22, 2021 11:16
-
-
Save zserge/b75162f6cc3bf53c501cc6831e0e0884 to your computer and use it in GitHub Desktop.
A tiny JVM to run a single method from a single class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Add { | |
public static int add(int a, int b) { | |
return a + b; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/binary" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
) | |
type Field struct { | |
Flags uint16 | |
Name string | |
Descriptor string | |
Attributes []Attribute | |
} | |
type Attribute struct { | |
Name string | |
Data []byte | |
} | |
type Const struct { | |
Tag byte | |
NameIndex uint16 | |
ClassIndex uint16 | |
NameAndTypeIndex uint16 | |
StringIndex uint16 | |
DescIndex uint16 | |
String string | |
} | |
type ConstPool []Const | |
func (cp ConstPool) Resolve(index uint16) string { | |
if cp[index-1].Tag == 0x01 { | |
return cp[index-1].String | |
} | |
return "" | |
} | |
type loader struct { | |
c Class | |
r io.Reader | |
err error | |
} | |
func (l *loader) bytes(n int) []byte { | |
b := make([]byte, n, n) | |
if l.err == nil { | |
_, l.err = io.ReadFull(l.r, b) | |
} | |
return b | |
} | |
func (l *loader) u1() uint8 { return l.bytes(1)[0] } | |
func (l *loader) u2() uint16 { return binary.BigEndian.Uint16(l.bytes(2)) } | |
func (l *loader) u4() uint32 { return binary.BigEndian.Uint32(l.bytes(4)) } | |
func (l *loader) u8() uint64 { return binary.BigEndian.Uint64(l.bytes(8)) } | |
func (l *loader) cpinfo() (constPool ConstPool) { | |
constPoolCount := l.u2() | |
// Valid constant pool indices start from 1 | |
for i := uint16(1); i < constPoolCount; i++ { | |
c := Const{Tag: l.u1()} | |
switch c.Tag { | |
case 0x01: // UTF8 string literal, 2 bytes length + data | |
c.String = string(l.bytes(int(l.u2()))) | |
case 0x07: // Class index | |
c.NameIndex = l.u2() | |
case 0x08: // String reference index | |
c.StringIndex = l.u2() | |
case 0x09, 0x0a: // Field and method: class index + NaT index | |
c.ClassIndex = l.u2() | |
c.NameAndTypeIndex = l.u2() | |
case 0x0c: // Name-and-type | |
c.NameIndex, c.DescIndex = l.u2(), l.u2() | |
default: | |
l.err = fmt.Errorf("unsupported tag: %d %d", c.Tag, i) | |
} | |
constPool = append(constPool, c) | |
} | |
return constPool | |
} | |
func (l *loader) interfaces(cp ConstPool) (interfaces []string) { | |
interfaceCount := l.u2() | |
for i := uint16(0); i < interfaceCount; i++ { | |
interfaces = append(interfaces, cp.Resolve(l.u2())) | |
} | |
return interfaces | |
} | |
func (l *loader) fields(cp ConstPool) (fields []Field) { | |
fieldsCount := l.u2() | |
for i := uint16(0); i < fieldsCount; i++ { | |
fields = append(fields, Field{ | |
Flags: l.u2(), | |
Name: cp.Resolve(l.u2()), | |
Descriptor: cp.Resolve(l.u2()), | |
Attributes: l.attrs(cp), | |
}) | |
} | |
return fields | |
} | |
func (l *loader) attrs(cp ConstPool) (attrs []Attribute) { | |
attributesCount := l.u2() | |
for i := uint16(0); i < attributesCount; i++ { | |
attrs = append(attrs, Attribute{ | |
Name: cp.Resolve(l.u2()), | |
Data: l.bytes(int(l.u4())), | |
}) | |
} | |
return attrs | |
} | |
type Class struct { | |
ConstPool ConstPool | |
Name string | |
Super string | |
Flags uint16 | |
Interfaces []string | |
Fields []Field | |
Methods []Field | |
Attributes []Attribute | |
} | |
func Load(r io.Reader) (Class, error) { | |
loader := &loader{r: r} | |
c := Class{} | |
loader.u8() // magic(4), minor(2), major(2) | |
cp := loader.cpinfo() // const pool info | |
c.ConstPool = cp | |
c.Flags = loader.u2() // access flags | |
c.Name = cp.Resolve(loader.u2()) // this class | |
c.Super = cp.Resolve(loader.u2()) // super class | |
c.Interfaces = loader.interfaces(cp) | |
c.Fields = loader.fields(cp) // fields | |
c.Methods = loader.fields(cp) // methods | |
c.Attributes = loader.attrs(cp) // methods | |
return c, loader.err | |
} | |
type Frame struct { | |
Class Class | |
IP uint32 | |
Code []byte | |
Locals []interface{} | |
Stack []interface{} | |
} | |
func (c Class) Frame(method string, args ...interface{}) Frame { | |
for _, m := range c.Methods { | |
if m.Name == method { | |
for _, a := range m.Attributes { | |
if a.Name == "Code" && len(a.Data) > 8 { | |
maxLocals := binary.BigEndian.Uint16(a.Data[2:4]) | |
frame := Frame{ | |
Class: c, | |
Code: a.Data[8:], | |
Locals: make([]interface{}, maxLocals, maxLocals), | |
} | |
for i := 0; i < len(args); i++ { | |
frame.Locals[i] = args[i] | |
} | |
return frame | |
} | |
} | |
} | |
} | |
panic("method not found") | |
} | |
func Exec(f Frame) interface{} { | |
for { | |
op := f.Code[f.IP] | |
log.Printf("OP:%02x STACK:%v", op, f.Stack) | |
n := len(f.Stack) | |
switch op { | |
case 26: // iload_0 | |
f.Stack = append(f.Stack, f.Locals[0]) | |
case 27: // iload_1 | |
f.Stack = append(f.Stack, f.Locals[1]) | |
case 96: | |
a := f.Stack[n-1].(int32) | |
b := f.Stack[n-2].(int32) | |
f.Stack[n-2] = a + b | |
f.Stack = f.Stack[:n-1] | |
case 172: // ireturn | |
v := f.Stack[n-1] | |
f.Stack = f.Stack[:n-1] | |
return v | |
} | |
f.IP++ | |
} | |
} | |
func main() { | |
f, _ := os.Open("Add.class") | |
c, _ := Load(f) | |
frame := c.Frame("add", int32(2), int32(3)) | |
log.Println(Exec(frame)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment