Class StackMapGenerator

java.lang.Object
org.glavo.classfile.impl.StackMapGenerator

public final class StackMapGenerator extends Object
StackMapGenerator is responsible for stack map frames generation.

Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.

The frames computation consists of following steps:

  1. Detection of mandatory stack map frames offsets:
    • Mandatory stack map frame offsets include all jump and switch instructions targets, offsets immediately following "no control flow" and all exception table handlers.
    • Detection is performed in a single fast pass through the bytecode, with no auxiliary structures construction nor further instructions processing.
  2. Generator loop processing bytecode instructions:
    • Generator loop simulates sequence instructions processing effect on the actual stack and locals.
    • All mandatory frames detected in the step #1 are retro-filled (or reverse-merged in subsequent processing) with the actual stack and locals for all matching jump, switch and exception handler targets.
    • All frames modified by reverse merges are marked as dirty for further processing.
    • Code blocks with not yet known entry frame content are skipped and related frames are also marked as dirty.
    • Generator loop process is repeated until all mandatory frames are cleared or until an error state is reached.
    • Generator loop always passes all instructions at least once to calculate max stack and max locals code attributes.
    • More than one pass is usually not necessary, except for more complex bytecode sequences.
      (Note: experimental measurements showed that more than 99% of the cases required only single pass to clear all frames, less than 1% of the cases required second pass and remaining 0,01% of the cases required third pass to clear all frames.).
  3. Dead code patching to pass class loading verification:
    • Dead code blocks are indicated by frames remaining without content after leaving the Generator loop.
    • Each dead code block is filled with NOP instructions, terminated with ATHROW instruction, and removed from exception handlers table.
    • Dead code block entry frame is set to java.lang.Throwable single stack item and no locals.

Reverse-merge of the stack map frames may in some situations require to determine class hierarchy relations.

Reverse-merge of individual types is performed when a target frame has already been retro-filled and it is necessary to adjust its existing stack entries and locals to also match actual stack map frame conditions. Following tables describe how new target stack entry or local type is calculated, based on the actual frame stack entry or local ("from") and actual value of the target stack entry or local ("to").

Reverse-merge of general type categories
to \ fromTOPPRIMITIVEUNINITIALIZEDREFERENCE
TOPTOPTOPTOPTOP
PRIMITIVETOPReverse-merge of primitive typesTOPTOP
UNINITIALIZEDTOPTOPIs NEW offset matching ? UNINITIALIZED : TOPTOP
REFERENCETOPTOPTOPReverse-merge of reference types

Reverse-merge of primitive types
to \ fromSHORTBYTEBOOLEANLONGDOUBLEFLOATINTEGER
SHORTSHORTTOPTOPTOPTOPTOPSHORT
BYTETOPBYTETOPTOPTOPTOPBYTE
BOOLEANTOPTOPBOOLEANTOPTOPTOPBOOLEAN
LONGTOPTOPTOPLONGTOPTOPTOP
DOUBLETOPTOPTOPTOPDOUBLETOPTOP
FLOATTOPTOPTOPTOPTOPFLOATTOP
INTEGERTOPTOPTOPTOPTOPTOPINTEGER

Reverse merge of reference types
to \ fromNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACE*OBJECT**
NULLNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACEOBJECT
j.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object
j.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Objectj.l.Cloneablej.l.Cloneable
j.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.l.Objectj.i.Serializablej.i.Serializable
ARRAYARRAYj.l.Objectj.l.Objectj.l.ObjectReverse merge of arraysj.l.Objectj.l.Object
INTERFACE*INTERFACEj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object
OBJECT**OBJECTj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.ObjectResolved common ancestor
*any interface reference except for j.l.Cloneable and j.i.Serializable
**any object reference except for j.l.Object

Array types are reverse-merged as reference to array type constructed from reverse-merged components. Reference to j.l.Object is an alternate result when construction of the array type is not possible (when reverse-merge of components returned TOP or other non-reference and non-primitive type).

Custom class hierarchy resolver has been implemented as a part of the library to avoid heavy class loading and to allow stack maps generation even for code with incomplete dependency classpath. However stack maps generated with warnings of unresolved dependencies may later fail to verify during class loading process.

Focus of the whole algorithm is on high performance and low memory footprint:

  • It does not produce, collect nor visit any complex intermediate structures (beside traversing the bytecode in binary form).
  • It works with only minimal mandatory stack map frames.
  • It does not spend time on any non-essential verifications.

In case of an exception during the Generator loop there is just minimal information available in the exception message.

To determine root cause of the exception it is recommended to enable debug logging of the Generator in one of the two modes using following java.lang.System properties:

-Dorg.glavo.classfile.impl.StackMapGenerator.DEBUG=true
Activates debug logging with basic information + generated stack map frames in case of success. It also re-runs with enabled full trace logging in case of an error or exception.
-Dorg.glavo.classfile.impl.StackMapGenerator.TRACE=true
Activates full detailed tracing of the generator process for all invocations.
  • Constructor Details

    • StackMapGenerator

      public StackMapGenerator(LabelContext labelContext, ClassDesc thisClass, String methodName, MethodTypeDesc methodDesc, boolean isStatic, ByteBuffer bytecode, SplitConstantPool cp, List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers)
      Primary constructor of the Generator class. New Generator instance must be created for each individual class/method. Instance contains only immutable results, all the calculations are processed during instance construction.
      Parameters:
      labelContext - LableContext instance used to resolve or patch ExceptionHandler labels to bytecode offsets (or vice versa)
      thisClass - class to generate stack maps for
      methodName - method name to generate stack maps for
      methodDesc - method descriptor to generate stack maps for
      isStatic - information whether the method is static
      bytecode - R/W ByteBuffer wrapping method bytecode, the content is altered in case Generator detects and patches dead code
      cp - R/W ConstantPoolBuilder instance used to resolve all involved CP entries and also generate new entries referenced from the generted stack maps
      handlers - R/W ExceptionHandler list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers and also to be altered when dead code is detected and must be excluded from exception handlers
  • Method Details

    • maxLocals

      public int maxLocals()
      Calculated maximum number of the locals required
      Returns:
      maximum number of the locals required
    • maxStack

      public int maxStack()
      Calculated maximum stack size required
      Returns:
      maximum stack size required
    • stackMapTableAttribute

      public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute()
      Getter of the generated StackMapTableAttribute or null if stack map is empty
      Returns:
      StackMapTableAttribute or null if stack map is empty