Skip to content

Instantly share code, notes, and snippets.

@zserge
Last active November 22, 2021 11:16
Show Gist options
  • Save zserge/b75162f6cc3bf53c501cc6831e0e0884 to your computer and use it in GitHub Desktop.
Save zserge/b75162f6cc3bf53c501cc6831e0e0884 to your computer and use it in GitHub Desktop.
A tiny JVM to run a single method from a single class
public class Add {
public static int add(int a, int b) {
return a + b;
}
}
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