diff --git a/c/class_resolver.h b/c/class_resolver.h index 1815c9d..239f10d 100644 --- a/c/class_resolver.h +++ b/c/class_resolver.h @@ -7,7 +7,10 @@ struct field_entry { struct field_info * field_info; - uint32_t value; + union { + uint64_t value64; + uint32_t value32; + }; }; enum initialization_state { diff --git a/c/execute.c b/c/execute.c index 2ce404d..e73d8d8 100644 --- a/c/execute.c +++ b/c/execute.c @@ -4,6 +4,7 @@ #include "memory_allocator.h" #include "bswap.h" #include "class_resolver.h" +#include "execute_helper.h" void op_aaload(struct vm * vm) { @@ -610,42 +611,55 @@ void op_fsub(struct vm * vm) void op_getfield(struct vm * vm, uint32_t index) { - assert(!"op_getfield"); + struct class_entry * class_entry; + struct field_entry * field_entry; + struct constant * field_descriptor_constant; + class_entry_field_entry_from_constant_index(vm, index, + &class_entry, + &field_entry, + &field_descriptor_constant); + assert(field_descriptor_constant->utf8.length == 1); + + uint32_t field_index = field_entry->field_info - class_entry->class_file->fields; + printf("putfield field_index %d\n", field_index); + + int32_t * objectref = (int32_t *)operand_stack_pop_u32(vm->current_frame); + + switch (field_descriptor_constant->utf8.bytes[0]) { + case 'Z': [[fallthrough]]; + case 'B': [[fallthrough]]; + case 'C': [[fallthrough]]; + case 'S': [[fallthrough]]; + case 'I': [[fallthrough]]; + case 'F': + { + uint32_t value = objectref[field_index]; + operand_stack_push_u32(vm->current_frame, value); + } + break; + case 'D': [[fallthrough]]; + case 'J': + { + uint32_t low = objectref[field_index * 2]; + uint32_t high = objectref[field_index * 2 + 1]; + operand_stack_push_u32(vm->current_frame, low); + operand_stack_push_u32(vm->current_frame, high); + } + break; + default: + assert(false); + } } void op_getstatic(struct vm * vm, uint32_t index) { - struct constant * fieldref_constant = &vm->current_thread.current_class->constant_pool[index - 1]; - #ifdef DEBUG - assert(fieldref_constant->tag == CONSTANT_Fieldref); - #endif - struct constant * class_constant = &vm->current_thread.current_class->constant_pool[fieldref_constant->fieldref.class_index - 1]; - #ifdef DEBUG - assert(class_constant->tag == CONSTANT_Class); - #endif - struct constant * nameandtype_constant = &vm->current_thread.current_class->constant_pool[fieldref_constant->fieldref.name_and_type_index - 1]; - #ifdef DEBUG - assert(nameandtype_constant->tag == CONSTANT_NameAndType); - #endif - struct constant * class_name_constant = &vm->current_thread.current_class->constant_pool[class_constant->class.name_index - 1]; - #ifdef DEBUG - assert(class_name_constant->tag == CONSTANT_Utf8); - #endif - struct constant * field_name_constant = &vm->current_thread.current_class->constant_pool[nameandtype_constant->nameandtype.name_index - 1]; - #ifdef DEBUG - assert(field_name_constant->tag == CONSTANT_Utf8); - #endif - - struct class_entry * class_entry = class_resolver_lookup_class(vm->class_hash_table.length, - vm->class_hash_table.entry, - class_name_constant->utf8.bytes, - class_name_constant->utf8.length); - assert(class_entry != nullptr); - - struct field_entry * field_entry = class_resolver_lookup_field(class_entry, - field_name_constant->utf8.bytes, - field_name_constant->utf8.length); - assert(field_entry != nullptr); + struct class_entry * class_entry; + struct field_entry * field_entry; + struct constant * field_descriptor_constant; + class_entry_field_entry_from_constant_index(vm, index, + &class_entry, + &field_entry, + &field_descriptor_constant); /* On successful resolution of the field, the class or interface that declared the resolved field is initialized if that class or interface has @@ -654,8 +668,30 @@ void op_getstatic(struct vm * vm, uint32_t index) if (!vm_initialize_class(vm, class_entry)) return; - uint32_t value = field_entry->value; - operand_stack_push_u32(vm->current_frame, value); + assert(field_descriptor_constant->utf8.length == 1); + + switch (field_descriptor_constant->utf8.bytes[0]) { + case 'Z': [[fallthrough]]; + case 'B': [[fallthrough]]; + case 'C': [[fallthrough]]; + case 'S': [[fallthrough]]; + case 'I': [[fallthrough]]; + case 'F': + { + uint32_t value = field_entry->value32; + operand_stack_push_u32(vm->current_frame, value); + } + break; + case 'D': [[fallthrough]]; + case 'J': + { + uint64_t value = field_entry->value64; + operand_stack_push_u64(vm->current_frame, value); + } + break; + default: + assert(false); + } } void op_goto(struct vm * vm, int32_t branch) @@ -996,48 +1032,22 @@ void op_invokeinterface(struct vm * vm, uint32_t index, uint32_t count) void op_invokespecial(struct vm * vm, uint32_t index) { - assert(!"op_invokespecial"); + struct class_entry * class_entry; + struct method_info * method_info; + class_entry_method_info_from_constant_index(vm, index, + &class_entry, + &method_info); + + vm_special_method_call(vm, class_entry->class_file, method_info); } void op_invokestatic(struct vm * vm, uint32_t index) { - struct constant * methodref_constant = &vm->current_thread.current_class->constant_pool[index - 1]; - #ifdef DEBUG - assert(methodref_constant->tag == CONSTANT_Methodref); - #endif - struct constant * class_constant = &vm->current_thread.current_class->constant_pool[methodref_constant->methodref.class_index - 1]; - #ifdef DEBUG - assert(class_constant->tag == CONSTANT_Class); - #endif - struct constant * nameandtype_constant = &vm->current_thread.current_class->constant_pool[methodref_constant->methodref.name_and_type_index - 1]; - #ifdef DEBUG - assert(nameandtype_constant->tag == CONSTANT_NameAndType); - #endif - struct constant * class_name_constant = &vm->current_thread.current_class->constant_pool[class_constant->class.name_index - 1]; - #ifdef DEBUG - assert(class_name_constant->tag == CONSTANT_Utf8); - #endif - struct constant * method_name_constant = &vm->current_thread.current_class->constant_pool[nameandtype_constant->nameandtype.name_index - 1]; - #ifdef DEBUG - assert(method_name_constant->tag == CONSTANT_Utf8); - #endif - struct constant * method_descriptor_constant = &vm->current_thread.current_class->constant_pool[nameandtype_constant->nameandtype.descriptor_index - 1]; - #ifdef DEBUG - assert(method_descriptor_constant->tag == CONSTANT_Utf8); - #endif - - struct class_entry * class_entry = class_resolver_lookup_class(vm->class_hash_table.length, - vm->class_hash_table.entry, - class_name_constant->utf8.bytes, - class_name_constant->utf8.length); - assert(class_entry != nullptr); - - struct method_info * method_info = class_resolver_lookup_method(class_entry, - method_name_constant->utf8.bytes, - method_name_constant->utf8.length, - method_descriptor_constant->utf8.bytes, - method_descriptor_constant->utf8.length); - assert(method_info != nullptr); + struct class_entry * class_entry; + struct method_info * method_info; + class_entry_method_info_from_constant_index(vm, index, + &class_entry, + &method_info); /* On successful resolution of the method, the class or interface that declared the resolved method is initialized if that class or interface has @@ -1048,7 +1058,13 @@ void op_invokestatic(struct vm * vm, uint32_t index) void op_invokevirtual(struct vm * vm, uint32_t index) { - assert(!"op_invokevirtual"); + struct class_entry * class_entry; + struct method_info * method_info; + class_entry_method_info_from_constant_index(vm, index, + &class_entry, + &method_info); + + vm_special_method_call(vm, class_entry->class_file, method_info); } void op_ior(struct vm * vm) @@ -1273,7 +1289,7 @@ void op_lconst_1(struct vm * vm) void op_ldc(struct vm * vm, uint32_t index) { - struct constant * constant = &vm->current_thread.current_class->constant_pool[index - 1]; + struct constant * constant = &vm->current_frame->class->constant_pool[index - 1]; #ifdef DEBUG assert(constant->tag == CONSTANT_Integer || constant->tag == CONSTANT_Float); #endif @@ -1283,7 +1299,7 @@ void op_ldc(struct vm * vm, uint32_t index) void op_ldc2_w(struct vm * vm, uint32_t index) { - struct constant * constant = &vm->current_thread.current_class->constant_pool[index - 1]; + struct constant * constant = &vm->current_frame->class->constant_pool[index - 1]; #ifdef DEBUG assert(constant->tag == CONSTANT_Long || constant->tag == CONSTANT_Double); #endif @@ -1486,11 +1502,11 @@ void op_multianewarray(struct vm * vm, uint32_t index, uint32_t dimensions) void op_new(struct vm * vm, uint32_t index) { - struct constant * class_constant = &vm->current_thread.current_class->constant_pool[index - 1]; + struct constant * class_constant = &vm->current_frame->class->constant_pool[index - 1]; #ifdef DEBUG assert(class_constant->tag == CONSTANT_Class); #endif - struct constant * class_name_constant = &vm->current_thread.current_class->constant_pool[class_constant->class.name_index - 1]; + struct constant * class_name_constant = &vm->current_frame->class->constant_pool[class_constant->class.name_index - 1]; #ifdef DEBUG assert(class_name_constant->tag == CONSTANT_Utf8); #endif @@ -1517,9 +1533,13 @@ void op_new(struct vm * vm, uint32_t index) int fields_count = class_entry->class_file->fields_count; (void)fields_count; - //int32_t * arrayref = memory_allocate(); + int32_t * objectref = memory_allocate(fields_count * 2 * 4); + for (int i = 0; i < fields_count; i++) { + objectref[i * 2] = 0; + objectref[i * 2 + 1] = 0; + } - assert(!"op_new"); + operand_stack_push_u32(vm->current_frame, (uint32_t)objectref); } enum ARRAY_TYPE { @@ -1594,42 +1614,72 @@ void op_pop2(struct vm * vm) void op_putfield(struct vm * vm, uint32_t index) { - assert(!"op_putfield"); + struct class_entry * class_entry; + struct field_entry * field_entry; + struct constant * field_descriptor_constant; + class_entry_field_entry_from_constant_index(vm, index, + &class_entry, + &field_entry, + &field_descriptor_constant); + + /* The type of a value stored by a putfield instruction must be compatible + with the descriptor of the referenced field (§4.3.2). If the field descriptor + type is boolean, byte, char, short, or int, then the value must be an int. If + the field descriptor type is float, long, or double, then the value must be a + float, long, or double, respectively. If the field descriptor type is a class + type or an array type, then the value must be a value of the field descriptor + type. */ + + assert(field_descriptor_constant->utf8.length == 1); + + uint32_t field_index = field_entry->field_info - class_entry->class_file->fields; + printf("putfield field_index %d\n", field_index); + + switch (field_descriptor_constant->utf8.bytes[0]) { + case 'Z': [[fallthrough]]; + case 'B': [[fallthrough]]; + case 'C': [[fallthrough]]; + case 'S': [[fallthrough]]; + case 'I': [[fallthrough]]; + case 'F': + { + uint32_t value = operand_stack_pop_u32(vm->current_frame); + int32_t * objectref = (int32_t *)operand_stack_pop_u32(vm->current_frame); + objectref[field_index] = value; + } + break; + case 'D': [[fallthrough]]; + case 'J': + { + uint32_t high = operand_stack_pop_u32(vm->current_frame); + uint32_t low = operand_stack_pop_u32(vm->current_frame); + int32_t * objectref = (int32_t *)operand_stack_pop_u32(vm->current_frame); + objectref[field_index * 2 + 1] = high; + objectref[field_index * 2] = low; + } + break; + default: + assert(false); + } } void op_putstatic(struct vm * vm, uint32_t index) { - struct constant * fieldref_constant = &vm->current_thread.current_class->constant_pool[index - 1]; - #ifdef DEBUG - assert(fieldref_constant->tag == CONSTANT_Fieldref); - #endif - struct constant * class_constant = &vm->current_thread.current_class->constant_pool[fieldref_constant->fieldref.class_index - 1]; - #ifdef DEBUG - assert(class_constant->tag == CONSTANT_Class); - #endif - struct constant * nameandtype_constant = &vm->current_thread.current_class->constant_pool[fieldref_constant->fieldref.name_and_type_index - 1]; - #ifdef DEBUG - assert(nameandtype_constant->tag == CONSTANT_NameAndType); - #endif - struct constant * class_name_constant = &vm->current_thread.current_class->constant_pool[class_constant->class.name_index - 1]; - #ifdef DEBUG - assert(class_name_constant->tag == CONSTANT_Utf8); - #endif - struct constant * field_name_constant = &vm->current_thread.current_class->constant_pool[nameandtype_constant->nameandtype.name_index - 1]; - #ifdef DEBUG - assert(field_name_constant->tag == CONSTANT_Utf8); - #endif + struct class_entry * class_entry; + struct field_entry * field_entry; + struct constant * field_descriptor_constant; + class_entry_field_entry_from_constant_index(vm, index, + &class_entry, + &field_entry, + &field_descriptor_constant); - struct class_entry * class_entry = class_resolver_lookup_class(vm->class_hash_table.length, - vm->class_hash_table.entry, - class_name_constant->utf8.bytes, - class_name_constant->utf8.length); - assert(class_entry != nullptr); - - struct field_entry * field_entry = class_resolver_lookup_field(class_entry, - field_name_constant->utf8.bytes, - field_name_constant->utf8.length); - assert(field_entry != nullptr); + /* The type of a value stored by a putstatic instruction must be compatible + with the descriptor of the referenced field (§4.3.2). If the field + descriptor type is boolean, byte, char, short, or int, then the value must + be an int. If the field descriptor type is float, long, or double, then the + value must be a float, long, or double, respectively. If the field + descriptor type is a class type or an array type, then the value must be a + value of the field descriptor type. */ /* On successful resolution of the field, the class or interface that declared the resolved field is initialized if that class or interface has not @@ -1638,8 +1688,30 @@ void op_putstatic(struct vm * vm, uint32_t index) if (!vm_initialize_class(vm, class_entry)) return; - uint32_t value = operand_stack_pop_u32(vm->current_frame); - field_entry->value = value; + assert(field_descriptor_constant->utf8.length == 1); + + switch (field_descriptor_constant->utf8.bytes[0]) { + case 'Z': [[fallthrough]]; + case 'B': [[fallthrough]]; + case 'C': [[fallthrough]]; + case 'S': [[fallthrough]]; + case 'I': [[fallthrough]]; + case 'F': + { + uint32_t value = operand_stack_pop_u32(vm->current_frame); + field_entry->value32 = value; + } + break; + case 'D': [[fallthrough]]; + case 'J': + { + uint64_t value = operand_stack_pop_u64(vm->current_frame); + field_entry->value64 = value; + } + break; + default: + assert(false); + } } void op_ret(struct vm * vm, uint32_t index) diff --git a/c/execute_helper.h b/c/execute_helper.h new file mode 100644 index 0000000..a42595c --- /dev/null +++ b/c/execute_helper.h @@ -0,0 +1,88 @@ +#include "debug_class_file.h" + +static inline void class_entry_field_entry_from_constant_index(struct vm * vm, + uint32_t index, + struct class_entry ** class_entry, + struct field_entry ** field_entry, + struct constant ** field_descriptor_constant) +{ + struct constant * fieldref_constant = &vm->current_frame->class->constant_pool[index - 1]; + #ifdef DEBUG + assert(fieldref_constant->tag == CONSTANT_Fieldref); + #endif + struct constant * class_constant = &vm->current_frame->class->constant_pool[fieldref_constant->fieldref.class_index - 1]; + #ifdef DEBUG + assert(class_constant->tag == CONSTANT_Class); + #endif + struct constant * nameandtype_constant = &vm->current_frame->class->constant_pool[fieldref_constant->fieldref.name_and_type_index - 1]; + #ifdef DEBUG + assert(nameandtype_constant->tag == CONSTANT_NameAndType); + #endif + struct constant * class_name_constant = &vm->current_frame->class->constant_pool[class_constant->class.name_index - 1]; + #ifdef DEBUG + assert(class_name_constant->tag == CONSTANT_Utf8); + #endif + struct constant * field_name_constant = &vm->current_frame->class->constant_pool[nameandtype_constant->nameandtype.name_index - 1]; + #ifdef DEBUG + assert(field_name_constant->tag == CONSTANT_Utf8); + #endif + *field_descriptor_constant = &vm->current_frame->class->constant_pool[nameandtype_constant->nameandtype.descriptor_index - 1]; + #ifdef DEBUG + assert((*field_descriptor_constant)->tag == CONSTANT_Utf8); + #endif + + *class_entry = class_resolver_lookup_class(vm->class_hash_table.length, + vm->class_hash_table.entry, + class_name_constant->utf8.bytes, + class_name_constant->utf8.length); + assert(*class_entry != nullptr); + + *field_entry = class_resolver_lookup_field(*class_entry, + field_name_constant->utf8.bytes, + field_name_constant->utf8.length); + assert(*field_entry != nullptr); +} + +static inline void class_entry_method_info_from_constant_index(struct vm * vm, + uint32_t index, + struct class_entry ** class_entry, + struct method_info ** method_info) +{ + struct constant * methodref_constant = &vm->current_frame->class->constant_pool[index - 1]; + #ifdef DEBUG + assert(methodref_constant->tag == CONSTANT_Methodref); + #endif + struct constant * class_constant = &vm->current_frame->class->constant_pool[methodref_constant->methodref.class_index - 1]; + #ifdef DEBUG + assert(class_constant->tag == CONSTANT_Class); + #endif + struct constant * nameandtype_constant = &vm->current_frame->class->constant_pool[methodref_constant->methodref.name_and_type_index - 1]; + #ifdef DEBUG + assert(nameandtype_constant->tag == CONSTANT_NameAndType); + #endif + struct constant * class_name_constant = &vm->current_frame->class->constant_pool[class_constant->class.name_index - 1]; + #ifdef DEBUG + assert(class_name_constant->tag == CONSTANT_Utf8); + #endif + struct constant * method_name_constant = &vm->current_frame->class->constant_pool[nameandtype_constant->nameandtype.name_index - 1]; + #ifdef DEBUG + assert(method_name_constant->tag == CONSTANT_Utf8); + #endif + struct constant * method_descriptor_constant = &vm->current_frame->class->constant_pool[nameandtype_constant->nameandtype.descriptor_index - 1]; + #ifdef DEBUG + assert(method_descriptor_constant->tag == CONSTANT_Utf8); + #endif + + *class_entry = class_resolver_lookup_class(vm->class_hash_table.length, + vm->class_hash_table.entry, + class_name_constant->utf8.bytes, + class_name_constant->utf8.length); + assert(*class_entry != nullptr); + + *method_info = class_resolver_lookup_method(*class_entry, + method_name_constant->utf8.bytes, + method_name_constant->utf8.length, + method_descriptor_constant->utf8.bytes, + method_descriptor_constant->utf8.length); + assert(*method_info != nullptr); +} diff --git a/c/frame.c b/c/frame.c index 2533041..a555ec0 100644 --- a/c/frame.c +++ b/c/frame.c @@ -87,7 +87,7 @@ bool vm_initialize_class(struct vm * vm, struct class_entry * class_entry) return true; if (class_entry->initialization_state == CLASS_INITIALIZING) { - if (vm->current_thread.current_class == class_entry->class_file) + if (vm->current_frame->class == class_entry->class_file) return true; else assert(false); // possible infinite initialization loop @@ -154,6 +154,52 @@ bool vm_initialize_class(struct vm * vm, struct class_entry * class_entry) return true; } +void vm_special_method_call(struct vm * vm, struct class_file * class_file, struct method_info * method_info) +{ + /* If the method is not native, the nargs argument values are popped from the + operand stack. A new frame is created on the Java Virtual Machine stack for + the method being invoked. The nargs argument values are consecutively made + the values of local variables of the new frame, with arg1 in local variable + 0 (or, if arg1 is of type long or double, in local variables 0 and 1) and + so on. The new frame is then made current, and the Java Virtual Machine pc + is set to the opcode of the first instruction of the method to be + invoked. Execution continues with the first instruction of the method. + */ + + int code_name_index = find_code_name_index(class_file); + assert(code_name_index > 0); + + struct Code_attribute * code = get_code_attribute(code_name_index, + method_info->attributes_count, + method_info->attributes); + assert(code != nullptr); + + struct frame * old_frame = vm->current_frame; + + vm->current_frame = stack_push_frame(&vm->frame_stack, 1); + vm->current_frame->code = code; + vm->current_frame->local_variable = stack_push_data(&vm->data_stack, code->max_locals); + vm->current_frame->operand_stack = stack_push_data(&vm->data_stack, code->max_stack); + vm->current_frame->operand_stack_ix = 0; + + struct constant * descriptor_constant = &class_file->constant_pool[method_info->descriptor_index - 1]; + int nargs = descriptor_nargs(descriptor_constant); + nargs += 1; + printf("nargs+1: %d\n", nargs); + for (int i = 0; i < nargs; i++) { + uint32_t value = operand_stack_pop_u32(old_frame); + printf("local[%d] = %x\n", nargs - i - 1, value); + vm->current_frame->local_variable[nargs - i - 1] = value; + } + vm->current_frame->return_type = descriptor_constant->utf8.bytes[descriptor_constant->utf8.length - 1]; + + vm->current_frame->pc = 0; + vm->current_frame->class = class_file; + vm->current_frame->method = method_info; + + printf("operand_stack_ix: %d\n", vm->current_frame->operand_stack_ix); +} + void vm_static_method_call(struct vm * vm, struct class_file * class_file, struct method_info * method_info) { /* If the method is not native, the nargs argument values are popped from the @@ -193,8 +239,8 @@ void vm_static_method_call(struct vm * vm, struct class_file * class_file, struc vm->current_frame->return_type = descriptor_constant->utf8.bytes[descriptor_constant->utf8.length - 1]; vm->current_frame->pc = 0; - vm->current_thread.current_class = class_file; - vm->current_thread.current_method = method_info; + vm->current_frame->class = class_file; + vm->current_frame->method = method_info; printf("operand_stack_ix: %d\n", vm->current_frame->operand_stack_ix); } @@ -261,6 +307,7 @@ void vm_method_return(struct vm * vm) break; } assert(old_frame->operand_stack_ix == 0); + printf("vm_method_return\n"); } static void print_vm_stack(struct vm * vm) @@ -287,13 +334,13 @@ void vm_execute(struct vm * vm) print_vm_stack(vm); decode_print_instruction(vm->current_frame->code->code, vm->current_frame->pc); uint32_t old_pc = vm->current_frame->pc; - struct method_info * old_method = vm->current_thread.current_method; + struct method_info * old_method = vm->current_frame->method; decode_execute_instruction(vm, vm->current_frame->code->code, vm->current_frame->pc); if (vm->frame_stack.ix == 1) { printf("terminate\n"); break; } - if (vm->current_thread.current_method == old_method && vm->current_frame->pc == old_pc) { + if (vm->current_frame->method == old_method && vm->current_frame->pc == old_pc) { // if the instruction did not branch, increment pc vm->current_frame->pc = vm->current_frame->next_pc; } diff --git a/c/frame.h b/c/frame.h index 88b0b0a..b9788d5 100644 --- a/c/frame.h +++ b/c/frame.h @@ -6,6 +6,8 @@ #include "class_resolver.h" struct frame { + struct class_file * class; + struct method_info * method; struct Code_attribute * code; uint32_t * local_variable; uint32_t * operand_stack; @@ -15,11 +17,6 @@ struct frame { uint8_t return_type; }; -struct thread { - struct class_file * current_class; - struct method_info * current_method; -}; - struct stack { union { struct frame * frame; @@ -32,7 +29,6 @@ struct stack { struct vm { struct stack frame_stack; struct stack data_stack; - struct thread current_thread; struct frame * current_frame; struct { int length; @@ -148,6 +144,7 @@ static inline double operand_stack_pop_f64(struct frame * frame) } bool vm_initialize_class(struct vm * vm, struct class_entry * class_entry); +void vm_special_method_call(struct vm * vm, struct class_file * class_file, struct method_info * method_info); void vm_static_method_call(struct vm * vm, struct class_file * class_file, struct method_info * method_info); void vm_method_return(struct vm * vm); void vm_execute(struct vm * vm); diff --git a/java/lang/Object.java b/java/lang/Object.java new file mode 100644 index 0000000..3677589 --- /dev/null +++ b/java/lang/Object.java @@ -0,0 +1,5 @@ +package java.lang; + +class Object { + public Object() {} +} diff --git a/p/LongStaticField.java b/p/LongStaticField.java new file mode 100644 index 0000000..16ca318 --- /dev/null +++ b/p/LongStaticField.java @@ -0,0 +1,13 @@ +package p; + +class LongStaticField { + static long foo = 10; + + static long test() { + return foo; + } + + public static void main() { + test(); + } +}