This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Patch: FYI: PR 13107


This patch fixes PR 13107.

It turned out that the best way to fix this PR was to completely redo
how we treat subroutines.  The new treatment, while somewhat less
efficient, is much simpler and also more robust.  I was able to remove
all of the most complicated and confusing bits from the verifier while
writing this patch.  And as for robustness, see the three new test
files.  These are all simple Java programs with the property that the
bytecode emitted by most compilers is obviously correct but
nevertheless rejected by most verifiers.

This verifier will accept some bytecode which Sun's will not.  I
believe, however, that these differences cannot be exploited to
violate type safety.  Instead, I believe the JDK verifier implements
some pointless rules.  (So do we, at present.  See the big explanatory
comment I've added.)

This patch does cause some "regressions" in the mauve verifier test
suite.  However, I've looked at these, and in all cases it is simply
that we accept code which according to the JVMS is suspect.  One of
the papers by Alessandro Coglio shows these restrictions to be
incorrect (and in some cases, incoherent).

At the moment I'm only checking this in on the trunk.  I've tested it
against a reasonable amount of code, but it is new, and pretty big.
Also, I believe the bug is not a regression.  Most likely, this will
not go in 3.4.

Tom

Index: ChangeLog
from  Tom Tromey  <tromey@redhat.com>

	PR libgcj/13107:
	* testsuite/libjava.lang/pr13107_2.xfail: New file.
	* testsuite/libjava.lang/pr13107_3.xfail: New file.
	* testsuite/libjava.lang/pr13107_3.java: New file.
	* testsuite/libjava.lang/pr13107_3.out: New file.
	* testsuite/libjava.lang/pr13107_2.java: New file.
	* testsuite/libjava.lang/pr13107_2.out: New file.
	* testsuite/libjava.lang/pr13107.java: New file.
	* testsuite/libjava.lang/pr13107.out: New file.
	* verify.cc (jsr_ptrs): Removed.
	(entry_points): Likewise.
	(struct subr_info): Likewise.
	(struct subr_entry_info): Likewise.
	(type_val::unused_by_subroutine_type): Likewise.
	(type::merge): Don't handle unused_by_subroutine_type.
	(type::print): Likewise.
	(state::flags): Removed.
	(state::subroutine): Likewise.
	(state::seen_subrs): Likewise.
	(state::NO_STACK): Likewise.
	(state::FLAG_CHANGED, state::FLAG_UNUSED): Likewise.
	(state): Updated all methods.
	(state::clean_subrs): Removed.
	(state::state): Removed `ret_semantics' flag.
	(state::copy): Likewise.
	(state::add_subr): Removed.
	(state::enter_subroutine): Likewise.
	(type::set_return_address): New method.
	(handle_jsr_insn): Set return address on the type.  Always
	invalidate PC after call.
	(check_nonrecursive_call): Removed.
	(~_Jv_BytecodeVerifier): Updated.
	(branch_prepass): Removed special handling of jsr.
	(note_branch_target): Likewise.
	(get_subroutine): Removed.
	(state::merge): Don't merge subroutines and don't handle
	NO_STACK.  Removed ret_semantics and jsr_semantics arguments.
	(state::note_variable): Removed.
	(state::is_unmerged_ret_state): Likewise.
	(state::print): Updated.
	(set_variable): Likewise.
	(merge_into): Renamed from push_jump_merge.  Removed ret_semantics
	and jsr_semantics arguments.  Updated for new reverification
	list.
	(pop_jump): Rewrote.
	(construct_primitive_array_type): Updated.
	(state::next): Removed.
	(INVALID_STATE): New define.
	(state::INVALID): Removed.
	(state::NO_NEXT): New value.
	(state::pc, state::next): New fields.
	(state::get_pc): New method.
	(next_verify_pc): Removed.
	(next_verify_state): New field.
	(verify_instructions_0): Always check for falling off end.
	(linked): New type.
	(linked_utf8): Removed.
	(states): Changed type.
	(type::state_mergeable_p): New method.
	(state::state_mergeable_p): Likewise.
	(handle_ret_insn): Removed most code.
	(state::reverify): New method.
	(add_new_state): Likewise.
	(state::set_pc): Likewise.

Index: verify.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/verify.cc,v
retrieving revision 1.59
diff -u -r1.59 verify.cc
--- verify.cc 8 Jan 2004 05:27:39 -0000 1.59
+++ verify.cc 23 Jan 2004 02:37:29 -0000
@@ -1,6 +1,6 @@
 // verify.cc - verify bytecode
 
-/* Copyright (C) 2001, 2002, 2003  Free Software Foundation
+/* Copyright (C) 2001, 2002, 2003, 2004  Free Software Foundation
 
    This file is part of libgcj.
 
@@ -32,6 +32,10 @@
 #endif /* VERIFY_DEBUG */
 
 
+// This is used to mark states which are not scheduled for
+// verification.
+#define INVALID_STATE ((state *) -1)
+
 static void debug_print (const char *fmt, ...)
   __attribute__ ((format (printf, 1, 2)));
 
@@ -46,6 +50,78 @@
 #endif /* VERIFY_DEBUG */
 }
 
+// This started as a fairly ordinary verifier, and for the most part
+// it remains so.  It works in the obvious way, by modeling the effect
+// of each opcode as it is encountered.  For most opcodes, this is a
+// straightforward operation.
+//
+// This verifier does not do type merging.  It used to, but this
+// results in difficulty verifying some relatively simple code
+// involving interfaces, and it pushed some verification work into the
+// interpreter.
+//
+// Instead of merging reference types, when we reach a point where two
+// flows of control merge, we simply keep the union of reference types
+// from each branch.  Then, when we need to verify a fact about a
+// reference on the stack (e.g., that it is compatible with the
+// argument type of a method), we check to ensure that all possible
+// types satisfy the requirement.
+//
+// Another area this verifier differs from the norm is in its handling
+// of subroutines.  The JVM specification has some confusing things to
+// say about subroutines.  For instance, it makes claims about not
+// allowing subroutines to merge and it rejects recursive subroutines.
+// For the most part these are red herrings; we used to try to follow
+// these things but they lead to problems.  For example, the notion of
+// "being in a subroutine" is not well-defined: is an exception
+// handler in a subroutine?  If you never execute the `ret' but
+// instead `goto 1' do you remain in the subroutine?
+//
+// For clarity on what is really required for type safety, read
+// "Simple Verification Technique for Complex Java Bytecode
+// Subroutines" by Alessandro Coglio.  Among other things this paper
+// shows that recursive subroutines are not harmful to type safety.
+// We implement something similar to what he proposes.  Note that this
+// means that this verifier will accept code that is rejected by some
+// other verifiers.
+//
+// For those not wanting to read the paper, the basic observation is
+// that we can maintain split states in subroutines.  We maintain one
+// state for each calling `jsr'.  In other words, we re-verify a
+// subroutine once for each caller, using the exact types held by the
+// callers (as opposed to the old approach of merging types and
+// keeping a bitmap registering what did or did not change).  This
+// approach lets us continue to verify correctly even when a
+// subroutine is exited via `goto' or `athrow' and not `ret'.
+//
+// In some other areas the JVM specification is (mildly) incorrect,
+// but we still implement what is specified.  For instance, you cannot
+// violate type safety by allocating an object with `new' and then
+// failing to initialize it, no matter how one branches or where one
+// stores the uninitialized reference.  See "Improving the official
+// specification of Java bytecode verification" by Alessandro Coglio.
+// Similarly, there's no real point in enforcing that padding bytes or
+// the mystery byte of invokeinterface must be 0, but we do that too.
+//
+// The verifier is currently neither completely lazy nor eager when it
+// comes to loading classes.  It tries to represent types by name when
+// possible, and then loads them when it needs to verify a fact about
+// the type.  Checking types by name is valid because we only use
+// names which come from the current class' constant pool.  Since all
+// such names are looked up using the same class loader, there is no
+// danger that we might be fooled into comparing different types with
+// the same name.
+//
+// In the future we plan to allow for a completely lazy mode of
+// operation, where the verifier will construct a list of type
+// assertions to be checked later.
+//
+// Some test cases for the verifier live in the "verify" module of the
+// Mauve test suite.  However, some of these are presently
+// (2004-01-20) believed to be incorrect.  (More precisely the notion
+// of "correct" is not well-defined, and this verifier differs from
+// others while remaining type-safe.)  Some other tests live in the
+// libgcj test suite.
 class _Jv_BytecodeVerifier
 {
 private:
@@ -55,11 +131,16 @@
 
   struct state;
   struct type;
-  struct subr_info;
-  struct subr_entry_info;
   struct linked_utf8;
   struct ref_intersection;
 
+  template<typename T>
+  struct linked
+  {
+    T *val;
+    linked<T> *next;
+  };
+
   // The current PC.
   int PC;
   // The PC corresponding to the start of the current instruction.
@@ -68,29 +149,21 @@
   // The current state of the stack, locals, etc.
   state *current_state;
 
-  // We store the state at branch targets, for merging.  This holds
-  // such states.
-  state **states;
-
-  // We keep a linked list of all the PCs which we must reverify.
-  // The link is done using the PC values.  This is the head of the
-  // list.
-  int next_verify_pc;
+  // At each branch target we keep a linked list of all the states we
+  // can process at that point.  We'll only have multiple states at a
+  // given PC if they both have different return-address types in the
+  // same stack or local slot.  This array is indexed by PC and holds
+  // the list of all such states.
+  linked<state> **states;
+
+  // We keep a linked list of all the states which we must reverify.
+  // This is the head of the list.
+  state *next_verify_state;
 
   // We keep some flags for each instruction.  The values are the
-  // FLAG_* constants defined above.
+  // FLAG_* constants defined above.  This is an array indexed by PC.
   char *flags;
 
-  // We need to keep track of which instructions can call a given
-  // subroutine.  FIXME: this is inefficient.  We keep a linked list
-  // of all calling `jsr's at at each jsr target.
-  subr_info **jsr_ptrs;
-
-  // We keep a linked list of entries which map each `ret' instruction
-  // to its unique subroutine entry point.  We expect that there won't
-  // be many `ret' instructions, so a linked list is ok.
-  subr_entry_info *entry_points;
-
   // The bytecode itself.
   unsigned char *bytecode;
   // The exceptions.
@@ -103,17 +176,14 @@
 
   // A linked list of utf8 objects we allocate.  This is really ugly,
   // but without this our utf8 objects would be collected.
-  linked_utf8 *utf8_list;
+  linked<_Jv_Utf8Const> *utf8_list;
 
   // A linked list of all ref_intersection objects we allocate.
   ref_intersection *isect_list;
 
-  struct linked_utf8
-  {
-    _Jv_Utf8Const *val;
-    linked_utf8 *next;
-  };
-
+  // Create a new Utf-8 constant and return it.  We do this to avoid
+  // having our Utf-8 constants prematurely collected.  FIXME this is
+  // ugly.
   _Jv_Utf8Const *make_utf8_const (char *s, int len)
   {
     _Jv_Utf8Const *val = _Jv_makeUtf8Const (s, len);
@@ -124,7 +194,8 @@
     r->hash = val->hash;
     memcpy (r->data, val->data, val->length + 1);
 
-    linked_utf8 *lu = (linked_utf8 *) _Jv_Malloc (sizeof (linked_utf8));
+    linked<_Jv_Utf8Const> *lu
+      = (linked<_Jv_Utf8Const> *) _Jv_Malloc (sizeof (linked<_Jv_Utf8Const>));
     lu->val = r;
     lu->next = utf8_list;
     utf8_list = lu;
@@ -183,13 +254,10 @@
     // to indicate an unusable value.
     unsuitable_type,
     return_address_type,
+    // This is the second word of a two-word value, i.e., a double or
+    // a long.
     continuation_type,
 
-    // There is an obscure special case which requires us to note when
-    // a local variable has not been used by a subroutine.  See
-    // push_jump_merge for more information.
-    unused_by_subroutine_type,
-
     // Everything after `reference_type' must be a reference type.
     reference_type,
     null_type,
@@ -497,28 +565,6 @@
     return false;
   }
 
-  // This is used to keep track of which `jsr's correspond to a given
-  // jsr target.
-  struct subr_info
-  {
-    // PC of the instruction just after the jsr.
-    int pc;
-    // Link.
-    subr_info *next;
-  };
-
-  // This is used to keep track of which subroutine entry point
-  // corresponds to which `ret' instruction.
-  struct subr_entry_info
-  {
-    // PC of the subroutine entry point.
-    int pc;
-    // PC of the `ret' instruction.
-    int ret_pc;
-    // Link.
-    subr_entry_info *next;
-  };
-
   // The `type' class is used to represent a single type in the
   // verifier.
   struct type
@@ -529,11 +575,16 @@
     // For reference types, the representation of the type.
     ref_intersection *klass;
 
-    // This is used when constructing a new object.  It is the PC of the
+    // This is used in two situations.
+    //
+    // First, when constructing a new object, it is the PC of the
     // `new' instruction which created the object.  We use the special
-    // value -2 to mean that this is uninitialized, and the special
-    // value -1 for the case where the current method is itself the
-    // <init> method.
+    // value UNINIT to mean that this is uninitialized, and the
+    // special value SELF for the case where the current method is
+    // itself the <init> method.
+    //
+    // Second, when the key is return_address_type, this holds the PC
+    // of the instruction following the `jsr'.
     int pc;
 
     static const int UNINIT = -2;
@@ -640,6 +691,23 @@
 	}
     }
 
