| /* |
| * Copyright (c) 2008 Saeed Siam |
| * Copyright (c) 2009 Vegard Nossum |
| * |
| * This file is released under the GPL version 2 with the following |
| * clarification and special exception: |
| * |
| * Linking this library statically or dynamically with other modules is |
| * making a combined work based on this library. Thus, the terms and |
| * conditions of the GNU General Public License cover the whole |
| * combination. |
| * |
| * As a special exception, the copyright holders of this library give you |
| * permission to link this library with independent modules to produce an |
| * executable, regardless of the license terms of these independent |
| * modules, and to copy and distribute the resulting executable under terms |
| * of your choice, provided that you also meet, for each linked independent |
| * module, the terms and conditions of the license of that module. An |
| * independent module is a module which is not derived from or based on |
| * this library. If you modify this library, you may extend this exception |
| * to your version of the library, but you are not obligated to do so. If |
| * you do not wish to do so, delete this exception statement from your |
| * version. |
| * |
| * Please refer to the file LICENSE for details. |
| */ |
| |
| #include "vm/class.h" |
| |
| #include "cafebabe/enclosing_method_attribute.h" |
| #include "cafebabe/inner_classes_attribute.h" |
| #include "cafebabe/annotations_attribute.h" |
| #include "cafebabe/constant_pool.h" |
| #include "cafebabe/method_info.h" |
| #include "cafebabe/field_info.h" |
| #include "cafebabe/stream.h" |
| #include "cafebabe/class.h" |
| |
| #include "jit/exception.h" |
| #include "jit/compiler.h" |
| #include "jit/vtable.h" |
| #include "jit/cu-mapping.h" |
| |
| #include "vm/fault-inject.h" |
| #include "vm/classloader.h" |
| #include "vm/annotation.h" |
| #include "vm/gc.h" |
| #include "vm/preload.h" |
| #include "vm/errors.h" |
| #include "vm/itable.h" |
| #include "vm/method.h" |
| #include "vm/object.h" |
| #include "vm/stdlib.h" |
| #include "vm/thread.h" |
| #include "vm/field.h" |
| #include "vm/die.h" |
| #include "vm/vm.h" |
| #include "vm/trace.h" |
| |
| #include "lib/string.h" |
| #include "lib/array.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| |
| bool opt_trace_vtable; |
| |
| static void |
| trace_vtable(struct vm_class *vmc) |
| { |
| struct vtable *vtable = &(vmc->vtable); |
| struct compilation_unit *cu; |
| struct vm_method *vmm; |
| |
| trace_printf("trace vtable: %s\n", vmc->name); |
| |
| for (uint16_t i = 0; i < vmc->vtable_size; ++i) { |
| cu = jit_lookup_cu((long unsigned int)vtable->native_ptr[i]); |
| if (cu) { |
| vmm = cu->method; |
| trace_printf(" vtable[%d] = %s.%s%s\n", i, vmm->class->name, |
| vmm->name, vmm->type); |
| } |
| } |
| |
| trace_flush(); |
| } |
| |
| |
| static void |
| setup_vtable(struct vm_class *vmc) |
| { |
| struct vm_class *super; |
| unsigned int super_vtable_size; |
| struct vtable *super_vtable; |
| unsigned int vtable_size; |
| |
| super = vmc->super; |
| |
| if (super) { |
| super_vtable_size = super->vtable_size; |
| super_vtable = &super->vtable; |
| } else { |
| super_vtable_size = 0; |
| super_vtable = NULL; |
| } |
| |
| vtable_size = 0; |
| for (uint16_t i = 0; i < vmc->nr_methods; ++i) { |
| struct vm_method *vmm = &vmc->methods[i]; |
| |
| if (super) { |
| struct vm_method *vmm2 |
| = vm_class_get_method_recursive(super, |
| vmm->name, vmm->type); |
| if (vmm2) { |
| vmm->virtual_index = vmm2->virtual_index; |
| continue; |
| } |
| } |
| |
| vmm->virtual_index = super_vtable_size + vtable_size; |
| ++vtable_size; |
| } |
| |
| vmc->vtable_size = super_vtable_size + vtable_size; |
| |
| vtable_init(&vmc->vtable, vmc->vtable_size); |
| |
| /* Superclass methods */ |
| for (uint16_t i = 0; i < super_vtable_size; ++i) |
| vtable_setup_method(&vmc->vtable, i, |
| super_vtable->native_ptr[i]); |
| |
| /* Our methods */ |
| for (uint16_t i = 0; i < vmc->nr_methods; ++i) { |
| struct vm_method *vmm = &vmc->methods[i]; |
| |
| vtable_setup_method(&vmc->vtable, |
| vmm->virtual_index, |
| vm_method_call_ptr(vmm)); |
| } |
| |
| if (opt_trace_vtable) |
| trace_vtable(vmc); |
| } |
| |
| /* |
| * Set the .object member of struct vm_class to point to the object |
| * of type java.lang.Class for this class. |
| */ |
| int vm_class_setup_object(struct vm_class *vmc) |
| { |
| vmc->object = vm_object_alloc(vm_java_lang_Class); |
| if (!vmc->object) { |
| throw_oom_error(); |
| return -1; |
| } |
| |
| field_set_object(vmc->object, vm_java_lang_Class_vmdata, |
| (struct vm_object *)vmc); |
| return 0; |
| } |
| |
| static int vm_class_link_common(struct vm_class *vmc) |
| { |
| int err; |
| |
| compile_lock_init(&vmc->cl, true); |
| |
| err = pthread_mutex_init(&vmc->mutex, NULL); |
| if (err) |
| return -err; |
| |
| if (preload_finished) |
| return vm_class_setup_object(vmc); |
| |
| /* If classes and fields were not preloaded yet then we can't |
| * allocate object for this class. Set .object to a dummy object |
| * for class synchronization to work. This will be replaced |
| * after preloading is finished. */ |
| static struct vm_object dummy_object; |
| vmc->object = &dummy_object; |
| |
| return vm_preload_add_class_fixup(vmc); |
| } |
| |
| /* |
| * This is used for grouping fields by their type, so that we can: |
| * |
| * 1. put reference types first (improves GC) |
| * 2. sort the rest of the fields by size (improves object layout) |
| */ |
| struct field_bucket { |
| unsigned int nr; |
| struct vm_field **fields; |
| }; |
| |
| static void bucket_order_fields(struct field_bucket *bucket, |
| unsigned int size, unsigned int *offset) |
| { |
| unsigned int tmp_offset = *offset; |
| |
| for (unsigned int i = 0; i < bucket->nr; ++i) { |
| struct vm_field *vmf = bucket->fields[i]; |
| |
| vmf->offset = tmp_offset; |
| tmp_offset += size; |
| } |
| |
| *offset = tmp_offset; |
| } |
| |
| static void buckets_order_fields(struct field_bucket buckets[VM_TYPE_MAX], |
| unsigned int *ref_size, unsigned int *size) |
| { |
| unsigned int offset = *size; |
| |
| /* We need to align here, because offset might be non-zero from the |
| * parent class. */ |
| offset = ALIGN(offset, sizeof(void *)); |
| bucket_order_fields(&buckets[J_REFERENCE], sizeof(void *), &offset); |
| |
| /* Align with 8-byte boundary here. We don't need to align anything |
| * after this, since we _know_ e.g. that after 8-byte fields, we will |
| * always be 8-byte aligned, which is also always 4-byte aligned. */ |
| offset = ALIGN(offset, 8); |
| |
| bucket_order_fields(&buckets[J_DOUBLE], 8, &offset); |
| bucket_order_fields(&buckets[J_LONG], 8, &offset); |
| |
| bucket_order_fields(&buckets[J_FLOAT], 4, &offset); |
| bucket_order_fields(&buckets[J_INT], 4, &offset); |
| |
| /* XXX: Should be size = 2 */ |
| bucket_order_fields(&buckets[J_SHORT], 4, &offset); |
| bucket_order_fields(&buckets[J_CHAR], 4, &offset); |
| |
| /* XXX: Should be size = 1 */ |
| bucket_order_fields(&buckets[J_BYTE], 4, &offset); |
| bucket_order_fields(&buckets[J_BOOLEAN], 4, &offset); |
| |
| *size = offset; |
| } |
| |
| static int insert_interface_method(struct vm_class *vmc, |
| struct array *extra_methods, |
| struct vm_method *vmm) |
| { |
| /* We need this "manual" recursive lookup because we haven't |
| * initialized this class' list of methods yet... */ |
| unsigned int idx = 0; |
| if (!cafebabe_class_get_method(vmc->class, vmm->name, vmm->type, &idx)) |
| return 0; |
| |
| if (!vmc->super) |
| return 0; |
| if (vm_class_get_method_recursive(vmc->super, vmm->name, vmm->type)) |
| return 0; |
| |
| return array_append(extra_methods, vmm); |
| } |
| |
| static int compare_method_signatures(const void *a, const void *b) |
| { |
| const struct vm_method *x = *(const struct vm_method **) a; |
| const struct vm_method *y = *(const struct vm_method **) b; |
| |
| int name = strcmp(x->name, y->name); |
| if (name) |
| return name; |
| |
| int type = strcmp(x->type, y->type); |
| if (type) |
| return type; |
| |
| return 0; |
| } |
| |
| static void free_buckets(int rows, int cols, struct field_bucket field_buckets[rows][cols]) |
| { |
| for (int i = 0; i < cols; ++i) { |
| for (int j = 0; j < rows; ++j) { |
| struct field_bucket *bucket = &field_buckets[j][i]; |
| |
| vm_free(bucket->fields); |
| } |
| } |
| } |
| |
| int vm_class_link(struct vm_class *vmc, const struct cafebabe_class *class) |
| { |
| const struct cafebabe_constant_info_class *constant_class; |
| const struct cafebabe_constant_info_utf8 *name; |
| unsigned int nr_inner_classes; |
| |
| vmc->class = class; |
| vmc->kind = VM_CLASS_KIND_REGULAR; |
| |
| if (vm_class_link_common(vmc)) |
| return -1; |
| |
| if (cafebabe_class_constant_get_class(class, class->this_class, &constant_class)) |
| return -1; |
| |
| if (cafebabe_class_constant_get_utf8(class, constant_class->name_index, &name)) |
| return -1; |
| |
| vmc->name = strndup((char *) name->bytes, name->length); |
| |
| vmc->access_flags = class->access_flags; |
| |
| vmc->source_file_name = cafebabe_class_get_source_file_name(class); |
| |
| if (class->super_class) { |
| const struct cafebabe_constant_info_class *constant_super; |
| const struct cafebabe_constant_info_utf8 *super_name; |
| |
| if (cafebabe_class_constant_get_class(class, class->super_class, &constant_super)) |
| goto error_free_name; |
| |
| if (cafebabe_class_constant_get_utf8(class, constant_super->name_index, &super_name)) |
| goto error_free_name; |
| |
| char *super_name_str = strndup((char *) super_name->bytes, super_name->length); |
| |
| /* XXX: Circularity check */ |
| vmc->super = classloader_load(vmc->classloader, super_name_str); |
| free(super_name_str); |
| |
| if (!vmc->super) |
| goto error_free_name; |
| } else { |
| if (!strcmp(vmc->name, "java.lang.Object")) |
| goto error_free_name; |
| |
| vmc->super = NULL; |
| } |
| |
| vmc->nr_interfaces = class->interfaces_count; |
| vmc->interfaces = malloc(sizeof(*vmc->interfaces) * vmc->nr_interfaces); |
| if (!vmc->interfaces) |
| goto error_free_name; |
| |
| for (unsigned int i = 0; i < class->interfaces_count; ++i) { |
| const struct cafebabe_constant_info_class *interface; |
| const struct cafebabe_constant_info_utf8 *name; |
| uint16_t idx = class->interfaces[i]; |
| |
| if (cafebabe_class_constant_get_class(class, idx, &interface)) |
| goto error_free_interfaces; |
| |
| if (cafebabe_class_constant_get_utf8(class, interface->name_index, &name)) |
| goto error_free_interfaces; |
| |
| char *c_name = strndup((char *) name->bytes, name->length); |
| if (!c_name) |
| goto error_free_interfaces; |
| |
| struct vm_class *vmi = classloader_load(vmc->classloader, c_name); |
| free(c_name); |
| if (!vmi) |
| goto error_free_interfaces; |
| |
| vmc->interfaces[i] = vmi; |
| } |
| |
| vmc->nr_fields = class->fields_count; |
| vmc->fields = vm_alloc(sizeof(*vmc->fields) * class->fields_count); |
| if (!vmc->fields) |
| goto error_free_interfaces; |
| |
| for (uint16_t i = 0; i < vmc->nr_fields; ++i) { |
| struct vm_field *vmf = &vmc->fields[i]; |
| |
| if (vm_field_init(vmf, vmc, i)) |
| goto error_free_fields; |
| } |
| |
| if (vmc->super) { |
| vmc->static_size = vmc->super->static_size; |
| vmc->object_size = vmc->super->object_size; |
| } else { |
| vmc->static_size = 0; |
| vmc->object_size = 0; |
| } |
| |
| struct field_bucket field_buckets[2][VM_TYPE_MAX]; |
| for (unsigned int i = 0; i < VM_TYPE_MAX; ++i) { |
| field_buckets[0][i].nr = 0; |
| field_buckets[1][i].nr = 0; |
| } |
| |
| /* Count the number of fields for each bucket */ |
| for (uint16_t i = 0; i < class->fields_count; ++i) { |
| struct vm_field *vmf = &vmc->fields[i]; |
| unsigned int stat = vm_field_is_static(vmf) ? 0 : 1; |
| enum vm_type type = vmf->type_info.vm_type; |
| struct field_bucket *bucket = &field_buckets[stat][type]; |
| |
| ++bucket->nr; |
| } |
| |
| /* Allocate enough space in each bucket */ |
| for (unsigned int i = 0; i < VM_TYPE_MAX; ++i) { |
| for (unsigned int j = 0; j < 2; ++j) { |
| struct field_bucket *bucket = &field_buckets[j][i]; |
| |
| bucket->fields = vm_alloc(bucket->nr * sizeof(*bucket->fields)); |
| bucket->nr = 0; |
| } |
| } |
| |
| /* Place the fields in the buckets */ |
| for (uint16_t i = 0; i < class->fields_count; ++i) { |
| struct vm_field *vmf = &vmc->fields[i]; |
| unsigned int stat = vm_field_is_static(vmf) ? 0 : 1; |
| enum vm_type type = vmf->type_info.vm_type; |
| struct field_bucket *bucket = &field_buckets[stat][type]; |
| |
| bucket->fields[bucket->nr++] = vmf; |
| } |
| |
| unsigned int tmp; |
| buckets_order_fields(field_buckets[0], &tmp, &vmc->static_size); |
| buckets_order_fields(field_buckets[1], &tmp, &vmc->object_size); |
| |
| /* XXX: only static fields, right size, etc. */ |
| vmc->static_values = vm_zalloc(vmc->static_size); |
| if (!vmc->static_values) |
| goto error_free_buckets; |
| |
| for (uint16_t i = 0; i < vmc->nr_fields; ++i) { |
| struct vm_field *vmf = &vmc->fields[i]; |
| |
| if (vm_field_is_static(vmf)) { |
| if (vm_field_init_static(vmf)) |
| goto error_free_static_values; |
| } else { |
| vm_field_init_nonstatic(vmf); |
| } |
| } |
| |
| struct array extra_methods; |
| array_init(&extra_methods); |
| |
| /* The array is temporary anyway, so there's no harm in allocating a |
| * bit more just in case. If it's too little, the array will expand. */ |
| array_resize(&extra_methods, 64); |
| |
| /* If in any of the superinterfaces we find a method which is not |
| * defined in this class file, we need to add a "miranda" method. |
| * Note that we don't need to do this recursively for all super- |
| * interfaces because they will have already done this very same |
| * procedure themselves. */ |
| for (unsigned int i = 0; i < class->interfaces_count; ++i) { |
| struct vm_class *vmi = vmc->interfaces[i]; |
| |
| for (unsigned int j = 0; j < vmi->nr_methods; ++j) { |
| struct vm_method *vmm = &vmi->methods[j]; |
| int err; |
| |
| err = insert_interface_method(vmc, &extra_methods, vmm); |
| if (err) |
| goto error_free_static_values; |
| } |
| } |
| |
| /* We need to weed out duplicate signatures in order to avoid a |
| * situation where two interfaces define the same method and a class |
| * implements both interfaces. We shouldn't add two methods with the |
| * same signature to the same class. */ |
| array_qsort(&extra_methods, &compare_method_signatures); |
| array_unique(&extra_methods, &compare_method_signatures); |
| |
| vmc->nr_methods = class->methods_count + extra_methods.size; |
| |
| vmc->methods = vm_alloc(sizeof(*vmc->methods) * vmc->nr_methods); |
| if (!vmc->methods) |
| goto error_free_static_values; |
| |
| for (uint16_t i = 0; i < class->methods_count; ++i) { |
| struct vm_method *vmm = &vmc->methods[i]; |
| |
| if (vm_method_init(vmm, vmc, i)) |
| goto error_free_methods; |
| } |
| |
| for (unsigned int i = 0; i < extra_methods.size; ++i) { |
| struct vm_method *vmm = &vmc->methods[class->methods_count + i]; |
| |
| if (vm_method_init_from_interface(vmm, vmc, class->methods_count + i, extra_methods.ptr[i])) |
| goto error_free_methods; |
| } |
| |
| for (uint16_t i = 0; i < vmc->nr_methods; ++i) { |
| struct vm_method *vmm = &vmc->methods[i]; |
| |
| vmm->itable_index = itable_hash(vmm); |
| |
| if (vm_method_prepare_jit(vmm)) |
| goto error_free_methods; |
| } |
| |
| array_destroy(&extra_methods); |
| |
| if (!vm_class_is_interface(vmc)) { |
| setup_vtable(vmc); |
| |
| if (!vm_class_is_abstract(vmc)) |
| vm_itable_setup(vmc); |
| } |
| |
| INIT_LIST_HEAD(&vmc->static_fixup_site_list); |
| |
| struct cafebabe_inner_classes_attribute inner_classes_attribute; |
| |
| memset(&inner_classes_attribute, 0, sizeof(inner_classes_attribute)); |
| if (cafebabe_read_inner_classes_attribute(class, &class->attributes, &inner_classes_attribute)) |
| return -1; |
| |
| nr_inner_classes = 0; |
| for (unsigned int i = 0; i < inner_classes_attribute.number_of_classes; i++) { |
| struct cafebabe_inner_class *inner = &inner_classes_attribute.inner_classes[i]; |
| |
| if (class->this_class == inner->outer_class_info_index) |
| nr_inner_classes++; |
| } |
| |
| vmc->inner_classes = vm_alloc(sizeof(*vmc->inner_classes) * nr_inner_classes); |
| if (!vmc->inner_classes) |
| goto error_free_methods; |
| |
| for (unsigned int i = 0; i < inner_classes_attribute.number_of_classes; i++) { |
| struct cafebabe_inner_class *inner = &inner_classes_attribute.inner_classes[i]; |
| |
| if (class->this_class == inner->inner_class_info_index) { |
| vmc->declaring_class = vmc->enclosing_class = vm_class_resolve_class(vmc, inner->outer_class_info_index); |
| |
| vmc->inner_class_access_flags = inner->inner_class_access_flags; |
| } else if (class->this_class == inner->outer_class_info_index) { |
| vmc->inner_classes[vmc->nr_inner_classes++] = inner->inner_class_info_index; |
| } |
| } |
| |
| struct cafebabe_annotations_attribute annotations_attribute; |
| |
| if (cafebabe_read_annotations_attribute(class, &class->attributes, &annotations_attribute)) |
| goto error_free_inner_classes; |
| |
| vmc->annotations = vm_alloc(sizeof(struct vm_annotation *) * annotations_attribute.num_annotations); |
| if (!vmc->annotations) |
| goto error_free_inner_classes; |
| |
| for (unsigned int i = 0; i < annotations_attribute.num_annotations; i++) { |
| struct cafebabe_annotation *annotation = &annotations_attribute.annotations[i]; |
| struct vm_annotation *vma; |
| |
| vma = vm_annotation_parse(vmc, annotation); |
| if (!vma) |
| goto error_free_annotations; |
| |
| vmc->annotations[vmc->nr_annotations++] = vma; |
| } |
| |
| cafebabe_annotations_attribute_deinit(&annotations_attribute); |
| |
| if (!cafebabe_read_enclosing_method_attribute(class, &class->attributes, &vmc->enclosing_method_attribute)) |
| vmc->enclosing_class = vm_class_resolve_class(vmc, vmc->enclosing_method_attribute.class_index); |
| |
| vmc->state = VM_CLASS_LINKED; |
| return 0; |
| |
| error_free_annotations: |
| for (unsigned int i = 0; i < vmc->nr_annotations; i++) { |
| struct vm_annotation *vma = vmc->annotations[i]; |
| |
| vm_annotation_free(vma); |
| } |
| vm_free(vmc->annotations); |
| error_free_methods: |
| vm_free(vmc->methods); |
| error_free_inner_classes: |
| vm_free(vmc->inner_classes); |
| error_free_static_values: |
| vm_free(vmc->static_values); |
| error_free_buckets: |
| free_buckets(2, VM_TYPE_MAX, field_buckets); |
| error_free_fields: |
| vm_free(vmc->fields); |
| error_free_interfaces: |
| free(vmc->interfaces); |
| error_free_name: |
| free(vmc->name); |
| |
| return -1; |
| } |
| |
| int vm_class_link_primitive_class(struct vm_class *vmc, const char *class_name) |
| { |
| int err; |
| |
| err = vm_class_link_common(vmc); |
| if (err) |
| return err; |
| |
| vmc->name = strdup(class_name); |
| if (!vmc->name) |
| return -ENOMEM; |
| |
| vmc->kind = VM_CLASS_KIND_PRIMITIVE; |
| vmc->class = NULL; |
| vmc->state = VM_CLASS_LINKED; |
| |
| vmc->access_flags = CAFEBABE_CLASS_ACC_PUBLIC | CAFEBABE_CLASS_ACC_FINAL | CAFEBABE_CLASS_ACC_ABSTRACT; |
| |
| vmc->super = vm_java_lang_Object; |
| vmc->nr_interfaces = 0; |
| vmc->interfaces = NULL; |
| vmc->fields = NULL; |
| vmc->methods = NULL; |
| |
| vmc->object_size = 0; |
| vmc->static_size = 0; |
| |
| vmc->vtable_size = vm_java_lang_Object->vtable_size; |
| vmc->vtable.native_ptr = vm_java_lang_Object->vtable.native_ptr; |
| |
| vmc->source_file_name = NULL; |
| return 0; |
| } |
| |
| int vm_class_link_array_class(struct vm_class *vmc, struct vm_class *elem_class, |
| const char *class_name) |
| { |
| int err; |
| |
| err = vm_class_link_common(vmc); |
| if (err) |
| return err; |
| |
| vmc->name = strdup(class_name); |
| if (!vmc->name) |
| return -ENOMEM; |
| |
| vmc->kind = VM_CLASS_KIND_ARRAY; |
| vmc->class = NULL; |
| vmc->state = VM_CLASS_LINKED; |
| |
| vmc->array_element_class = elem_class; |
| vmc->access_flags = (elem_class->access_flags & ~CAFEBABE_CLASS_ACC_INTERFACE) |
| | CAFEBABE_CLASS_ACC_FINAL | CAFEBABE_CLASS_ACC_ABSTRACT; |
| |
| vmc->super = vm_java_lang_Object; |
| /* XXX: Actually, arrays should implement Serializable as well. */ |
| vmc->nr_interfaces = 1; |
| vmc->interfaces = &vm_java_lang_Cloneable; |
| vmc->fields = NULL; |
| vmc->methods = NULL; |
| |
| vmc->object_size = 0; |
| vmc->static_size = 0; |
| |
| vmc->vtable_size = vm_java_lang_Object->vtable_size; |
| vmc->vtable.native_ptr = vm_java_lang_Object->vtable.native_ptr; |
| |
| vmc->source_file_name = NULL; |
| return 0; |
| } |
| |
| static bool vm_class_check_class_init_fault(struct vm_class *vmc, |
| struct vm_object *arg) |
| { |
| char * str; |
| bool fault; |
| |
| str = vm_string_to_cstr(arg); |
| fault = (strcmp(str, vmc->name) == 0); |
| free(str); |
| |
| return fault; |
| } |
| |
| int vm_class_ensure_object(struct vm_class *vmc) |
| { |
| /* Before preload is finished .object points to a common |
| * dummy object. */ |
| assert(preload_finished); |
| return 0; |
| } |
| |
| int vm_class_init(struct vm_class *vmc) |
| { |
| struct vm_object *exception; |
| enum compile_lock_status status; |
| |
| status = compile_lock_enter(&vmc->cl); |
| if (status == STATUS_COMPILED_OK || status == STATUS_REENTER) |
| return 0; |
| |
| if (status == STATUS_COMPILED_ERRONOUS) { |
| signal_new_exception(vm_java_lang_NoClassDefFoundError, |
| vmc->name); |
| return -1; |
| } |
| |
| vm_object_lock(vmc->object); |
| assert(vmc->state == VM_CLASS_LINKED); |
| vmc->state = VM_CLASS_INITIALIZING; |
| vm_object_unlock(vmc->object); |
| |
| /* Fault injection, for testing purposes */ |
| if (vm_fault_enabled(VM_FAULT_CLASS_INIT)) { |
| struct vm_object *arg; |
| |
| arg = vm_fault_arg(VM_FAULT_CLASS_INIT); |
| if (vm_class_check_class_init_fault(vmc, arg)) { |
| signal_new_exception(vm_java_lang_RuntimeException, |
| NULL); |
| goto error; |
| } |
| } |
| |
| /* JVM spec, 2nd. ed., 2.17.1: "But before Terminator can be |
| * initialized, its direct superclass must be initialized, as well |
| * as the direct superclass of its direct superclass, and so on, |
| * recursively." */ |
| if (vmc->super) { |
| int ret = vm_class_ensure_init(vmc->super); |
| if (ret) |
| goto error; |
| } |
| |
| vm_class_ensure_object(vmc); |
| |
| if (vmc->class) { |
| /* XXX: Make sure there's at most one of these. */ |
| for (uint16_t i = 0; i < vmc->class->methods_count; ++i) { |
| if (strcmp(vmc->methods[i].name, "<clinit>")) |
| continue; |
| |
| void (*clinit_trampoline)(void) |
| = vm_method_trampoline_ptr(&vmc->methods[i]); |
| |
| clinit_trampoline(); |
| if (exception_occurred()) |
| goto error; |
| } |
| } |
| |
| vm_object_lock(vmc->object); |
| vmc->state = VM_CLASS_INITIALIZED; |
| vm_object_notify_all(vmc->object); |
| vm_object_unlock(vmc->object); |
| |
| compile_lock_leave(&vmc->cl, STATUS_COMPILED_OK); |
| return 0; |
| |
| error: |
| compile_lock_leave(&vmc->cl, STATUS_COMPILED_ERRONOUS); |
| exception = exception_occurred(); |
| |
| if (!vm_object_is_instance_of(exception, vm_java_lang_Error)) { |
| signal_new_exception_with_cause( |
| vm_java_lang_ExceptionInInitializerError, |
| exception, |
| vmc->name); |
| } |
| |
| vm_object_lock(vmc->object); |
| vmc->state = VM_CLASS_ERRONEOUS; |
| vm_object_notify_all(vmc->object); |
| vm_object_unlock(vmc->object); |
| |
| return -1; |
| } |
| |
| struct vm_class *vm_class_resolve_class(const struct vm_class *vmc, uint16_t i) |
| { |
| const struct cafebabe_constant_info_class *constant_class; |
| |
| if (cafebabe_class_constant_get_class(vmc->class, i, &constant_class)) |
| return NULL; |
| |
| const struct cafebabe_constant_info_utf8 *class_name; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| constant_class->name_index, &class_name)) |
| { |
| NOT_IMPLEMENTED; |
| return NULL; |
| } |
| |
| char *class_name_str = strndup((char *) class_name->bytes, |
| class_name->length); |
| if (!class_name_str) { |
| NOT_IMPLEMENTED; |
| return NULL; |
| } |
| |
| struct vm_class *class |
| = classloader_load(vmc->classloader, class_name_str); |
| if (!class) { |
| warn("failed to load class %s", class_name_str); |
| goto out; |
| } |
| out: |
| free(class_name_str); |
| return class; |
| } |
| |
| int vm_class_resolve_field(const struct vm_class *vmc, uint16_t i, |
| struct vm_class **r_vmc, char **r_name, char **r_type) |
| { |
| const struct cafebabe_constant_info_field_ref *field; |
| if (cafebabe_class_constant_get_field_ref(vmc->class, i, &field)) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| struct vm_class *class = vm_class_resolve_class(vmc, |
| field->class_index); |
| if (!class) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_name_and_type *name_and_type; |
| if (cafebabe_class_constant_get_name_and_type(vmc->class, |
| field->name_and_type_index, &name_and_type)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_utf8 *name; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| name_and_type->name_index, &name)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_utf8 *type; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| name_and_type->descriptor_index, &type)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *name_str = strndup((char *) name->bytes, name->length); |
| if (!name_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *type_str = strndup((char *) type->bytes, type->length); |
| if (!type_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| *r_vmc = class; |
| *r_name = name_str; |
| *r_type = type_str; |
| return 0; |
| } |
| |
| struct vm_field *vm_class_get_field(const struct vm_class *vmc, |
| const char *name, const char *type) |
| { |
| if (vmc->kind != VM_CLASS_KIND_REGULAR) |
| return NULL; |
| |
| unsigned int index = 0; |
| if (!cafebabe_class_get_field(vmc->class, name, type, &index)) |
| return &vmc->fields[index]; |
| |
| return NULL; |
| } |
| |
| struct vm_field *vm_class_get_field_recursive(const struct vm_class *vmc, |
| const char *name, const char *type) |
| { |
| /* See JVM Spec, 2nd ed., 5.4.3.2 "Field Resolution" */ |
| do { |
| struct vm_field *vmf = vm_class_get_field(vmc, name, type); |
| if (vmf) |
| return vmf; |
| |
| for (unsigned int i = 0; i < vmc->nr_interfaces; ++i) { |
| vmf = vm_class_get_field_recursive( |
| vmc->interfaces[i], name, type); |
| if (vmf) |
| return vmf; |
| } |
| |
| vmc = vmc->super; |
| } while(vmc); |
| |
| return NULL; |
| } |
| |
| struct vm_field * |
| vm_class_resolve_field_recursive(const struct vm_class *vmc, uint16_t i) |
| { |
| struct vm_class *class; |
| char *name; |
| char *type; |
| struct vm_field *result; |
| |
| if (vm_class_resolve_field(vmc, i, &class, &name, &type)) { |
| NOT_IMPLEMENTED; |
| return NULL; |
| } |
| |
| result = vm_class_get_field_recursive(class, name, type); |
| |
| free(name); |
| free(type); |
| return result; |
| } |
| |
| static int vm_class_resolve_name_and_type(const struct vm_class *vmc, uint16_t index, char **r_name, char **r_type) |
| { |
| const struct cafebabe_constant_info_name_and_type *name_and_type; |
| |
| if (cafebabe_class_constant_get_name_and_type(vmc->class, index, &name_and_type)) |
| return -1; |
| |
| const struct cafebabe_constant_info_utf8 *name; |
| if (cafebabe_class_constant_get_utf8(vmc->class, name_and_type->name_index, &name)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_utf8 *type; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| name_and_type->descriptor_index, &type)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *name_str = strndup((char *) name->bytes, name->length); |
| if (!name_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *type_str = strndup((char *) type->bytes, type->length); |
| if (!type_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| *r_name = name_str; |
| *r_type = type_str; |
| |
| return 0; |
| } |
| |
| int vm_class_resolve_method(const struct vm_class *vmc, uint16_t i, |
| struct vm_class **r_vmc, char **r_name, char **r_type) |
| { |
| const struct cafebabe_constant_info_method_ref *method; |
| if (cafebabe_class_constant_get_method_ref(vmc->class, i, &method)) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| struct vm_class *class = vm_class_resolve_class(vmc, method->class_index); |
| if (!class) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| if (vm_class_resolve_name_and_type(vmc, method->name_and_type_index, r_name, r_type)) |
| return -1; |
| |
| *r_vmc = class; |
| |
| return 0; |
| } |
| |
| struct vm_method *vm_class_get_method(const struct vm_class *vmc, |
| const char *name, const char *type) |
| { |
| if (vmc->kind != VM_CLASS_KIND_REGULAR) |
| return NULL; |
| |
| for (unsigned int i = 0; i < vmc->nr_methods; ++i) { |
| struct vm_method *vmm = &vmc->methods[i]; |
| |
| if (!strcmp(vmm->name, name) && !strcmp(vmm->type, type)) |
| return vmm; |
| } |
| |
| return NULL; |
| } |
| |
| struct vm_method *vm_class_get_method_recursive(const struct vm_class *vmc, |
| const char *name, const char *type) |
| { |
| do { |
| struct vm_method *vmf = vm_class_get_method(vmc, name, type); |
| if (vmf) |
| return vmf; |
| |
| vmc = vmc->super; |
| } while(vmc); |
| |
| return NULL; |
| } |
| |
| static struct vm_method *vm_class_get_interface_method_recursive( |
| const struct vm_class *vmc, const char *name, const char *type) |
| { |
| struct vm_method *vmm = vm_class_get_method(vmc, name, type); |
| if (vmm) |
| return vmm; |
| |
| for (unsigned int i = 0; i < vmc->nr_interfaces; ++i) { |
| vmm = vm_class_get_interface_method_recursive( |
| vmc->interfaces[i], name, type); |
| if (vmm) |
| return vmm; |
| } |
| |
| return NULL; |
| } |
| |
| int vm_class_resolve_interface_method(const struct vm_class *vmc, uint16_t i, |
| struct vm_class **r_vmc, char **r_name, char **r_type) |
| { |
| const struct cafebabe_constant_info_interface_method_ref *method; |
| if (cafebabe_class_constant_get_interface_method_ref(vmc->class, i, |
| &method)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| struct vm_class *class = vm_class_resolve_class(vmc, |
| method->class_index); |
| if (!class) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_name_and_type *name_and_type; |
| if (cafebabe_class_constant_get_name_and_type(vmc->class, |
| method->name_and_type_index, &name_and_type)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_utf8 *name; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| name_and_type->name_index, &name)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| const struct cafebabe_constant_info_utf8 *type; |
| if (cafebabe_class_constant_get_utf8(vmc->class, |
| name_and_type->descriptor_index, &type)) |
| { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *name_str = strndup((char *) name->bytes, name->length); |
| if (!name_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| char *type_str = strndup((char *) type->bytes, type->length); |
| if (!type_str) { |
| NOT_IMPLEMENTED; |
| return -1; |
| } |
| |
| *r_vmc = class; |
| *r_name = name_str; |
| *r_type = type_str; |
| return 0; |
| } |
| |
| static struct vm_method * |
| missing_method(struct vm_class *vmc, char *name, char *type, uint16_t access_flags) |
| { |
| struct cafebabe_method_info *method; |
| struct vm_method *vmm; |
| |
| method = vm_alloc(sizeof *method); |
| if (!method) |
| return NULL; |
| |
| method->access_flags = access_flags; |
| |
| vmm = vm_alloc(sizeof *vmm); |
| if (!vmm) |
| goto error_free_method; |
| |
| vmm->class = vmc; |
| |
| vmm->name = strdup(name); |
| if (!vmm->name) |
| goto error_free_vmm; |
| |
| vmm->type = strdup(type); |
| if (!vmm->type) |
| goto error_free_name; |
| |
| vmm->method = method; |
| |
| if (vm_method_do_init(vmm)) |
| goto error_free_type; |
| |
| return vmm; |
| |
| error_free_type: |
| free(vmm->type); |
| error_free_name: |
| free(vmm->name); |
| error_free_vmm: |
| free(vmm); |
| error_free_method: |
| free(method); |
| |
| return NULL; |
| } |
| |
| struct vm_method * |
| vm_class_resolve_method_recursive(const struct vm_class *vmc, uint16_t i, uint16_t access_flags) |
| { |
| struct vm_method *result; |
| struct vm_class *class; |
| char *name; |
| char *type; |
| |
| if (vm_class_resolve_method(vmc, i, &class, &name, &type)) { |
| NOT_IMPLEMENTED; |
| return NULL; |
| } |
| |
| result = vm_class_get_method_recursive(class, name, type); |
| if (!result) |
| result = missing_method(class, name, type, access_flags); |
| |
| free(name); |
| free(type); |
| |
| return result; |
| } |
| |
| struct vm_method * |
| vm_class_resolve_interface_method_recursive(const struct vm_class *vmc, |
| uint16_t i) |
| { |
| struct vm_class *class; |
| char *name; |
| char *type; |
| struct vm_method *result; |
| |
| if (vm_class_resolve_interface_method(vmc, i, &class, &name, &type)) { |
| NOT_IMPLEMENTED; |
| return NULL; |
| } |
| |
| result = vm_class_get_interface_method_recursive(class, name, type); |
| |
| free(name); |
| free(type); |
| return result; |
| } |
| |
| static bool is_numeric(const char *s) |
| { |
| for (unsigned int i = 0; i < strlen(s); i++) { |
| if (!isdigit(s[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| struct vm_method *vm_class_get_enclosing_method(const struct vm_class *vmc) |
| { |
| struct vm_method *result; |
| char *name; |
| char *type; |
| |
| if (!vmc->enclosing_class) |
| return NULL; |
| |
| if (vm_class_resolve_name_and_type(vmc, vmc->enclosing_method_attribute.method_index, &name, &type)) |
| return NULL; |
| |
| result = vm_class_get_method_recursive(vmc->enclosing_class, name, type); |
| |
| free(name); |
| free(type); |
| |
| return result; |
| } |
| |
| /* See Section 15.9.5 ("Anonymous Class Declarations") of the JLS for details. */ |
| bool vm_class_is_anonymous(const struct vm_class *vmc) |
| { |
| if (!vm_class_is_regular_class(vmc)) |
| return false; |
| |
| if (vm_class_is_abstract(vmc)) |
| return false; |
| |
| char *separator = strchr(vmc->name, '$'); |
| if (!separator) |
| return false; |
| |
| size_t len = strlen(separator); |
| if (len == 0) |
| return false; |
| |
| return is_numeric(separator + 1); |
| } |
| |
| static void supertype_cache_update(struct vm_class *vmc, const struct vm_class *super) |
| { |
| unsigned int ndx = vmc->supertype_cache_ndx++ % SUPERTYPE_CACHE_SIZE; |
| |
| vmc->supertype_cache[ndx] = super; |
| } |
| |
| static bool vm_class_is_subclass_of(const struct vm_class *vmc, const struct vm_class *from) |
| { |
| struct vm_class *super = from->super; |
| |
| while (super) { |
| if (vmc == super) |
| return true; |
| |
| super = super->super; |
| } |
| |
| return false; |
| } |
| |
| static bool vm_class_is_assignable_from_nocache(const struct vm_class *vmc, const struct vm_class *from); |
| |
| static bool vm_class_is_instance_of_array(const struct vm_class *vmc, const struct vm_class *from) |
| { |
| if (!vm_class_is_array_class(from)) |
| return false; |
| |
| struct vm_class *vmc_el = vm_class_get_array_element_class(vmc); |
| |
| struct vm_class *from_el = vm_class_get_array_element_class(vmc); |
| |
| return vm_class_is_assignable_from_nocache(vmc_el, from_el); |
| } |
| |
| static bool vm_class_implements(const struct vm_class *vmc, const struct vm_class *from) |
| { |
| for (unsigned int i = 0; i < from->nr_interfaces; ++i) { |
| if (vmc == from->interfaces[i] || vm_class_implements(vmc, from->interfaces[i])) |
| return true; |
| } |
| |
| if (from->super) |
| return vm_class_implements(vmc, from->super); |
| |
| return false; |
| } |
| |
| static bool vm_class_is_assignable_from_nocache(const struct vm_class *vmc, const struct vm_class *from) |
| { |
| if (vmc == from) |
| return true; |
| |
| if (vm_class_is_interface(vmc)) |
| return vm_class_implements(vmc, from); |
| |
| if (vm_class_is_array_class(vmc)) |
| return vm_class_is_instance_of_array(vmc, from); |
| |
| return vm_class_is_subclass_of(vmc, from); |
| } |
| |
| /* Reference: http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#isAssignableFrom(java.lang.Class) */ |
| bool vm_class_is_assignable_from_slow(struct vm_class *vmc, const struct vm_class *from) |
| { |
| bool ret; |
| |
| ret = vm_class_is_assignable_from_nocache(vmc, from); |
| |
| if (ret) |
| supertype_cache_update(vmc, from); |
| |
| return ret; |
| } |
| |
| char *vm_class_get_array_element_class_name(const char *class_name) |
| { |
| if (class_name[0] != '[') |
| return NULL; |
| |
| if (class_name[1] == 'L') |
| /* Skip '[L' prefix and ';' suffix */ |
| return strndup(class_name + 2, strlen(class_name) - 3); |
| |
| return strdup(class_name + 1); |
| } |
| |
| struct vm_class * |
| vm_class_get_array_element_class(const struct vm_class *array_class) |
| { |
| struct vm_class *result; |
| |
| result = array_class->array_element_class; |
| assert(result); |
| |
| vm_class_ensure_init(result); |
| |
| return result; |
| } |
| |
| enum vm_type vm_class_get_storage_vmtype(const struct vm_class *class) |
| { |
| if (class->kind != VM_CLASS_KIND_PRIMITIVE) |
| return J_REFERENCE; |
| |
| return class->primitive_vm_type; |
| } |
| |
| struct vm_class *vm_class_get_class_from_class_object(struct vm_object *clazz) |
| { |
| return (struct vm_class*)field_get_object(clazz, |
| vm_java_lang_Class_vmdata); |
| } |
| |
| struct vm_class *vm_class_get_array_class(struct vm_class *element_class) |
| { |
| struct vm_class *result; |
| char *name; |
| |
| if (!asprintf(&name, "[L%s;", element_class->name)) |
| return throw_oom_error(); |
| |
| result = classloader_load(element_class->classloader, name); |
| free(name); |
| return result; |
| } |
| |
| struct vm_class * |
| vm_class_define(struct vm_object *classloader, const char *name, |
| uint8_t *data, unsigned long len) |
| { |
| struct cafebabe_stream stream; |
| struct cafebabe_class *class; |
| struct vm_class *result = NULL; |
| |
| cafebabe_stream_open_buffer(&stream, data, len); |
| |
| class = malloc(sizeof *class); |
| if (cafebabe_class_init(class, &stream)) { |
| signal_new_exception(vm_java_lang_ClassFormatError, NULL); |
| goto out_stream; |
| } |
| |
| cafebabe_stream_close_buffer(&stream); |
| |
| result = vm_alloc(sizeof *result); |
| if (!result) |
| return throw_oom_error(); |
| |
| result->classloader = classloader; |
| |
| if (vm_class_link(result, class)) |
| return NULL; |
| |
| return result; |
| |
| out_stream: |
| cafebabe_stream_close_buffer(&stream); |
| return NULL; |
| } |