At each point of the bytecode, each element in the locales and the operand stack has an implicit type. In the old system, the verifier calculated these types as they arrived, but if the control flow goes back, which can change the types in the target value, which means that it had to iterate before approaching.
Types are now explicitly specified at such transition points. The verifier makes a single, linear pass through the bytecode. Whenever it hits the stack frame, it claims that the current put types are compatible with the explicit types in the stack stack, and then it continues using the types of stack frames. Whenever he jumps to a jump, he claims that the stack frame at the target of the jump has types compatible with the current supposed types.
In fact, the frames of the stack clearly preserve the results of "iteration to convergence", which means that instead of calculating them, the verifier simply checks the correctness of the results, which can be done in one pass.
In addition, new class classes are not allowed to use the jsr and ret commands, which greatly simplifies validation.
As a specific example, suppose you have code like the following
.method static foo : ()V L0: aconst_null L1: astore_0 L2: new Foo L3: dup L4: invokespecial Method Foo <init> ()V L5: astore_0 L6: goto L2 .end method
When checking the output, verfier initially prints the type var 0 as NULL on L2. Once he reaches L6, he should go back and change the type to Foo.
When checking the stack map, the verifier again initially displays the type var 0 as NULL on L2. However, he sees that there is a stack stack on L2 and checks that type 0 is in the stack frame. Whatever it is, it sets 0 for this type and continues checking. When he gets to L6, he looks at the stack stack of the jump target (L2) and claims that type 0 on L6 (which is Foo) can be assigned to type 0 on L2 (indicated on the L2 frame stack).
Suppose the stack stack in L2 declares that 0 is of type Object. The stack verifier then displays the following types at each step.
L0: INVALID (unset) L1: INVALID (unset) L2: NULL (checks stack frame at L2) (assert that NULL is assignable to Object) L2: Object L3: Object L4: Object L5: Object L6: Foo (check stack frame at L2) (assert that Foo is assignable to Object)