+    // Mark this type as a particular return address.
+    void set_return_address (int npc)
+    {
+      pc = npc;
+    }
+
+    // Return true if this type and type OTHER are considered
+    // mergeable for the purposes of state merging.  This is related
+    // to subroutine handling.  For this purpose two types are
+    // considered unmergeable if they are both return-addresses but
+    // have different PCs.
+    bool state_mergeable_p (const type &other) const
+    {
+      return (key != return_address_type
+	      || other.key != return_address_type
+	      || pc == other.pc);
+    }
 
     // Return true if an object of type K can be assigned to a variable
     // of type *THIS.  Handle various special cases too.  Might modify
@@ -780,13 +848,16 @@
     {
       // The way this is written, we don't need to check isarray().
       if (key != reference_type)
-	verifier->verify_fail ("internal error in verify_dimensions: not a reference type");
+	verifier->verify_fail ("internal error in verify_dimensions:"
+			       " not a reference type");
 
       if (klass->count_dimensions () < ndims)
-	verifier->verify_fail ("array type has fewer dimensions than required");
+	verifier->verify_fail ("array type has fewer dimensions"
+			       " than required");
     }
 
-    // Merge OLD_TYPE into this.  On error throw exception.
+    // Merge OLD_TYPE into this.  On error throw exception.  Return
+    // true if the merge caused a type change.
     bool merge (type& old_type, bool local_semantics,
 		_Jv_BytecodeVerifier *verifier)
     {
@@ -829,20 +900,9 @@
 	{
 	  if (local_semantics)
 	    {
-	      // If we're merging into an "unused" slot, then we
-	      // simply accept whatever we're merging from.
-	      if (key == unused_by_subroutine_type)
-		{
-		  *this = old_type;
-		  changed = true;
-		}
-	      else if (old_type.key == unused_by_subroutine_type)
-		{
-		  // Do nothing.
-		}
 	      // If we already have an `unsuitable' type, then we
 	      // don't need to change again.
-	      else if (key != unsuitable_type)
+	      if (key != unsuitable_type)
 		{
 		  key = unsuitable_type;
 		  changed = true;
@@ -872,7 +932,6 @@
 	case unsuitable_type: c = '-'; break;
 	case return_address_type: c = 'r'; break;
 	case continuation_type: c = '+'; break;
-	case unused_by_subroutine_type: c = '_'; break;
 	case reference_type: c = 'L'; break;
 	case null_type: c = '@'; break;
 	case uninitialized_reference_type: c = 'U'; break;
@@ -895,16 +954,6 @@
     type *stack;
     // The local variables.
     type *locals;
-    // Flags are used in subroutines to keep track of which local
-    // variables have been accessed.  They are also used after 
-    char *flags;
-    // If not 0, then we are in a subroutine.  The value is the PC of
-    // the subroutine's entry point.  We can use 0 as an exceptional
-    // value because PC=0 can never be a subroutine.
-    int subroutine;
-    // This is used to keep a linked list of all the states which
-    // require re-verification.  We use the PC to keep track.
-    int next;
     // We keep track of the type of `this' specially.  This is used to
     // ensure that an instance initializer invokes another initializer
     // on `this' before returning.  We must keep track of this
@@ -912,40 +961,27 @@
     // assigns to locals[0] (overwriting `this') and then returns
     // without really initializing.
     type this_type;
-    // This is a list of all subroutines that have been seen at this
-    // point.  Ordinarily this is NULL; it is only allocated and used
-    // in relatively weird situations involving non-ret exit from a
-    // subroutine.  We have to keep track of this in this way to avoid
-    // endless recursion in these cases.
-    subr_info *seen_subrs;
-
-    // INVALID marks a state which is not on the linked list of states
-    // requiring reverification.
-    static const int INVALID = -1;
-    // NO_NEXT marks the state at the end of the reverification list.
-    static const int NO_NEXT = -2;
-
-    // This is used to mark the stack depth at the instruction just
-    // after a `jsr' when we haven't yet processed the corresponding
-    // `ret'.  See handle_jsr_insn for more information.
-    static const int NO_STACK = -1;
-
-    // This flag indicates that the local was changed in this
-    // subroutine.
-    static const int FLAG_CHANGED = 1;
-    // This is set only on the flags of the state of an instruction
-    // directly following a "jsr".  It indicates that the local
-    // variable was changed by the subroutine corresponding to the
-    // "jsr".
-    static const int FLAG_USED = 2;
+
+    // The PC for this state.  This is only valid on states which are
+    // permanently attached to a given PC.  For an object like
+    // `current_state', which is used transiently, this has no
+    // meaning.
+    int pc;
+    // We keep a linked list of all states requiring reverification.
+    // If this is the special value INVALID_STATE then this state is
+    // not on the list.  NULL marks the end of the linked list.
+    state *next;
+
+    // NO_NEXT is the PC value meaning that a new state must be
+    // acquired from the verification list.
+    static const int NO_NEXT = -1;
 
     state ()
       : this_type ()
     {
       stack = NULL;
       locals = NULL;
-      flags = NULL;
-      seen_subrs = NULL;
+      next = INVALID_STATE;
     }
 
     state (int max_stack, int max_locals)
@@ -957,26 +993,19 @@
       for (int i = 0; i < max_stack; ++i)
 	stack[i] = unsuitable_type;
       locals = new type[max_locals];
-      flags = (char *) _Jv_Malloc (sizeof (char) * max_locals);
-      seen_subrs = NULL;
       for (int i = 0; i < max_locals; ++i)
-	{
-	  locals[i] = unsuitable_type;
-	  flags[i] = 0;
-	}
-      next = INVALID;
-      subroutine = 0;
+	locals[i] = unsuitable_type;
+      pc = NO_NEXT;
+      next = INVALID_STATE;
     }
 
-    state (const state *orig, int max_stack, int max_locals,
-	   bool ret_semantics = false)
+    state (const state *orig, int max_stack, int max_locals)
     {
       stack = new type[max_stack];
       locals = new type[max_locals];
-      flags = (char *) _Jv_Malloc (sizeof (char) * max_locals);
-      seen_subrs = NULL;
-      copy (orig, max_stack, max_locals, ret_semantics);
-      next = INVALID;
+      copy (orig, max_stack, max_locals);
+      pc = NO_NEXT;
+      next = INVALID_STATE;
     }
 
     ~state ()
@@ -985,9 +1014,6 @@
 	delete[] stack;
       if (locals)
 	delete[] locals;
-      if (flags)
-	_Jv_Free (flags);
-      clean_subrs ();
     }
 
     void *operator new[] (size_t bytes)
@@ -1010,65 +1036,17 @@
       _Jv_Free (mem);
     }
 
-    void clean_subrs ()
-    {
-      subr_info *info = seen_subrs;
-      while (info != NULL)
-	{
-	  subr_info *next = info->next;
-	  _Jv_Free (info);
-	  info = next;
-	}
-      seen_subrs = NULL;
-    }
-
-    void copy (const state *copy, int max_stack, int max_locals,
-	       bool ret_semantics = false)
+    void copy (const state *copy, int max_stack, int max_locals)
     {
       stacktop = copy->stacktop;
       stackdepth = copy->stackdepth;
-      subroutine = copy->subroutine;
       for (int i = 0; i < max_stack; ++i)
 	stack[i] = copy->stack[i];
       for (int i = 0; i < max_locals; ++i)
-	{
-	  // See push_jump_merge to understand this case.
-	  if (ret_semantics)
-	    {
-	      if ((copy->flags[i] & FLAG_CHANGED))
-		{
-		  // Changed in the subroutine, so we copy it here.
-		  locals[i] = copy->locals[i];
-		  flags[i] |= FLAG_USED;
-		}
-	      else
-		{
-		  // Not changed in the subroutine.  Use a special
-		  // type so the coming merge will overwrite.
-		  locals[i] = type (unused_by_subroutine_type);
-		}
-	    }
-	  else
-	    locals[i] = copy->locals[i];
-
-	  // Clear the flag unconditionally just so printouts look ok,
-	  // then only set it if we're still in a subroutine and it
-	  // did in fact change.
-	  flags[i] &= ~FLAG_CHANGED;
-	  if (subroutine && (copy->flags[i] & FLAG_CHANGED) != 0)
-	    flags[i] |= FLAG_CHANGED;
-	}
-
-      clean_subrs ();
-      if (copy->seen_subrs)
-	{
-	  for (subr_info *info = copy->seen_subrs;
-	       info != NULL; info = info->next)
-	    add_subr (info->pc);
-	}
+	locals[i] = copy->locals[i];
 
       this_type = copy->this_type;
-      // Don't modify `next'.
+      // Don't modify `next' or `pc'.
     }
 
     // Modify this state to reflect entry to an exception handler.
@@ -1081,33 +1059,21 @@
 	stack[i] = unsuitable_type;
     }
 
