Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
The frames computation consists of following steps:
- 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.
- 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.).
- 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
NOPinstructions, terminated withATHROWinstruction, and removed from exception handlers table. - Dead code block entry frame is set to
java.lang.Throwablesingle 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").
| to \ from | TOP | PRIMITIVE | UNINITIALIZED | REFERENCE |
|---|---|---|---|---|
| TOP | TOP | TOP | TOP | TOP |
| PRIMITIVE | TOP | Reverse-merge of primitive types | TOP | TOP |
| UNINITIALIZED | TOP | TOP | Is NEW offset matching ? UNINITIALIZED : TOP | TOP |
| REFERENCE | TOP | TOP | TOP | Reverse-merge of reference types |
| to \ from | SHORT | BYTE | BOOLEAN | LONG | DOUBLE | FLOAT | INTEGER |
|---|---|---|---|---|---|---|---|
| SHORT | SHORT | TOP | TOP | TOP | TOP | TOP | SHORT |
| BYTE | TOP | BYTE | TOP | TOP | TOP | TOP | BYTE |
| BOOLEAN | TOP | TOP | BOOLEAN | TOP | TOP | TOP | BOOLEAN |
| LONG | TOP | TOP | TOP | LONG | TOP | TOP | TOP |
| DOUBLE | TOP | TOP | TOP | TOP | DOUBLE | TOP | TOP |
| FLOAT | TOP | TOP | TOP | TOP | TOP | FLOAT | TOP |
| INTEGER | TOP | TOP | TOP | TOP | TOP | TOP | INTEGER |
| to \ from | NULL | j.l.Object | j.l.Cloneable | j.i.Serializable | ARRAY | INTERFACE* | OBJECT** |
|---|---|---|---|---|---|---|---|
| NULL | NULL | j.l.Object | j.l.Cloneable | j.i.Serializable | ARRAY | INTERFACE | OBJECT |
| j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object |
| j.l.Cloneable | j.l.Cloneable | j.l.Cloneable | j.l.Cloneable | j.l.Cloneable | j.l.Object | j.l.Cloneable | j.l.Cloneable |
| j.i.Serializable | j.i.Serializable | j.i.Serializable | j.i.Serializable | j.i.Serializable | j.l.Object | j.i.Serializable | j.i.Serializable |
| ARRAY | ARRAY | j.l.Object | j.l.Object | j.l.Object | Reverse merge of arrays | j.l.Object | j.l.Object |
| INTERFACE* | INTERFACE | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object |
| OBJECT** | OBJECT | j.l.Object | j.l.Object | j.l.Object | j.l.Object | j.l.Object | Resolved 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.
-
Constructor Summary
ConstructorsConstructorDescriptionStackMapGenerator(LabelContext labelContext, ClassDesc thisClass, String methodName, MethodTypeDesc methodDesc, boolean isStatic, ByteBuffer bytecode, SplitConstantPool cp, ClassFileImpl context, List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) Primary constructor of theGeneratorclass. -
Method Summary
Modifier and TypeMethodDescriptionintCalculated maximum number of the locals requiredintmaxStack()Calculated maximum stack size requiredAttribute<? extends StackMapTableAttribute> Getter of the generatedStackMapTableAttributeor null if stack map is empty
-
Constructor Details
-
StackMapGenerator
public StackMapGenerator(LabelContext labelContext, ClassDesc thisClass, String methodName, MethodTypeDesc methodDesc, boolean isStatic, ByteBuffer bytecode, SplitConstantPool cp, ClassFileImpl context, List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) Primary constructor of theGeneratorclass. NewGeneratorinstance must be created for each individual class/method. Instance contains only immutable results, all the calculations are processed during instance construction.- Parameters:
labelContext-LabelContextinstance used to resolve or patchExceptionHandlerlabels to bytecode offsets (or vice versa)thisClass- class to generate stack maps formethodName- method name to generate stack maps formethodDesc- method descriptor to generate stack maps forisStatic- information whether the method is staticbytecode- R/WByteBufferwrapping method bytecode, the content is altered in caseGeneratordetects and patches dead codecp- R/WConstantPoolBuilderinstance used to resolve all involved CP entries and also generate new entries referenced from the generated stack mapshandlers- R/WExceptionHandlerlist 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
Getter of the generatedStackMapTableAttributeor null if stack map is empty- Returns:
StackMapTableAttributeor null if stack map is empty
-