| /* Handle exceptions for GNU compiler for the Java(TM) language. |
| Copyright (C) 1997, 1998, 1999, 2000, 2002, 2003, 2004 |
| Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. |
| |
| Java and all Java-based marks are trademarks or registered trademarks |
| of Sun Microsystems, Inc. in the United States and other countries. |
| The Free Software Foundation is independent of Sun Microsystems, Inc. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "tree.h" |
| #include "real.h" |
| #include "rtl.h" |
| #include "java-tree.h" |
| #include "javaop.h" |
| #include "java-opcodes.h" |
| #include "jcf.h" |
| #include "function.h" |
| #include "except.h" |
| #include "java-except.h" |
| #include "toplev.h" |
| |
| static void expand_start_java_handler (struct eh_range *); |
| static void expand_end_java_handler (struct eh_range *); |
| static struct eh_range *find_handler_in_range (int, struct eh_range *, |
| struct eh_range *); |
| static void link_handler (struct eh_range *, struct eh_range *); |
| static void check_start_handlers (struct eh_range *, int); |
| static void free_eh_ranges (struct eh_range *range); |
| |
| struct eh_range *current_method_handlers; |
| |
| struct eh_range *current_try_block = NULL; |
| |
| struct eh_range *eh_range_freelist = NULL; |
| |
| /* These variables are used to speed up find_handler. */ |
| |
| static int cache_range_start, cache_range_end; |
| static struct eh_range *cache_range; |
| static struct eh_range *cache_next_child; |
| |
| /* A dummy range that represents the entire method. */ |
| |
| struct eh_range whole_range; |
| |
| #if defined(DEBUG_JAVA_BINDING_LEVELS) |
| extern int binding_depth; |
| extern int is_class_level; |
| extern int current_pc; |
| extern void indent (); |
| |
| #endif |
| |
| /* Search for the most specific eh_range containing PC. |
| Assume PC is within RANGE. |
| CHILD is a list of children of RANGE such that any |
| previous children have end_pc values that are too low. */ |
| |
| static struct eh_range * |
| find_handler_in_range (int pc, struct eh_range *range, struct eh_range *child) |
| { |
| for (; child != NULL; child = child->next_sibling) |
| { |
| if (pc < child->start_pc) |
| break; |
| if (pc < child->end_pc) |
| return find_handler_in_range (pc, child, child->first_child); |
| } |
| cache_range = range; |
| cache_range_start = pc; |
| cache_next_child = child; |
| cache_range_end = child == NULL ? range->end_pc : child->start_pc; |
| return range; |
| } |
| |
| /* Find the inner-most handler that contains PC. */ |
| |
| struct eh_range * |
| find_handler (int pc) |
| { |
| struct eh_range *h; |
| if (pc >= cache_range_start) |
| { |
| h = cache_range; |
| if (pc < cache_range_end) |
| return h; |
| while (pc >= h->end_pc) |
| { |
| cache_next_child = h->next_sibling; |
| h = h->outer; |
| } |
| } |
| else |
| { |
| h = &whole_range; |
| cache_next_child = h->first_child; |
| } |
| return find_handler_in_range (pc, h, cache_next_child); |
| } |
| |
| /* Recursive helper routine for check_nested_ranges. */ |
| |
| static void |
| link_handler (struct eh_range *range, struct eh_range *outer) |
| { |
| struct eh_range **ptr; |
| |
| if (range->start_pc == outer->start_pc && range->end_pc == outer->end_pc) |
| { |
| outer->handlers = chainon (outer->handlers, range->handlers); |
| return; |
| } |
| |
| /* If the new range completely encloses the `outer' range, then insert it |
| between the outer range and its parent. */ |
| if (range->start_pc <= outer->start_pc && range->end_pc >= outer->end_pc) |
| { |
| range->outer = outer->outer; |
| range->next_sibling = NULL; |
| range->first_child = outer; |
| { |
| struct eh_range **pr = &(outer->outer->first_child); |
| while (*pr != outer) |
| pr = &(*pr)->next_sibling; |
| *pr = range; |
| } |
| outer->outer = range; |
| return; |
| } |
| |
| /* Handle overlapping ranges by splitting the new range. */ |
| if (range->start_pc < outer->start_pc || range->end_pc > outer->end_pc) |
| { |
| struct eh_range *h = xmalloc (sizeof (struct eh_range)); |
| if (range->start_pc < outer->start_pc) |
| { |
| h->start_pc = range->start_pc; |
| h->end_pc = outer->start_pc; |
| range->start_pc = outer->start_pc; |
| } |
| else |
| { |
| h->start_pc = outer->end_pc; |
| h->end_pc = range->end_pc; |
| range->end_pc = outer->end_pc; |
| } |
| h->first_child = NULL; |
| h->outer = NULL; |
| h->handlers = build_tree_list (TREE_PURPOSE (range->handlers), |
| TREE_VALUE (range->handlers)); |
| h->next_sibling = NULL; |
| h->expanded = 0; |
| /* Restart both from the top to avoid having to make this |
| function smart about reentrancy. */ |
| link_handler (h, &whole_range); |
| link_handler (range, &whole_range); |
| return; |
| } |
| |
| ptr = &outer->first_child; |
| for (;; ptr = &(*ptr)->next_sibling) |
| { |
| if (*ptr == NULL || range->end_pc <= (*ptr)->start_pc) |
| { |
| range->next_sibling = *ptr; |
| range->first_child = NULL; |
| range->outer = outer; |
| *ptr = range; |
| return; |
| } |
| else if (range->start_pc < (*ptr)->end_pc) |
| { |
| link_handler (range, *ptr); |
| return; |
| } |
| /* end_pc > (*ptr)->start_pc && start_pc >= (*ptr)->end_pc. */ |
| } |
| } |
| |
| /* The first pass of exception range processing (calling add_handler) |
| constructs a linked list of exception ranges. We turn this into |
| the data structure expected by the rest of the code, and also |
| ensure that exception ranges are properly nested. */ |
| |
| void |
| handle_nested_ranges (void) |
| { |
| struct eh_range *ptr, *next; |
| |
| ptr = whole_range.first_child; |
| whole_range.first_child = NULL; |
| for (; ptr; ptr = next) |
| { |
| next = ptr->next_sibling; |
| ptr->next_sibling = NULL; |
| link_handler (ptr, &whole_range); |
| } |
| } |
| |
| /* Free RANGE as well as its children and siblings. */ |
| |
| static void |
| free_eh_ranges (struct eh_range *range) |
| { |
| while (range) |
| { |
| struct eh_range *next = range->next_sibling; |
| free_eh_ranges (range->first_child); |
| if (range != &whole_range) |
| free (range); |
| range = next; |
| } |
| } |
| |
| /* Called to re-initialize the exception machinery for a new method. */ |
| |
| void |
| method_init_exceptions (void) |
| { |
| free_eh_ranges (&whole_range); |
| whole_range.start_pc = 0; |
| whole_range.end_pc = DECL_CODE_LENGTH (current_function_decl) + 1; |
| whole_range.outer = NULL; |
| whole_range.first_child = NULL; |
| whole_range.next_sibling = NULL; |
| cache_range_start = 0xFFFFFF; |
| } |
| |
| /* Add an exception range. If we already have an exception range |
| which has the same handler and label, and the new range overlaps |
| that one, then we simply extend the existing range. Some bytecode |
| obfuscators generate seemingly nonoverlapping exception ranges |
| which, when coalesced, do in fact nest correctly. |
| |
| This constructs an ordinary linked list which check_nested_ranges() |
| later turns into the data structure we actually want. |
| |
| We expect the input to come in order of increasing START_PC. This |
| function doesn't attempt to detect the case where two previously |
| added disjoint ranges could be coalesced by a new range; that is |
| what the sorting counteracts. */ |
| |
| void |
| add_handler (int start_pc, int end_pc, tree handler, tree type) |
| { |
| struct eh_range *ptr, *prev = NULL, *h; |
| |
| for (ptr = whole_range.first_child; ptr; ptr = ptr->next_sibling) |
| { |
| if (start_pc >= ptr->start_pc |
| && start_pc <= ptr->end_pc |
| && TREE_PURPOSE (ptr->handlers) == type |
| && TREE_VALUE (ptr->handlers) == handler) |
| { |
| /* Already found an overlapping range, so coalesce. */ |
| ptr->end_pc = MAX (ptr->end_pc, end_pc); |
| return; |
| } |
| prev = ptr; |
| } |
| |
| h = xmalloc (sizeof (struct eh_range)); |
| h->start_pc = start_pc; |
| h->end_pc = end_pc; |
| h->first_child = NULL; |
| h->outer = NULL; |
| h->handlers = build_tree_list (type, handler); |
| h->next_sibling = NULL; |
| h->expanded = 0; |
| |
| if (prev == NULL) |
| whole_range.first_child = h; |
| else |
| prev->next_sibling = h; |
| } |
| |
| |
| /* if there are any handlers for this range, issue start of region */ |
| static void |
| expand_start_java_handler (struct eh_range *range) |
| { |
| #if defined(DEBUG_JAVA_BINDING_LEVELS) |
| indent (); |
| fprintf (stderr, "expand start handler pc %d --> %d\n", |
| current_pc, range->end_pc); |
| #endif /* defined(DEBUG_JAVA_BINDING_LEVELS) */ |
| range->expanded = 1; |
| expand_eh_region_start (); |
| } |
| |
| tree |
| prepare_eh_table_type (tree type) |
| { |
| tree exp; |
| tree *slot; |
| const char *name; |
| char *buf; |
| tree decl; |
| tree utf8_ref; |
| |
| /* The "type" (match_info) in a (Java) exception table is a pointer to: |
| * a) NULL - meaning match any type in a try-finally. |
| * b) a pointer to a pointer to a class. |
| * c) a pointer to a pointer to a utf8_ref. The pointer is |
| * rewritten to point to the appropriate class. */ |
| |
| if (type == NULL_TREE) |
| return NULL_TREE; |
| |
| if (TYPE_TO_RUNTIME_MAP (output_class) == NULL) |
| TYPE_TO_RUNTIME_MAP (output_class) = java_treetreehash_create (10, 1); |
| |
| slot = java_treetreehash_new (TYPE_TO_RUNTIME_MAP (output_class), type); |
| if (*slot != NULL) |
| return TREE_VALUE (*slot); |
| |
| if (is_compiled_class (type) && !flag_indirect_dispatch) |
| { |
| name = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (type))); |
| buf = alloca (strlen (name) + 5); |
| sprintf (buf, "%s_ref", name); |
| decl = build_decl (VAR_DECL, get_identifier (buf), ptr_type_node); |
| TREE_STATIC (decl) = 1; |
| DECL_ARTIFICIAL (decl) = 1; |
| DECL_IGNORED_P (decl) = 1; |
| TREE_READONLY (decl) = 1; |
| TREE_THIS_VOLATILE (decl) = 0; |
| DECL_INITIAL (decl) = build_class_ref (type); |
| layout_decl (decl, 0); |
| pushdecl (decl); |
| exp = build1 (ADDR_EXPR, ptr_type_node, decl); |
| } |
| else |
| { |
| utf8_ref = build_utf8_ref (DECL_NAME (TYPE_NAME (type))); |
| name = IDENTIFIER_POINTER (DECL_NAME (TREE_OPERAND (utf8_ref, 0))); |
| buf = alloca (strlen (name) + 5); |
| sprintf (buf, "%s_ref", name); |
| decl = build_decl (VAR_DECL, get_identifier (buf), utf8const_ptr_type); |
| TREE_STATIC (decl) = 1; |
| DECL_ARTIFICIAL (decl) = 1; |
| DECL_IGNORED_P (decl) = 1; |
| TREE_READONLY (decl) = 1; |
| TREE_THIS_VOLATILE (decl) = 0; |
| layout_decl (decl, 0); |
| pushdecl (decl); |
| exp = build1 (ADDR_EXPR, build_pointer_type (utf8const_ptr_type), decl); |
| TYPE_CATCH_CLASSES (output_class) = |
| tree_cons (NULL, make_catch_class_record (exp, utf8_ref), |
| TYPE_CATCH_CLASSES (output_class)); |
| } |
| |
| *slot = tree_cons (type, exp, NULL_TREE); |
| |
| return exp; |
| } |
| |
| static int |
| expand_catch_class (void **entry, void *x ATTRIBUTE_UNUSED) |
| { |
| struct treetreehash_entry *ite = (struct treetreehash_entry *) *entry; |
| tree decl = TREE_OPERAND (TREE_VALUE ((tree)ite->value), 0); |
| rest_of_decl_compilation (decl, (char*) 0, global_bindings_p (), 0); |
| return true; |
| } |
| |
| /* For every class in the TYPE_TO_RUNTIME_MAP, expand the |
| corresponding object that is used by the runtime type matcher. */ |
| |
| void |
| java_expand_catch_classes (tree this_class) |
| { |
| if (TYPE_TO_RUNTIME_MAP (this_class)) |
| htab_traverse |
| (TYPE_TO_RUNTIME_MAP (this_class), |
| expand_catch_class, NULL); |
| } |
| |
| /* Build a reference to the jthrowable object being carried in the |
| exception header. */ |
| |
| tree |
| build_exception_object_ref (tree type) |
| { |
| tree obj; |
| |
| /* Java only passes object via pointer and doesn't require adjusting. |
| The java object is immediately before the generic exception header. */ |
| obj = build (EXC_PTR_EXPR, build_pointer_type (type)); |
| obj = build (MINUS_EXPR, TREE_TYPE (obj), obj, |
| TYPE_SIZE_UNIT (TREE_TYPE (obj))); |
| obj = build1 (INDIRECT_REF, type, obj); |
| |
| return obj; |
| } |
| |
| /* If there are any handlers for this range, isssue end of range, |
| and then all handler blocks */ |
| static void |
| expand_end_java_handler (struct eh_range *range) |
| { |
| tree handler = range->handlers; |
| force_poplevels (range->start_pc); |
| expand_start_all_catch (); |
| for ( ; handler != NULL_TREE; handler = TREE_CHAIN (handler)) |
| { |
| /* For bytecode we treat exceptions a little unusually. A |
| `finally' clause looks like an ordinary exception handler for |
| Throwable. The reason for this is that the bytecode has |
| already expanded the finally logic, and we would have to do |
| extra (and difficult) work to get this to look like a |
| gcc-style finally clause. */ |
| tree type = TREE_PURPOSE (handler); |
| if (type == NULL) |
| type = throwable_type_node; |
| |
| expand_start_catch (prepare_eh_table_type (type)); |
| expand_goto (TREE_VALUE (handler)); |
| expand_end_catch (); |
| } |
| expand_end_all_catch (); |
| #if defined(DEBUG_JAVA_BINDING_LEVELS) |
| indent (); |
| fprintf (stderr, "expand end handler pc %d <-- %d\n", |
| current_pc, range->start_pc); |
| #endif /* defined(DEBUG_JAVA_BINDING_LEVELS) */ |
| } |
| |
| /* Recursive helper routine for maybe_start_handlers. */ |
| |
| static void |
| check_start_handlers (struct eh_range *range, int pc) |
| { |
| if (range != NULL_EH_RANGE && range->start_pc == pc) |
| { |
| check_start_handlers (range->outer, pc); |
| if (!range->expanded) |
| expand_start_java_handler (range); |
| } |
| } |
| |
| |
| static struct eh_range *current_range; |
| |
| /* Emit any start-of-try-range starting at start_pc and ending after |
| end_pc. */ |
| |
| void |
| maybe_start_try (int start_pc, int end_pc) |
| { |
| struct eh_range *range; |
| if (! doing_eh (1)) |
| return; |
| |
| range = find_handler (start_pc); |
| while (range != NULL_EH_RANGE && range->start_pc == start_pc |
| && range->end_pc < end_pc) |
| range = range->outer; |
| |
| current_range = range; |
| check_start_handlers (range, start_pc); |
| } |
| |
| /* Emit any end-of-try-range ending at end_pc and starting before |
| start_pc. */ |
| |
| void |
| maybe_end_try (int start_pc, int end_pc) |
| { |
| if (! doing_eh (1)) |
| return; |
| |
| while (current_range != NULL_EH_RANGE && current_range->end_pc <= end_pc |
| && current_range->start_pc >= start_pc) |
| { |
| expand_end_java_handler (current_range); |
| current_range = current_range->outer; |
| } |
| } |