-    // Modify this state to reflect entry into a subroutine.
-    void enter_subroutine (int npc, int max_locals)
+    inline int get_pc () const
     {
-      subroutine = npc;
-      // Mark all items as unchanged.  Each subroutine needs to keep
-      // track of its `changed' state independently.  In the case of
-      // nested subroutines, this information will be merged back into
-      // parent by the `ret'.
-      for (int i = 0; i < max_locals; ++i)
-	flags[i] &= ~FLAG_CHANGED;
+      return pc;
     }
 
-    // Indicate that we've been in this this subroutine.
-    void add_subr (int pc)
+    void set_pc (int npc)
     {
-      subr_info *n = (subr_info *) _Jv_Malloc (sizeof (subr_info));
-      n->pc = pc;
-      n->next = seen_subrs;
-      seen_subrs = n;
+      pc = npc;
     }
 
     // Merge STATE_OLD into this state.  Destructively modifies this
     // state.  Returns true if the new state was in fact changed.
     // Will throw an exception if the states are not mergeable.
-    bool merge (state *state_old, bool ret_semantics,
-		int max_locals, _Jv_BytecodeVerifier *verifier,
-		bool jsr_semantics = false)
+    bool merge (state *state_old, int max_locals,
+		_Jv_BytecodeVerifier *verifier)
     {
       bool changed = false;
 
@@ -1116,135 +1082,20 @@
       if (this_type.isinitialized ())
 	this_type = state_old->this_type;
 
-      // Merge subroutine states.  Here we just keep track of what
-      // subroutine we think we're in.  We only check for a merge
-      // (which is invalid) when we see a `ret'.
-      if (subroutine == state_old->subroutine)
-	{
-	  // Nothing.
-	}
-      else if (subroutine == 0)
-	{
-	  subroutine = state_old->subroutine;
-	  changed = true;
-	}
-      else
-	{
-	  // If the subroutines differ, and we haven't seen this
-	  // subroutine before, indicate that the state changed.  This
-	  // is needed to detect when subroutines have merged.
-	  bool found = false;
-	  for (subr_info *info = seen_subrs; info != NULL; info = info->next)
-	    {
-	      if (info->pc == state_old->subroutine)
-		{
-		  found = true;
-		  break;
-		}
-	    }
-	  if (! found)
-	    {
-	      add_subr (state_old->subroutine);
-	      changed = true;
-	    }
-	}
-
-      // Merge stacks, including special handling for NO_STACK case.
-      // If the destination is NO_STACK, this means it is the
-      // instruction following a "jsr" and has not yet been processed
-      // in any way.  In this situation, if we are currently
-      // processing a "ret", then we must *copy* any locals changed in
-      // the subroutine into the current state.  Merging in this
-      // situation is incorrect because the locals we've noted didn't
-      // come real program flow, they are just an artifact of how
-      // we've chosen to handle the post-jsr state.
-      bool copy_in_locals = ret_semantics && stacktop == NO_STACK;
-
-      if (state_old->stacktop == NO_STACK)
-	{
-	  // This can happen if we're doing a pass-through jsr merge.
-	  // Here we can just ignore the stack.
-	}
-      else if (stacktop == NO_STACK)
-	{
-	  stacktop = state_old->stacktop;
-	  stackdepth = state_old->stackdepth;
-	  for (int i = 0; i < stacktop; ++i)
-	    stack[i] = state_old->stack[i];
-	  changed = true;
-	}
-      else if (state_old->stacktop != stacktop)
+      // Merge stacks.
+      if (state_old->stacktop != stacktop)  // FIXME stackdepth instead?
 	verifier->verify_fail ("stack sizes differ");
-      else
+      for (int i = 0; i < state_old->stacktop; ++i)
 	{
-	  for (int i = 0; i < state_old->stacktop; ++i)
-	    {
-	      if (stack[i].merge (state_old->stack[i], false, verifier))
-		changed = true;
-	    }
+	  if (stack[i].merge (state_old->stack[i], false, verifier))
+	    changed = true;
 	}
 
       // Merge local variables.
       for (int i = 0; i < max_locals; ++i)
 	{
-	  // If we're not processing a `ret', then we merge every
-	  // local variable.  If we are processing a `ret', then we
-	  // only merge locals which changed in the subroutine.  When
-	  // processing a `ret', STATE_OLD is the state at the point
-	  // of the `ret', and THIS is the state just after the `jsr'.
-	  // See comment above for explanation of COPY_IN_LOCALS.
-	  if (copy_in_locals)
-	    {
-	      if ((state_old->flags[i] & FLAG_CHANGED) != 0)
-		{
-		  locals[i] = state_old->locals[i];
-		  changed = true;
-		  // There's no point in calling note_variable here,
-		  // since we call it under the same condition before
-		  // the loop ends.
-		}
-	    }
-	  else if (jsr_semantics && (flags[i] & FLAG_USED) != 0)
-	    {
-	      // We are processing the "pass-through" part of a jsr
-	      // statement.  In this particular case, the local was
-	      // changed by the subroutine.  So, we have no work to
-	      // do, as the pre-jsr value does not survive the
-	      // subroutine call.
-	    }
-	  else if (! ret_semantics
-		   || (state_old->flags[i] & FLAG_CHANGED) != 0)
-	    {
-	      // If we have ordinary (not ret) semantics, then we have
-	      // merging flow control, so we merge types.  Or, we have
-	      // jsr pass-through semantics and the type survives the
-	      // subroutine (see above), so again we merge.  Or,
-	      // finally, we have ret semantics and this value did
-	      // change, in which case we merge the change from the
-	      // subroutine into the post-jsr instruction.
-	      if (locals[i].merge (state_old->locals[i], true, verifier))
-		{
-		  // Note that we don't call `note_variable' here.
-		  // This change doesn't represent a real change to a
-		  // local, but rather a merge artifact.  If we're in
-		  // a subroutine which is called with two
-		  // incompatible types in a slot that is unused by
-		  // the subroutine, then we don't want to mark that
-		  // variable as having been modified.
-		  changed = true;
-		}
-	    }
-
-	  // If we're in a subroutine, we must compute the union of
-	  // all the changed local variables.
-	  if ((state_old->flags[i] & FLAG_CHANGED) != 0)
-	    note_variable (i);
-
-	  // If we're returning from a subroutine, we must mark the
-	  // post-jsr instruction with information about what changed,
-	  // so that future "pass-through" jsr merges work correctly.
-	  if (ret_semantics && (state_old->flags[i] & FLAG_CHANGED) != 0)
-	    flags[i] |= FLAG_USED;
+	  if (locals[i].merge (state_old->locals[i], true, verifier))
+	    changed = true;
 	}
 
       return changed;
@@ -1285,13 +1136,6 @@
       this_type = k;
     }
 
