├── .gitignore ├── Arith.class ├── Arith.java ├── HelloWorld.class ├── HelloWorld.java ├── LICENSE ├── Makefile ├── README.md ├── main.go └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /gojvm 2 | -------------------------------------------------------------------------------- /Arith.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DQNEO/gojvm/851bf7503dd9b9c21972c1789c06e9f6b292fed2/Arith.class -------------------------------------------------------------------------------- /Arith.java: -------------------------------------------------------------------------------- 1 | public class Arith { 2 | public static void main(String[] args) { 3 | int c = sum(30, 12); 4 | System.out.println(c); 5 | } 6 | 7 | private static int sum(int a, int b) { 8 | return a + b; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /HelloWorld.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DQNEO/gojvm/851bf7503dd9b9c21972c1789c06e9f6b292fed2/HelloWorld.class -------------------------------------------------------------------------------- /HelloWorld.java: -------------------------------------------------------------------------------- 1 | public class HelloWorld { 2 | public static void main(String[] args) { 3 | System.out.println("Hello world"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 DQNEO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: main.go 2 | go build -o gojvm main.go 3 | 4 | clean: 5 | rm gojvm 6 | 7 | test: 8 | ./test.sh 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gojvm 2 | 3 | gojvm is an JVM implementation by Go. 4 | 5 | It can interpret and run a JVM bytecode file. 6 | Currently, it only supports "hello world" and arithmetic addition. 7 | 8 | # Usage 9 | 10 | ## Hello world 11 | 12 | HelloWorld.java 13 | 14 | ```java 15 | public class HelloWorld { 16 | public static void main(String[] args) { 17 | System.out.println("Hello world"); 18 | } 19 | } 20 | ``` 21 | 22 | ``` 23 | $ cat HelloWorld.class | go run main.go (git)-[master] (p9) 24 | Hello world 25 | ``` 26 | 27 | ## Arithmetic addition 28 | 29 | Arith.java 30 | 31 | ```java 32 | public class Arith { 33 | public static void main(String[] args) { 34 | int c = sum(30, 12); 35 | System.out.println(c); 36 | } 37 | 38 | private static int sum(int a, int b) { 39 | return a + b; 40 | } 41 | } 42 | ``` 43 | 44 | ``` 45 | $ cat Arith.class | go run main.go (git)-[master] ? 46 | 42 47 | ``` 48 | # How to test 49 | 50 | ``` 51 | make test 52 | ``` 53 | 54 | # Acknowledgment 55 | 56 | gojvm is inspired by [PHPJava](https://github.com/php-java/php-java). 57 | 58 | I really appreciate the work. 59 | 60 | # License 61 | 62 | MIT 63 | 64 | # Author 65 | 66 | @DQNEO 67 | 68 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | var cf *ClassFile 11 | var cpool ConstantPool 12 | 13 | type ExceptionTable struct { 14 | start_pc u2 15 | end_pc u2 16 | handler_pc u2 17 | catch_type u2 18 | } 19 | 20 | type CodeAttribute struct { 21 | attribute_name_index u2 22 | attribute_length u4 23 | max_stqck u2 24 | max_locals u2 25 | code_length u4 26 | code []byte 27 | exception_table_length u2 28 | exception_tables []ExceptionTable 29 | attributes_count u2 30 | attribute_infos []AttributeInfo 31 | } 32 | 33 | type AttributeInfo struct { 34 | attribute_name_index u2 35 | attribute_length u4 36 | body []byte 37 | } 38 | 39 | type MethodInfo struct { 40 | access_flags u2 41 | name_index u2 42 | descriptor_index u2 43 | attributes_count u2 44 | ai []CodeAttribute 45 | } 46 | 47 | type ConstantPoolEntry interface { 48 | Type() string 49 | String() string 50 | } 51 | 52 | type CONSTANT_Class_info struct { 53 | tag u1 54 | name_index u2 55 | } 56 | 57 | type CONSTANT_Fieldref_info struct { 58 | tag u1 59 | class_index u2 60 | name_and_type_index u2 61 | } 62 | 63 | type CONSTANT_Methodref_info struct { 64 | tag u1 65 | class_index u2 66 | name_and_type_index u2 67 | } 68 | 69 | type CONSTANT_String_info struct { 70 | tag u1 71 | string_index u2 72 | } 73 | 74 | type CONSTANT_NameAndType_info struct { 75 | tag u1 76 | name_index u2 77 | descriptor_index u2 78 | } 79 | 80 | type CONSTANT_Utf8_info struct { 81 | tag u1 82 | length u2 83 | bytes []byte 84 | } 85 | 86 | func (c *CONSTANT_Class_info) Type() string { return "Class" } 87 | func (c *CONSTANT_Fieldref_info) Type() string { return "Fieldref" } 88 | func (c *CONSTANT_Methodref_info) Type() string { return "Methodref" } 89 | func (c *CONSTANT_String_info) Type() string { return "String" } 90 | func (c *CONSTANT_NameAndType_info) Type() string { return "NameAndType" } 91 | func (c *CONSTANT_Utf8_info) Type() string { return "UTF8" } 92 | 93 | func (c *CONSTANT_Class_info) String() string { return "Class" } 94 | func (c *CONSTANT_Fieldref_info) String() string { return "Fieldref" } 95 | func (c *CONSTANT_Methodref_info) String() string { return "Methodref" } 96 | func (c *CONSTANT_String_info) String() string { return "String" } 97 | func (c *CONSTANT_NameAndType_info) String() string { return "NameAndType" } 98 | func (c *CONSTANT_Utf8_info) String() string { return "UTF8" } 99 | 100 | func (c *CONSTANT_Class_info) getName() string { 101 | return cpool.getUTF8AsString(c.name_index) 102 | } 103 | 104 | func (c *CONSTANT_Fieldref_info) getClassInfo() *CONSTANT_Class_info { 105 | return cpool.getClassInfo(c.class_index) 106 | } 107 | 108 | func (c *CONSTANT_Fieldref_info) getNameAndType() *CONSTANT_NameAndType_info { 109 | return cpool.getNameAndType(c.name_and_type_index) 110 | } 111 | 112 | func (c *CONSTANT_Methodref_info) getClassInfo() *CONSTANT_Class_info { 113 | return cpool.getClassInfo(c.class_index) 114 | } 115 | 116 | func (c *CONSTANT_Methodref_info) getNameAndType() *CONSTANT_NameAndType_info { 117 | return cpool.getNameAndType(c.name_and_type_index) 118 | } 119 | 120 | 121 | func (c *CONSTANT_NameAndType_info) getName() string { 122 | return cpool.getUTF8AsString(c.name_index) 123 | } 124 | 125 | func (c *CONSTANT_NameAndType_info) getDescriptor() string { 126 | return cpool.getUTF8AsString(c.descriptor_index) 127 | } 128 | 129 | func readMagic(br *ByteReader) [4]byte { 130 | br.byteIndex += 4 131 | return [4]byte{br.bytes[0], br.bytes[1], br.bytes[2], br.bytes[3]} 132 | } 133 | 134 | type u1 uint8 135 | type u2 uint16 136 | type u4 uint32 137 | 138 | func (br *ByteReader) readU1() u1 { 139 | b := br.bytes[br.byteIndex] 140 | br.byteIndex++ 141 | return u1(b) 142 | } 143 | 144 | func (br *ByteReader) readU2() u2 { 145 | left := br.bytes[br.byteIndex] 146 | right := br.bytes[br.byteIndex+1] 147 | br.byteIndex += 2 148 | 149 | return u2(u2(left)*256 + u2(right)) 150 | } 151 | func (br *ByteReader) readU4() u4 { 152 | b1 := br.bytes[br.byteIndex] 153 | b2 := br.bytes[br.byteIndex+1] 154 | b3 := br.bytes[br.byteIndex+2] 155 | b4 := br.bytes[br.byteIndex+3] 156 | br.byteIndex += 4 157 | 158 | return u4(u4(b1)*256*256*256 + u4(b2)*256*256 + u4(b3)*256 + u4(b4)) 159 | } 160 | 161 | func (br *ByteReader) readBytes(n int) []byte { 162 | r := br.bytes[br.byteIndex : br.byteIndex+n] 163 | br.byteIndex += n 164 | return r 165 | } 166 | 167 | func readAttributeInfo(br *ByteReader) AttributeInfo { 168 | a := AttributeInfo{ 169 | attribute_name_index: br.readU2(), 170 | attribute_length: br.readU4(), 171 | } 172 | a.body = br.readBytes(int(a.attribute_length)) 173 | return a 174 | } 175 | 176 | func readExceptionTable(br *ByteReader) { 177 | br.readU2() 178 | br.readU2() 179 | br.readU2() 180 | br.readU2() 181 | } 182 | 183 | func readCodeAttribute(br *ByteReader) CodeAttribute { 184 | a := CodeAttribute{ 185 | attribute_name_index: br.readU2(), 186 | attribute_length: br.readU4(), 187 | max_stqck: br.readU2(), 188 | max_locals: br.readU2(), 189 | code_length: br.readU4(), 190 | } 191 | a.code = br.readBytes(int(a.code_length)) 192 | a.exception_table_length = br.readU2() 193 | for i := u2(0); i < a.exception_table_length; i++ { 194 | readExceptionTable(br) 195 | } 196 | a.attributes_count = br.readU2() 197 | for i := u2(0); i < a.attributes_count; i++ { 198 | readAttributeInfo(br) 199 | } 200 | return a 201 | } 202 | 203 | func (br *ByteReader) readMethodInfo() MethodInfo { 204 | methodInfo := MethodInfo{ 205 | access_flags: br.readU2(), 206 | name_index: br.readU2(), 207 | descriptor_index: br.readU2(), 208 | attributes_count: br.readU2(), 209 | } 210 | var cas []CodeAttribute 211 | for i := u2(0); i < methodInfo.attributes_count; i++ { 212 | ca := readCodeAttribute(br) 213 | cas = append(cas, ca) 214 | } 215 | methodInfo.ai = cas 216 | 217 | return methodInfo 218 | } 219 | 220 | type ConstantPool []ConstantPoolEntry 221 | 222 | // https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-4.html#jvms-4.1z 223 | type ClassFile struct { 224 | magic [4]byte 225 | minor_version u2 226 | major_version u2 227 | constant_pool_count u2 228 | constant_pool ConstantPool 229 | access_flags u2 230 | this_class u2 231 | super_class u2 232 | interface_count u2 233 | fields_count u2 234 | methods_count u2 235 | methods []MethodInfo 236 | attributes_count u2 237 | attributes []AttributeInfo 238 | methodMap map[u2]*MethodInfo 239 | } 240 | 241 | type ByteReader struct { 242 | bytes []byte 243 | byteIndex int 244 | } 245 | 246 | func parseClassFile(filename string) *ClassFile { 247 | bytes, err := ioutil.ReadFile(filename) 248 | if err != nil { 249 | panic(err) 250 | } 251 | 252 | br := &ByteReader{ 253 | byteIndex:0, 254 | bytes: bytes, 255 | } 256 | magic := readMagic(br) // cafebabe 257 | minor_version := br.readU2() 258 | major_version := br.readU2() 259 | constant_pool_count := br.readU2() 260 | 261 | var constant_pool []ConstantPoolEntry 262 | constant_pool = append(constant_pool, nil) 263 | for i := u2(0); i < constant_pool_count-1; i++ { 264 | tag := br.readU1() 265 | //debugf("[i=%d] tag=%02X\n", i, tag) 266 | var e ConstantPoolEntry 267 | switch tag { 268 | case 0x09: 269 | e = &CONSTANT_Fieldref_info{ 270 | class_index: br.readU2(), 271 | name_and_type_index: br.readU2(), 272 | tag: tag, 273 | } 274 | case 0x0a: 275 | e = &CONSTANT_Methodref_info{ 276 | class_index: br.readU2(), 277 | name_and_type_index: br.readU2(), 278 | tag: tag, 279 | } 280 | case 0x08: 281 | e = &CONSTANT_String_info{ 282 | string_index: br.readU2(), 283 | tag: tag, 284 | } 285 | case 0x07: 286 | e = &CONSTANT_Class_info{ 287 | name_index: br.readU2(), 288 | tag: tag, 289 | } 290 | case 0x01: 291 | ln := br.readU2() 292 | e = &CONSTANT_Utf8_info{ 293 | tag: tag, 294 | length: ln, 295 | bytes: br.readBytes(int(ln)), 296 | } 297 | case 0x0c: 298 | e = &CONSTANT_NameAndType_info{ 299 | name_index: br.readU2(), 300 | descriptor_index: br.readU2(), 301 | tag: tag, 302 | } 303 | default: 304 | panic("unknown tag") 305 | } 306 | //e.tag = tag 307 | constant_pool = append(constant_pool, e) 308 | } 309 | 310 | access_flags := br.readU2() 311 | this_class := br.readU2() 312 | super_class := br.readU2() 313 | interface_count := br.readU2() 314 | fields_count := br.readU2() 315 | methods_count := br.readU2() 316 | 317 | var methods []MethodInfo = make([]MethodInfo, methods_count) 318 | for i := u2(0); i < methods_count; i++ { 319 | methodInfo := br.readMethodInfo() 320 | methods[i] = methodInfo 321 | } 322 | attributes_count := br.readU2() 323 | var attributes []AttributeInfo 324 | for i := u2(0); i < attributes_count; i++ { 325 | attr := readAttributeInfo(br) 326 | attributes = append(attributes, attr) 327 | } 328 | if len(bytes) == br.byteIndex { 329 | debugf("__EOF__\n") 330 | } 331 | 332 | return &ClassFile{ 333 | magic: magic, 334 | minor_version: minor_version, 335 | major_version: major_version, 336 | constant_pool_count: constant_pool_count, 337 | constant_pool: constant_pool, 338 | access_flags: access_flags, 339 | this_class: this_class, 340 | super_class: super_class, 341 | interface_count: interface_count, 342 | fields_count: fields_count, 343 | methods_count: methods_count, 344 | methods: methods, 345 | attributes_count: attributes_count, 346 | attributes: attributes, 347 | } 348 | } 349 | 350 | func c2s(c interface{}) string { 351 | switch c.(type) { 352 | case *CONSTANT_Fieldref_info: 353 | cf := c.(*CONSTANT_Fieldref_info) 354 | return fmt.Sprintf("Fieldref\t#0x%02x.#0x%02x", 355 | cf.class_index, cf.name_and_type_index) 356 | case *CONSTANT_Methodref_info: 357 | cm := c.(*CONSTANT_Methodref_info) 358 | return fmt.Sprintf("Methodref\t#0x%02x.#0x%02x", 359 | cm.class_index, cm.name_and_type_index) 360 | case *CONSTANT_Class_info: 361 | return fmt.Sprintf("Class\t0x%02x", c.(*CONSTANT_Class_info).name_index) 362 | case *CONSTANT_String_info: 363 | return fmt.Sprintf("String\t0x%02x", c.(*CONSTANT_String_info).string_index) 364 | case *CONSTANT_NameAndType_info: 365 | cn := c.(*CONSTANT_NameAndType_info) 366 | return fmt.Sprintf("NameAndType\t#0x%02x:#0x%02x", cn.name_index, cn.descriptor_index) 367 | case *CONSTANT_Utf8_info: 368 | return fmt.Sprintf("Utf8\t%s", c.(*CONSTANT_Utf8_info).bytes) 369 | default: 370 | panic("Unknown constant pool") 371 | } 372 | 373 | } 374 | 375 | func debugConstantPool(cp ConstantPool) { 376 | for i, c := range cp { 377 | if i == 0 { 378 | continue 379 | } 380 | s := c2s(c) 381 | debugf(" #0x%02x = %s\n",i, s) 382 | } 383 | } 384 | 385 | func (cp ConstantPool) get(i u2) interface{} { 386 | return cp[i] 387 | } 388 | 389 | func (cp ConstantPool) getFieldref(id u2) *CONSTANT_Fieldref_info { 390 | entry := cp.get(id) 391 | c, ok := entry.(*CONSTANT_Fieldref_info) 392 | if !ok { 393 | panic("type mismatch") 394 | } 395 | return c 396 | } 397 | 398 | func (cp ConstantPool) getMethodref(id u2) *CONSTANT_Methodref_info { 399 | entry := cp.get(id) 400 | c, ok := entry.(*CONSTANT_Methodref_info) 401 | if !ok { 402 | panic("type mismatch") 403 | } 404 | return c 405 | } 406 | 407 | func (cp ConstantPool) getClassInfo(id u2) *CONSTANT_Class_info { 408 | entry := cp.get(id) 409 | ci, ok := entry.(*CONSTANT_Class_info) 410 | if !ok { 411 | panic("type mismatch") 412 | } 413 | return ci 414 | } 415 | 416 | func (cp ConstantPool) getNameAndType(id u2) *CONSTANT_NameAndType_info { 417 | entry := cp.get(id) 418 | c, ok := entry.(*CONSTANT_NameAndType_info) 419 | if !ok { 420 | panic("type mismatch") 421 | } 422 | return c 423 | } 424 | 425 | func (cp ConstantPool) getString(id u2) string { 426 | entry := cp.get(id) 427 | c, ok := entry.(*CONSTANT_String_info) 428 | if !ok { 429 | panic(fmt.Sprintf("CONSTANT_String_info expected, but got %T", entry)) 430 | } 431 | 432 | return cp.getUTF8AsString(c.string_index) 433 | } 434 | 435 | func (cp ConstantPool) getUTF8AsString(id u2) string { 436 | return string(cp.getUTF8Bytes(id)) 437 | } 438 | 439 | func (cp ConstantPool) getUTF8Bytes(id u2) []byte { 440 | entry := cp.get(id) 441 | utf8, ok := entry.(*CONSTANT_Utf8_info) 442 | if !ok { 443 | panic("type mismatch") 444 | } 445 | return utf8.bytes 446 | } 447 | 448 | func debugClassFile(cf *ClassFile) { 449 | cp := cf.constant_pool 450 | for _, char := range cf.magic { 451 | debugf("%x ", char) 452 | } 453 | 454 | debugf("\n") 455 | debugf("major_version = %d, minior_version = %d\n", cf.major_version, cf.minor_version) 456 | debugf("access_flags=%d\n", cf.access_flags) 457 | thisClassInfo := cp.getClassInfo(cf.this_class) 458 | debugf("class %s\n", thisClassInfo.getName()) 459 | debugf(" super_class=%s\n", cp.getClassInfo(cf.super_class).getName()) 460 | 461 | debugf("Constant pool:\n") 462 | debugConstantPool(cf.constant_pool) 463 | 464 | debugf("interface_count=%d\n", cf.interface_count) 465 | //debugf("interfaces=%d\n", interfaces) 466 | debugf("fields_count=%d\n", cf.fields_count) 467 | debugf("methods_count=%d\n", cf.methods_count) 468 | 469 | cf.methodMap = make(map[u2]*MethodInfo) 470 | for _, methodInfo := range cf.methods{ 471 | cf.methodMap[methodInfo.name_index] = &methodInfo 472 | methodName := cp.getUTF8AsString(methodInfo.name_index) 473 | debugf(" #0x%02x %s:\n", methodInfo.name_index, methodName) 474 | for _, ca := range methodInfo.ai { 475 | for _, c := range ca.code { 476 | debugf(" %02x", c) 477 | } 478 | } 479 | debugf("\n") 480 | } 481 | debugf("attributes_count=%d\n", cf.attributes_count) 482 | debugf("attribute=%v\n", cf.attributes[0]) 483 | } 484 | 485 | var stack []interface{} 486 | 487 | func push(e interface{}) { 488 | stack = append(stack, e) 489 | } 490 | 491 | func pop() interface{} { 492 | e := stack[len(stack)-1] 493 | newStack := stack[0:len(stack)-1] 494 | stack = newStack 495 | return e 496 | } 497 | 498 | func executeCode(code []byte, localvars []interface{}) { 499 | debugf("len code=%d\n", len(code)) 500 | 501 | br := &ByteReader{ 502 | bytes: code, 503 | } 504 | for _, b := range code { 505 | debugf("0x%02x ", b) 506 | } 507 | debugf("\n") 508 | 509 | for { 510 | if br.byteIndex >= len(br.bytes) { 511 | break 512 | } 513 | b := br.readU1() 514 | debugf("inst 0x%02x\n", b) 515 | // https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-6.html#jvms-6.5 516 | switch b { 517 | case 0x10: // bipush 518 | operand := br.readU1() 519 | debugf(" bipush 0x%02x\n", operand) 520 | push(operand) 521 | case 0x12: // ldc 522 | operand := br.readU1() 523 | debugf(" ldc 0x%02x\n", operand) 524 | push(operand) 525 | case 0x1a: // iload_0 526 | debugf(" iload_0\n") 527 | arg := localvars[0] 528 | push(arg) 529 | case 0x1b: // iload_1 530 | debugf(" iload_1\n") 531 | arg := localvars[1] 532 | push(arg) 533 | case 0x3c: // istore_1 534 | arg := pop() 535 | debugf(" istore_1\n") 536 | localvars[1] = arg 537 | case 0x60: // iadd 538 | debugf(" iadd\n") 539 | arg0 := pop().(u1) 540 | arg1 := pop().(u1) 541 | ret := arg0 + arg1 542 | debugf(" result=%d\n", ret) 543 | push(ret) 544 | case 0xac: // ireturn 545 | ret := pop().(u1) 546 | rax = ret 547 | return 548 | case 0xb1: // return 549 | debugf(" return\n") 550 | return 551 | case 0xb2: // getstatic 552 | operand := br.readU2() 553 | debugf(" getstatic 0x%02x\n", operand) 554 | fieldRef := cpool.getFieldref(operand) 555 | classInfo := fieldRef.getClassInfo() 556 | name := fieldRef.getNameAndType().getName() 557 | desc := fieldRef.getNameAndType().getDescriptor() 558 | debugf(" => %s#%s#%s#%s\n", c2s(fieldRef), classInfo.getName(), name, desc) 559 | push(operand) 560 | case 0xb6: // invokevirtual 561 | operand := br.readU2() 562 | debugf(" invokevirtual 0x%02x\n", operand) 563 | methodRef := cpool.getMethodref(operand) 564 | methodClassInfo := methodRef.getClassInfo() 565 | methodNameAndType := methodRef.getNameAndType() 566 | methodName := cpool.getUTF8AsString(methodNameAndType.name_index) 567 | debugf(" invoking %s.%s()\n", methodClassInfo.getName(), methodName) // java/lang/System 568 | 569 | // argument info 570 | desc := methodNameAndType.getDescriptor() 571 | var arg0 interface{} 572 | var num_args int 573 | if strings.Contains(desc, ";") { 574 | desc_args := strings.Split(desc, ";") 575 | num_args = len(desc_args) - 1 576 | debugf(" descriptor=%s, num_args=%d\n", desc, num_args) 577 | arg0ifc := pop() 578 | debugf(" arg0ifc=%T, %v\n", arg0ifc, arg0ifc) 579 | arg0id := u2(arg0ifc.(u1)) 580 | arg0c := cpool.getString(u2(arg0id)) 581 | arg0 = arg0c 582 | } else { 583 | num_args = strings.Count(desc, "I") 584 | debugf(" descriptor=%s, num_args=%d\n", desc, num_args) 585 | arg0ifc := pop() 586 | debugf(" arg0ifc=%T, %v\n", arg0ifc, arg0ifc) 587 | arg0 = arg0ifc.(u1) 588 | } 589 | debugf(" arg0=%v\n", arg0) 590 | 591 | // receiverId info 592 | receiverId := pop() 593 | // System.out:PrintStream 594 | fieldRef := cpool.getFieldref(receiverId.(u2)) 595 | fieldClassInfo := fieldRef.getClassInfo() // class java/lang/System 596 | fieldNameAndType := fieldRef.getNameAndType() 597 | fieldName := fieldNameAndType.getName() // out 598 | debugf(" receiver=%s.%s %s\n", fieldClassInfo.getName(), fieldName, fieldNameAndType.getDescriptor()) // java/lang/System.out Ljava/io/PrintStream; 599 | 600 | debugf("[Invoking]\n") 601 | receiver := classMap[fieldClassInfo.getName()].staicfields[fieldName] 602 | method := classMap[methodClassInfo.getName()].methods[methodName] 603 | method(receiver, arg0) 604 | case 0xb8: // invokestatic 605 | // https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-6.html#jvms-6.5.invokestatic 606 | indexbyte1 := br.readU1() 607 | indexbyte2 := br.readU1() 608 | index := (indexbyte1 << 8) | indexbyte2 609 | debugf(" invokestatic 0x%02x, 0x%02x => 0x%02x\n", indexbyte1, indexbyte2, index) 610 | methodRef := cpool.getMethodref(u2(index)) 611 | methodClassInfo := methodRef.getClassInfo() 612 | methodNameAndType := methodRef.getNameAndType() 613 | methodName := cpool.getUTF8AsString(methodNameAndType.name_index) 614 | debugf(" invoking #0x%02x %s.%s()\n", 615 | methodNameAndType.name_index, methodClassInfo.getName(), methodName) // java/lang/System 616 | 617 | // argument info 618 | desc := methodNameAndType.getDescriptor() // (II)I 619 | num_args := 2 // (II) => 2 620 | debugf(" descriptor=%s, num_args=%d\n", desc, num_args) 621 | arg1 := pop().(u1) 622 | arg2 := pop().(u1) 623 | methodInfo,ok := cf.methodMap[methodNameAndType.name_index] 624 | if !ok { 625 | panic("Method not found") 626 | } 627 | params := []interface{}{arg1, arg2} 628 | methodInfo.invoke(params) 629 | debugf("returned back\n") 630 | ret := rax 631 | debugf("sum %d = %d + %d\n", ret, arg1, arg2) 632 | push(ret) 633 | default: 634 | panic(fmt.Sprintf("Unknown instruction: 0x%02X", b)) 635 | } 636 | debugf("# stack=%#v\n", stack) 637 | } 638 | } 639 | 640 | var rax interface{} // to store return value 641 | 642 | var classMap map[string]*JavaClass 643 | 644 | type JavaClass struct { 645 | staicfields map[string]interface{} 646 | methods map[string]func(...interface{}) 647 | } 648 | 649 | type PrintStream struct { 650 | fp *os.File 651 | } 652 | 653 | func (methodInfo MethodInfo) invoke(localvars []interface{}) { 654 | debugf("# in method %s\n", cf.constant_pool.getUTF8AsString(methodInfo.name_index)) 655 | for _, ca := range methodInfo.ai { 656 | executeCode(ca.code, localvars) 657 | debugf("#---\n") 658 | } 659 | } 660 | 661 | var debug bool 662 | 663 | func debugf(format string, args ...interface{}) { 664 | if debug { 665 | fmt.Fprintf(os.Stderr, "# " + format, args...) 666 | } 667 | } 668 | 669 | func initJava() { 670 | classMap = map[string]*JavaClass{ 671 | "java/lang/System" : &JavaClass{ 672 | staicfields: map[string]interface{}{ 673 | "out": &PrintStream{ 674 | fp: os.Stdout, 675 | }, 676 | }, 677 | }, 678 | "java/io/PrintStream": &JavaClass{ 679 | methods: map[string]func(...interface{}){ 680 | "println": func(args ...interface{}) { 681 | ps, ok := args[0].(*PrintStream) 682 | if !ok { 683 | panic("Type mismatch") 684 | } 685 | fmt.Fprintln(ps.fp, args[1]) 686 | }, 687 | }, 688 | }, 689 | } 690 | } 691 | 692 | 693 | func main() { 694 | debug = false 695 | initJava() 696 | cf = parseClassFile("/dev/stdin") 697 | cpool = cf.constant_pool 698 | debugClassFile(cf) 699 | for _, methodInfo := range cf.methods { 700 | methodName := cf.constant_pool.getUTF8AsString(methodInfo.name_index) 701 | if methodName == "main" { 702 | var localvars []interface{} = make([]interface{}, 16) 703 | methodInfo.invoke(localvars) 704 | } 705 | } 706 | } 707 | 708 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | actual=$(cat HelloWorld.class | ./gojvm) 6 | expected="Hello world" 7 | if [[ $actual == $expected ]];then 8 | echo "ok" 9 | else 10 | echo "not ok" 11 | fi 12 | 13 | actual=$(cat Arith.class | ./gojvm) 14 | expected="42" 15 | if [[ $actual == $expected ]];then 16 | echo "ok" 17 | else 18 | echo "not ok" 19 | fi 20 | 21 | echo "All tests passed." 22 | --------------------------------------------------------------------------------