-    // Note that a local variable was modified.
-    void note_variable (int index)
-    {
-      if (subroutine > 0)
-	flags[index] |= FLAG_CHANGED;
-    }
-
     // Mark each `new'd object we know of that was allocated at PC as
     // initialized.
     void set_initialized (int pc, int max_locals)
@@ -1303,17 +1147,36 @@
       this_type.set_initialized (pc);
     }
 
-    // Return true if this state is the unmerged result of a `ret'.
-    bool is_unmerged_ret_state (int max_locals) const
-    {
-      if (stacktop == NO_STACK)
-	return true;
+    // This tests to see whether two states can be considered "merge
+    // compatible".  If both states have a return-address in the same
+    // slot, and the return addresses are different, then they are not
+    // compatible and we must not try to merge them.
+    bool state_mergeable_p (state *other, int max_locals,
+			    _Jv_BytecodeVerifier *verifier)
+    {
+      // This is tricky: if the stack sizes differ, then not only are
+      // these not mergeable, but in fact we should give an error, as
+      // we've found two execution paths that reach a branch target
+      // with different stack depths.  FIXME stackdepth instead?
+      if (stacktop != other->stacktop)
+	verifier->verify_fail ("stack sizes differ");
+
+      for (int i = 0; i < stacktop; ++i)
+	if (! stack[i].state_mergeable_p (other->stack[i]))
+	  return false;
       for (int i = 0; i < max_locals; ++i)
+	if (! locals[i].state_mergeable_p (other->locals[i]))
+	  return false;
+      return true;
+    }
+
+    void reverify (_Jv_BytecodeVerifier *verifier)
+    {
+      if (next == INVALID_STATE)
 	{
-	  if (locals[i].key == unused_by_subroutine_type)
-	    return true;
+	  next = verifier->next_verify_state;
+	  verifier->next_verify_state = this;
 	}
-      return false;
     }
 
 #ifdef VERIFY_DEBUG
@@ -1328,17 +1191,7 @@
 	debug_print (".");
       debug_print ("    [local] ");
       for (i = 0; i < max_locals; ++i)
-	{
-	  locals[i].print ();
-	  if ((flags[i] & FLAG_USED) != 0)
-	    debug_print ((flags[i] & FLAG_CHANGED) ? ">" : "<");
-	  else
-	    debug_print ((flags[i] & FLAG_CHANGED) ? "+" : " ");
-	}
-      if (subroutine == 0)
-	debug_print ("   | None");
-      else
-	debug_print ("   | %4d", subroutine);
+	locals[i].print ();
       debug_print (" | %p\n", this);
     }
 #else
@@ -1419,18 +1272,11 @@
     if (index > current_method->max_locals - depth)
       verify_fail ("invalid local variable");
     current_state->locals[index] = t;
-    current_state->note_variable (index);
 
     if (depth == 2)
-      {
-	current_state->locals[index + 1] = continuation_type;
-	current_state->note_variable (index + 1);
-      }
+      current_state->locals[index + 1] = continuation_type;
     if (index > 0 && current_state->locals[index - 1].iswide ())
-      {
-	current_state->locals[index - 1] = unsuitable_type;
-	// There's no need to call note_variable here.
-      }
+      current_state->locals[index - 1] = unsuitable_type;
   }
 
   type get_variable (int index, type t)
@@ -1520,56 +1366,71 @@
     return npc;
   }
 
+  // Add a new state to the state list at NPC.
+  state *add_new_state (int npc, state *old_state)
+  {
+    state *new_state = new state (old_state, current_method->max_stack,
+				  current_method->max_locals);
+    debug_print ("== New state in add_new_state\n");
+    new_state->print ("New", npc, current_method->max_stack,
+		      current_method->max_locals);
+    linked<state> *nlink
+      = (linked<state> *) _Jv_Malloc (sizeof (linked<state>));
+    nlink->val = new_state;
+    nlink->next = states[npc];
+    states[npc] = nlink;
+    new_state->set_pc (npc);
+    return new_state;
+  }
+
   // Merge the indicated state into the state at the branch target and
-  // schedule a new PC if there is a change.  If RET_SEMANTICS is
-  // true, then we are merging from a `ret' instruction into the
-  // instruction after a `jsr'.  This is a special case with its own
-  // modified semantics.  If JSR_SEMANTICS is true, then we're merging
-  // some type information from a "jsr" instruction to the immediately
-  // following instruction.  In this situation we have to be careful
-  // not to merge local variables whose values are modified by the
-  // subroutine we're about to call.
-  void push_jump_merge (int npc, state *nstate,
-			bool ret_semantics = false,
-			bool jsr_semantics = false)
-  {
-    bool changed = true;
-    if (states[npc] == NULL)
-      {
-	// There's a weird situation here.  If are examining the
-	// branch that results from a `ret', and there is not yet a
-	// state available at the branch target (the instruction just
-	// after the `jsr'), then we have to construct a special kind
-	// of state at that point for future merging.  This special
-	// state has the type `unused_by_subroutine_type' in each slot
-	// which was not modified by the subroutine.
-	states[npc] = new state (nstate, current_method->max_stack,
-				 current_method->max_locals, ret_semantics);
-	debug_print ("== New state in push_jump_merge (ret_semantics = %s)\n",
-		     ret_semantics ? "true" : "false");
-	states[npc]->print ("New", npc, current_method->max_stack,
-			    current_method->max_locals);
+  // schedule a new PC if there is a change.  NPC is the PC of the
+  // branch target, and FROM_STATE is the state at the source of the
+  // branch.  This method returns true if the destination state
+  // changed and requires reverification, false otherwise.
+  void merge_into (int npc, state *from_state)
+  {
+    // Iterate over all target states and merge our state into each,
+    // if applicable.  FIXME one improvement we could make here is
+    // "state destruction".  Merging a new state into an existing one
+    // might cause a return_address_type to be merged to
+    // unsuitable_type.  In this case the resulting state may now be
+    // mergeable with other states currently held in parallel at this
+    // location.  So in this situation we could pairwise compare and
+    // reduce the number of parallel states.
+    bool applicable = false;
+    for (linked<state> *iter = states[npc]; iter != NULL; iter = iter->next)
+      {
+	state *new_state = iter->val;
+	if (new_state->state_mergeable_p (from_state,
+					  current_method->max_locals, this))
+	  {
+	    applicable = true;
+
+	    debug_print ("== Merge states in merge_into\n");
+	    from_state->print ("Frm", start_PC, current_method->max_stack,
+			       current_method->max_locals);
+	    new_state->print (" To", npc, current_method->max_stack,
+			      current_method->max_locals);
+	    bool changed = new_state->merge (from_state,
+					     current_method->max_locals,
+					     this);
+	    new_state->print ("New", npc, current_method->max_stack,
+			      current_method->max_locals);
+
+	    if (changed)
+	      new_state->reverify (this);
+	  }
       }
-    else
+
+    if (! applicable)
       {
-	debug_print ("== Merge states in push_jump_merge\n");
-	nstate->print ("Frm", start_PC, current_method->max_stack,
-		       current_method->max_locals);
-	states[npc]->print (" To", npc, current_method->max_stack,
-			    current_method->max_locals);
-	changed = states[npc]->merge (nstate, ret_semantics,
-				      current_method->max_locals, this,
-				      jsr_semantics);
-	states[npc]->print ("New", npc, current_method->max_stack,
-			    current_method->max_locals);
-      }
-
-    if (changed && states[npc]->next == state::INVALID)
-      {
-	// The merge changed the state, and the new PC isn't yet on our
-	// list of PCs to re-verify.
-	states[npc]->next = next_verify_pc;
-	next_verify_pc = npc;
+	// Either we don't yet have a state at NPC, or we have a
+	// return-address type that is in conflict with all existing
+	// state.  So, we need to create a new entry.
+	state *new_state = add_new_state (npc, from_state);
+	// A new state added in this way must always be reverified.
+	new_state->reverify (this);
       }
   }
 
@@ -1578,7 +1439,7 @@
     int npc = compute_jump (offset);
     if (npc < PC)
       current_state->check_no_uninitialized_objects (current_method->max_locals, this);
-    push_jump_merge (npc, current_state);
+    merge_into (npc, current_state);
   }
 
   void push_exception_jump (type t, int pc)
@@ -1590,37 +1451,20 @@
     if (current_method->max_stack < 1)
       verify_fail ("stack overflow at exception handler");
     s.set_exception (t, current_method->max_stack);
-    push_jump_merge (pc, &s);
+    merge_into (pc, &s);
   }
 
-  int pop_jump ()
+  state *pop_jump ()
   {
-    int *prev_loc = &next_verify_pc;
-    int npc = next_verify_pc;
-
-    while (npc != state::NO_NEXT)
+    state *new_state = next_verify_state;
+    if (new_state == INVALID_STATE)
+      verify_fail ("programmer error in pop_jump");
+    if (new_state != NULL)
       {
-	// If the next available PC is an unmerged `ret' state, then
-	// we aren't yet ready to handle it.  That's because we would
-	// need all kind of special cases to do so.  So instead we
-	// defer this jump until after we've processed it via a
-	// fall-through.  This has to happen because the instruction
-	// before this one must be a `jsr'.
-	if (! states[npc]->is_unmerged_ret_state (current_method->max_locals))
-	  {
-	    *prev_loc = states[npc]->next;
-	    states[npc]->next = state::INVALID;
-	    return npc;
-	  }
-
-	prev_loc = &states[npc]->next;
-	npc = states[npc]->next;
+	next_verify_state = new_state->next;
+	new_state->next = INVALID_STATE;
       }
-
-    // Note that we might have gotten here even when there are
-    // remaining states to process.  That can happen if we find a
-    // `jsr' without a `ret'.
-    return state::NO_NEXT;
+    return new_state;
   }
 
   void invalidate_pc ()
@@ -1628,7 +1472,7 @@
     PC = state::NO_NEXT;
   }
 
-  void note_branch_target (int pc, bool is_jsr_target = false)
+  void note_branch_target (int pc)
   {
     // Don't check `pc <= PC', because we've advanced PC after
     // fetching the target and we haven't yet checked the next
@@ -1636,14 +1480,6 @@
     if (pc < PC && ! (flags[pc] & FLAG_INSN_START))
       verify_fail ("branch not to instruction start", start_PC);
     flags[pc] |= FLAG_BRANCH_TARGET;
-    if (is_jsr_target)
-      {
-	// Record the jsr which called this instruction.
-	subr_info *info = (subr_info *) _Jv_Malloc (sizeof (subr_info));
-	info->pc = PC;
-	info->next = jsr_ptrs[pc];
-	jsr_ptrs[pc] = info;
-      }
   }
 
   void skip_padding ()
@@ -1653,108 +1489,43 @@
 	verify_fail ("found nonzero padding byte");
   }
 
-  // Return the subroutine to which the instruction at PC belongs.
-  int get_subroutine (int pc)
-  {
-    if (states[pc] == NULL)
-      return 0;
-    return states[pc]->subroutine;
-  }
-
   // Do the work for a `ret' instruction.  INDEX is the index into the
   // local variables.
   void handle_ret_insn (int index)
   {
-    get_variable (index, return_address_type);
-
-    int csub = current_state->subroutine;
-    if (csub == 0)
-      verify_fail ("no subroutine");
+    type ret_addr = get_variable (index, return_address_type);
+    // It would be nice if we could do this.  However, the JVM Spec
+    // doesn't say that this is what happens.  It is implied that
+    // reusing a return address is invalid, but there's no actual
+    // prohibition against it.
+    // set_variable (index, unsuitable_type);
+
+    int npc = ret_addr.get_pc ();
+    // We might be returning to a `jsr' that is at the end of the
+    // bytecode.  This is ok if we never return from the called
+    // subroutine, but if we see this here it is an error.
+    if (npc >= current_method->code_length)
+      verify_fail ("fell off end");
 
-    // Check to see if we've merged subroutines.
-    subr_entry_info *entry;
-    for (entry = entry_points; entry != NULL; entry = entry->next)
-      {
-	if (entry->ret_pc == start_PC)
-	  break;
-      }
-    if (entry == NULL)
-      {
-	entry = (subr_entry_info *) _Jv_Malloc (sizeof (subr_entry_info));
-	entry->pc = csub;
-	entry->ret_pc = start_PC;
-	entry->next = entry_points;
-	entry_points = entry;
-      }
-    else if (entry->pc != csub)
-      verify_fail ("subroutines merged");
-
-    for (subr_info *subr = jsr_ptrs[csub]; subr != NULL; subr = subr->next)
-      {
-	// We might be returning to a `jsr' that is at the end of the
-	// bytecode.  This is ok if we never return from the called
-	// subroutine, but if we see this here it is an error.
-	if (subr->pc >= current_method->code_length)
-	  verify_fail ("fell off end");
-
-	// Temporarily modify the current state so it looks like we're
-	// in the enclosing context.
-	current_state->subroutine = get_subroutine (subr->pc);
-	if (subr->pc < PC)
-	  current_state->check_no_uninitialized_objects (current_method->max_locals, this);
-	push_jump_merge (subr->pc, current_state, true);
-      }
-
-    current_state->subroutine = csub;
+    if (npc < PC)
+      current_state->check_no_uninitialized_objects (current_method->max_locals,
+						     this);
+    merge_into (npc, current_state);
     invalidate_pc ();
   }
 
-  // We're in the subroutine SUB, calling a subroutine at DEST.  Make
-  // sure this subroutine isn't already on the stack.
-  void check_nonrecursive_call (int sub, int dest)
-  {
-    if (sub == 0)
-      return;
-    if (sub == dest)
-      verify_fail ("recursive subroutine call");
-    for (subr_info *info = jsr_ptrs[sub]; info != NULL; info = info->next)
-      check_nonrecursive_call (get_subroutine (info->pc), dest);
-  }
-
   void handle_jsr_insn (int offset)
   {
     int npc = compute_jump (offset);
 
     if (npc < PC)
       current_state->check_no_uninitialized_objects (current_method->max_locals, this);
-    check_nonrecursive_call (current_state->subroutine, npc);
 
     // Modify our state as appropriate for entry into a subroutine.
-    push_type (return_address_type);
-    push_jump_merge (npc, current_state);
-    // Clean up.
-    pop_type (return_address_type);
-
-    // On entry to the subroutine, the subroutine number must be set
-    // and the locals must be marked as cleared.  We do this after
-    // merging state so that we don't erroneously "notice" a variable
-    // change merely on entry.
-    states[npc]->enter_subroutine (npc, current_method->max_locals);
-
-    // Indicate that we don't know the stack depth of the instruction
-    // following the `jsr'.  The idea here is that we need to merge
-    // the local variable state across the jsr, but the subroutine
-    // might change the stack depth, so we can't make any assumptions
-    // about it.  So we have yet another special case.  We know that
-    // at this point PC points to the instruction after the jsr.  Note
-    // that it is ok to have a `jsr' at the end of the bytecode,
-    // provided that the called subroutine never returns.  So, we have
-    // a special case here and another one when we handle the ret.
-    if (PC < current_method->code_length)
-      {
-	current_state->stacktop = state::NO_STACK;
-	push_jump_merge (PC, current_state, false, true);
-      }
+    type ret_addr (return_address_type);
+    ret_addr.set_return_address (PC);
+    push_type (ret_addr);
+    merge_into (npc, current_state);
     invalidate_pc ();
   }
 
@@ -1794,7 +1565,6 @@
       case unsuitable_type:
       case return_address_type:
       case continuation_type:
-      case unused_by_subroutine_type:
       case reference_type:
       case null_type:
       case uninitialized_reference_type:
@@ -1810,16 +1580,9 @@
   void branch_prepass ()
   {
     flags = (char *) _Jv_Malloc (current_method->code_length);
-    jsr_ptrs = (subr_info **) _Jv_Malloc (sizeof (subr_info *)
-					  * current_method->code_length);
 
     for (int i = 0; i < current_method->code_length; ++i)
-      {
-	flags[i] = 0;
-	jsr_ptrs[i] = NULL;
-      }
-
-    bool last_was_jsr = false;
+      flags[i] = 0;
 
     PC = 0;
     while (PC < current_method->code_length)
@@ -1829,13 +1592,6 @@
 	start_PC = PC;
 	flags[PC] |= FLAG_INSN_START;
 
-	// If the previous instruction was a jsr, then the next
-	// instruction is a branch target -- the branch being the
-	// corresponding `ret'.
-	if (last_was_jsr)
-	  note_branch_target (PC);
-	last_was_jsr = false;
-
 	java_opcode opcode = (java_opcode) bytecode[PC++];
 	switch (opcode)
 	  {
@@ -2029,8 +1785,6 @@
 	    break;
 
 	  case op_jsr:
-	    last_was_jsr = true;
-	    // Fall through.
 	  case op_ifeq:
 	  case op_ifne:
 	  case op_iflt:
@@ -2048,7 +1802,7 @@
 	  case op_ifnull:
 	  case op_ifnonnull:
 	  case op_goto:
-	    note_branch_target (compute_jump (get_short ()), last_was_jsr);
+	    note_branch_target (compute_jump (get_short ()));
 	    break;
 
 	  case op_tableswitch:
@@ -2095,10 +1849,8 @@
 	    break;
 
 	  case op_jsr_w:
-	    last_was_jsr = true;
-	    // Fall through.
 	  case op_goto_w:
-	    note_branch_target (compute_jump (get_int ()), last_was_jsr);
+	    note_branch_target (compute_jump (get_int ()));
 	    break;
 
 	  // These are unused here, but we call them out explicitly
@@ -2375,37 +2127,31 @@
     // True if we are verifying an instance initializer.
     bool this_is_init = initialize_stack ();
 
-    states = (state **) _Jv_Malloc (sizeof (state *)
-				    * current_method->code_length);
+    states = (linked<state> **) _Jv_Malloc (sizeof (linked<state> *)
+					    * current_method->code_length);
     for (int i = 0; i < current_method->code_length; ++i)
       states[i] = NULL;
 
-    next_verify_pc = state::NO_NEXT;
+    next_verify_state = NULL;
 
     while (true)
       {
 	// If the PC was invalidated, get a new one from the work list.
 	if (PC == state::NO_NEXT)
 	  {
-	    PC = pop_jump ();
-	    if (PC == state::INVALID)
-	      verify_fail ("can't happen: saw state::INVALID");
-	    if (PC == state::NO_NEXT)
+	    state *new_state = pop_jump ();
+	    // If it is null, we're done.
+	    if (new_state == NULL)
 	      break;
+
+	    PC = new_state->get_pc ();
 	    debug_print ("== State pop from pending list\n");
 	    // Set up the current state.
-	    current_state->copy (states[PC], current_method->max_stack,
+	    current_state->copy (new_state, current_method->max_stack,
 				 current_method->max_locals);
 	  }
 	else
 	  {
-	    // Control can't fall off the end of the bytecode.  We
-	    // only need to check this in the fall-through case,
-	    // because branch bounds are checked when they are
-	    // pushed.
-	    if (PC >= current_method->code_length)
-	      verify_fail ("fell off end");
-
 	    // We only have to do this checking in the situation where
 	    // control flow falls through from the previous
 	    // instruction.  Otherwise merging is done at the time we
@@ -2413,39 +2159,29 @@
 	    if (states[PC] != NULL)
 	      {
 		// We've already visited this instruction.  So merge
-		// the states together.  If this yields no change then
-		// we don't have to re-verify.  However, if the new
-		// state is an the result of an unmerged `ret', we
-		// must continue through it.
-		debug_print ("== Fall through merge\n");
-		states[PC]->print ("Old", PC, current_method->max_stack,
-				   current_method->max_locals);
-		current_state->print ("Cur", PC, current_method->max_stack,
-				      current_method->max_locals);
-		if (! current_state->merge (states[PC], false,
-					    current_method->max_locals, this)
-		    && ! states[PC]->is_unmerged_ret_state (current_method->max_locals))
-		  {
-		    debug_print ("== Fall through optimization\n");
-		    invalidate_pc ();
-		    continue;
-		  }
-		// Save a copy of it for later.
-		states[PC]->copy (current_state, current_method->max_stack,
-				  current_method->max_locals);
-		current_state->print ("New", PC, current_method->max_stack,
-				      current_method->max_locals);
+		// the states together.  It is simplest, but not most
+		// efficient, to just always invalidate the PC here.
+		merge_into (PC, current_state);
+		invalidate_pc ();
+		continue;
 	      }
 	  }
 
+	// Control can't fall off the end of the bytecode.  We need to
+	// check this in both cases, not just the fall-through case,
+	// because we don't check to see whether a `jsr' appears at
+	// the end of the bytecode until we process a `ret'.
+	if (PC >= current_method->code_length)
+	  verify_fail ("fell off end");
+
 	// We only have to keep saved state at branch targets.  If
 	// we're at a branch target and the state here hasn't been set
-	// yet, we set it now.
+	// yet, we set it now.  You might notice that `ret' targets
+	// won't necessarily have FLAG_BRANCH_TARGET set.  This
+	// doesn't matter, since those states will be filled in by
+	// merge_into.
 	if (states[PC] == NULL && (flags[PC] & FLAG_BRANCH_TARGET))
-	  {
-	    states[PC] = new state (current_state, current_method->max_stack,
-				    current_method->max_locals);
-	  }
+	  add_new_state (PC, current_state);
 
 	// Set this before handling exceptions so that debug output is
 	// sane.
@@ -3328,58 +3064,45 @@
 
     states = NULL;
     flags = NULL;
-    jsr_ptrs = NULL;
     utf8_list = NULL;
     isect_list = NULL;
-    entry_points = NULL;
   }
 
   ~_Jv_BytecodeVerifier ()
   {
-    if (states)
-      _Jv_Free (states);
     if (flags)
       _Jv_Free (flags);
 
-    if (jsr_ptrs)
-      {
-	for (int i = 0; i < current_method->code_length; ++i)
-	  {
-	    if (jsr_ptrs[i] != NULL)
-	      {
-		subr_info *info = jsr_ptrs[i];
-		while (info != NULL)
-		  {
-		    subr_info *next = info->next;
-		    _Jv_Free (info);
-		    info = next;
-		  }
-	      }
-	  }
-	_Jv_Free (jsr_ptrs);
-      }
-
     while (utf8_list != NULL)
       {
-	linked_utf8 *n = utf8_list->next;
+	linked<_Jv_Utf8Const> *n = utf8_list->next;
 	_Jv_Free (utf8_list->val);
 	_Jv_Free (utf8_list);
 	utf8_list = n;
       }
 
-    while (entry_points != NULL)
-      {
-	subr_entry_info *next = entry_points->next;
-	_Jv_Free (entry_points);
-	entry_points = next;
-      }
-
     while (isect_list != NULL)
       {
 	ref_intersection *next = isect_list->alloc_next;
 	delete isect_list;
 	isect_list = next;
       }
+
+    if (states)
+      {
+	for (int i = 0; i < current_method->code_length; ++i)
+	  {
+	    linked<state> *iter = states[i];
+	    while (iter != NULL)
+	      {
+		linked<state> *next = iter->next;
+		delete iter->val;
+		_Jv_Free (iter);
+		iter = next;
+	      }
+	  }
+	_Jv_Free (states);
+      }
   }
 };
 
@@ -3389,4 +3112,5 @@
   _Jv_BytecodeVerifier v (meth);
   v.verify_instructions ();
 }
+
 #endif	/* INTERPRETER */
Index: testsuite/libjava.lang/pr13107.java
===================================================================
RCS file: testsuite/libjava.lang/pr13107.java
diff -N testsuite/libjava.lang/pr13107.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107.java 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1,25 @@
+class pr13107
+{
+  public static void main(String[] args)
+  {
+    for (int i = 0; i < 1; i++) {
+      String s = "A";
+
+      if (s == "A")
+        continue;
+      
+      try{
+	try{
+          System.out.println(s);
+	}
+	finally{
+	  if (s != "A")
+	    throw new Error();
+	}
+      }
+      catch(Exception e){
+	s = "B";
+      }
+    }
+  }
+}
Index: testsuite/libjava.lang/pr13107.out
===================================================================
RCS file: testsuite/libjava.lang/pr13107.out
diff -N testsuite/libjava.lang/pr13107.out
Index: testsuite/libjava.lang/pr13107_2.java
===================================================================
RCS file: testsuite/libjava.lang/pr13107_2.java
diff -N testsuite/libjava.lang/pr13107_2.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107_2.java 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1,19 @@
+public class pr13107_2
+{
+  public static int foo (boolean b)
+  {
+    int i;
+    try {
+	if (b) return 1;
+	i= 2;
+      }
+    finally {
+      if (b) i = 3;
+    }
+    return i;
+  }
+
+  public static void main(String[] args)
+  {
+  }
+}
Index: testsuite/libjava.lang/pr13107_2.out
===================================================================
RCS file: testsuite/libjava.lang/pr13107_2.out
diff -N testsuite/libjava.lang/pr13107_2.out
Index: testsuite/libjava.lang/pr13107_2.xfail
===================================================================
RCS file: testsuite/libjava.lang/pr13107_2.xfail
diff -N testsuite/libjava.lang/pr13107_2.xfail
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107_2.xfail 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1 @@
+xfail-byte
Index: testsuite/libjava.lang/pr13107_3.java
===================================================================
RCS file: testsuite/libjava.lang/pr13107_3.java
diff -N testsuite/libjava.lang/pr13107_3.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107_3.java 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1,16 @@
+public class pr13107_3
+{
+  public static void main(String[] args)
+  {
+    for (int i = 0; i < 1; i++)
+      {
+	try {
+	  System.out.println(i);
+	}
+	finally {
+	  if (i == 3)
+	    continue;
+	}
+      }
+  }
+}
Index: testsuite/libjava.lang/pr13107_3.out
===================================================================
RCS file: testsuite/libjava.lang/pr13107_3.out
diff -N testsuite/libjava.lang/pr13107_3.out
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107_3.out 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1 @@
+0
Index: testsuite/libjava.lang/pr13107_3.xfail
===================================================================
RCS file: testsuite/libjava.lang/pr13107_3.xfail
diff -N testsuite/libjava.lang/pr13107_3.xfail
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ testsuite/libjava.lang/pr13107_3.xfail 23 Jan 2004 02:37:30 -0000
@@ -0,0 +1 @@
+xfail-byte


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]