1 /*
   2  * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.  Oracle designates this
   9  * particular file as subject to the "Classpath" exception as provided
  10  * by Oracle in the LICENSE file that accompanied this code.
  11  *
  12  * This code is distributed in the hope that it will be useful, but WITHOUT
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15  * version 2 for more details (a copy is included in the LICENSE file that
  16  * accompanied this code).
  17  *
  18  * You should have received a copy of the GNU General Public License version
  19  * 2 along with this work; if not, write to the Free Software Foundation,
  20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  21  *
  22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  23  * or visit www.oracle.com if you need additional information or have any
  24  * questions.
  25  *
  26  */
  27 package jdk.internal.classfile.impl;
  28 
  29 import java.lang.classfile.Attribute;
  30 import java.lang.classfile.Attributes;
  31 import java.lang.classfile.Label;
  32 import java.lang.classfile.attribute.StackMapTableAttribute;
  33 import java.lang.classfile.constantpool.*;
  34 import java.lang.constant.ClassDesc;
  35 import java.lang.constant.MethodTypeDesc;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.List;
  39 import java.util.Objects;
  40 import java.util.stream.Collectors;
  41 
  42 import jdk.internal.classfile.impl.WritableField.UnsetField;
  43 import jdk.internal.constant.ClassOrInterfaceDescImpl;
  44 import jdk.internal.util.Preconditions;
  45 
  46 import static java.lang.classfile.ClassFile.*;
  47 import static java.lang.classfile.constantpool.PoolEntry.*;
  48 import static java.lang.constant.ConstantDescs.*;
  49 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
  50 
  51 /**
  52  * StackMapGenerator is responsible for stack map frames generation.
  53  * <p>
  54  * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process.
  55  * <p>
  56  * The {@linkplain #generate() frames computation} consists of following steps:
  57  * <ol>
  58  * <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
  59  *      <li>Mandatory stack map frame include all jump and switch instructions targets,
  60  *          offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
  61  *          and all exception table handlers.
  62  *      <li>Detection is performed in a single fast pass through the bytecode,
  63  *          with no auxiliary structures construction nor further instructions processing.
  64  * </ul>
  65  * <li>Generator loop {@linkplain #processMethod() processing bytecode instructions}:<ul>
  66  *      <li>Generator loop simulates sequence instructions {@linkplain #processBlock(RawBytecodeHelper) processing effect on the actual stack and locals}.
  67  *      <li>All mandatory {@linkplain Frame frames} detected in the step #1 are {@linkplain Frame#checkAssignableTo(Frame) retro-filled}
  68  *          (or {@linkplain Frame#merge(Type, Type[], int, Frame) reverse-merged} in subsequent processing)
  69  *          with the actual stack and locals for all matching jump, switch and exception handler targets.
  70  *      <li>All frames modified by reverse merges are marked as {@linkplain Frame#dirty dirty} for further processing.
  71  *      <li>Code blocks with not yet known entry frame content are skipped and related frames are also marked as dirty.
  72  *      <li>Generator loop process is repeated until all mandatory frames are cleared or until an error state is reached.
  73  *      <li>Generator loop always passes all instructions at least once to calculate {@linkplain #maxStack max stack}
  74  *          and {@linkplain #maxLocals max locals} code attributes.
  75  *      <li>More than one pass is usually not necessary, except for more complex bytecode sequences.<br>
  76  *          <i>(Note: experimental measurements showed that more than 99% of the cases required only single pass to clear all frames,
  77  *          less than 1% of the cases required second pass and remaining 0,01% of the cases required third pass to clear all frames.)</i>.
  78  * </ul>
  79  * <li>Dead code patching to pass class loading verification:<ul>
  80  *      <li>Dead code blocks are indicated by frames remaining without content after leaving the Generator loop.
  81  *      <li>Each dead code block is filled with <code>NOP</code> instructions, terminated with
  82  *          <code>ATHROW</code> instruction, and removed from exception handlers table.
  83  *      <li>Dead code block entry frame is set to <code>java.lang.Throwable</code> single stack item and no locals.
  84  * </ul>
  85  * </ol>
  86  * <p>
  87  * {@linkplain Frame#merge(Type, Type[], int, Frame) Reverse-merge} of the stack map frames
  88  * may in some situations require to determine {@linkplain ClassHierarchyImpl class hierarchy} relations.
  89  * <p>
  90  * Reverse-merge of individual {@linkplain Type types} is performed when a target frame has already been retro-filled
  91  * and it is necessary to adjust its existing stack entries and locals to also match actual stack map frame conditions.
  92  * Following tables describe how new target stack entry or local type is calculated, based on the actual frame stack entry or local ("from")
  93  * and actual value of the target stack entry or local ("to").
  94  *
  95  * <table border="1">
  96  * <caption>Reverse-merge of general type categories</caption>
  97  * <tr><th>to \ from<th>TOP<th>PRIMITIVE<th>UNINITIALIZED<th>REFERENCE
  98  * <tr><th>TOP<td>TOP<td>TOP<td>TOP<td>TOP
  99  * <tr><th>PRIMITIVE<td>TOP<td><a href="#primitives">Reverse-merge of primitive types</a><td>TOP<td>TOP
 100  * <tr><th>UNINITIALIZED<td>TOP<td>TOP<td>Is NEW offset matching ? UNINITIALIZED : TOP<td>TOP
 101  * <tr><th>REFERENCE<td>TOP<td>TOP<td>TOP<td><a href="#references">Reverse-merge of reference types</a>
 102  * </table>
 103  * <p>
 104  * <table id="primitives" border="1">
 105  * <caption>Reverse-merge of primitive types</caption>
 106  * <tr><th>to \ from<th>SHORT<th>BYTE<th>BOOLEAN<th>LONG<th>DOUBLE<th>FLOAT<th>INTEGER
 107  * <tr><th>SHORT<td>SHORT<td>TOP<td>TOP<td>TOP<td>TOP<td>TOP<td>SHORT
 108  * <tr><th>BYTE<td>TOP<td>BYTE<td>TOP<td>TOP<td>TOP<td>TOP<td>BYTE
 109  * <tr><th>BOOLEAN<td>TOP<td>TOP<td>BOOLEAN<td>TOP<td>TOP<td>TOP<td>BOOLEAN
 110  * <tr><th>LONG<td>TOP<td>TOP<td>TOP<td>LONG<td>TOP<td>TOP<td>TOP
 111  * <tr><th>DOUBLE<td>TOP<td>TOP<td>TOP<td>TOP<td>DOUBLE<td>TOP<td>TOP
 112  * <tr><th>FLOAT<td>TOP<td>TOP<td>TOP<td>TOP<td>TOP<td>FLOAT<td>TOP
 113  * <tr><th>INTEGER<td>TOP<td>TOP<td>TOP<td>TOP<td>TOP<td>TOP<td>INTEGER
 114  * </table>
 115  * <p>
 116  * <table id="references" border="1">
 117  * <caption>Reverse merge of reference types</caption>
 118  * <tr><th>to \ from<th>NULL<th>j.l.Object<th>j.l.Cloneable<th>j.i.Serializable<th>ARRAY<th>INTERFACE*<th>OBJECT**
 119  * <tr><th>NULL<td>NULL<td>j.l.Object<td>j.l.Cloneable<td>j.i.Serializable<td>ARRAY<td>INTERFACE<td>OBJECT
 120  * <tr><th>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object
 121  * <tr><th>j.l.Cloneable<td>j.l.Cloneable<td>j.l.Cloneable<td>j.l.Cloneable<td>j.l.Cloneable<td>j.l.Object<td>j.l.Cloneable<td>j.l.Cloneable
 122  * <tr><th>j.i.Serializable<td>j.i.Serializable<td>j.i.Serializable<td>j.i.Serializable<td>j.i.Serializable<td>j.l.Object<td>j.i.Serializable<td>j.i.Serializable
 123  * <tr><th>ARRAY<td>ARRAY<td>j.l.Object<td>j.l.Object<td>j.l.Object<td><a href="#arrays">Reverse merge of arrays</a><td>j.l.Object<td>j.l.Object
 124  * <tr><th>INTERFACE*<td>INTERFACE<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object
 125  * <tr><th>OBJECT**<td>OBJECT<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>j.l.Object<td>Resolved common ancestor
 126  * <tr><td colspan="8">*any interface reference except for j.l.Cloneable and j.i.Serializable<br>**any object reference except for j.l.Object
 127  * </table>
 128  * <p id="arrays">
 129  * Array types are reverse-merged as reference to array type constructed from reverse-merged components.
 130  * 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).
 131  * <p>
 132  * Custom class hierarchy resolver has been implemented as a part of the library to avoid heavy class loading
 133  * and to allow stack maps generation even for code with incomplete dependency classpath.
 134  * However stack maps generated with {@linkplain ClassHierarchyImpl#resolve(java.lang.constant.ClassDesc) warnings of unresolved dependencies} may later fail to verify during class loading process.
 135  * <p>
 136  * Focus of the whole algorithm is on high performance and low memory footprint:<ul>
 137  *      <li>It does not produce, collect nor visit any complex intermediate structures
 138  *          <i>(beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}).</i>
 139  *      <li>It works with only minimal mandatory stack map frames.
 140  *      <li>It does not spend time on any non-essential verifications.
 141  * </ul>
 142  */
 143 
 144 public final class StackMapGenerator {
 145 
 146     static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) {
 147         return new StackMapGenerator(
 148                 dcb,
 149                 buf.thisClass().asSymbol(),
 150                 dcb.methodInfo.methodName().stringValue(),
 151                 dcb.methodInfo.methodTypeSymbol(),
 152                 (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0,
 153                 dcb.bytecodesBufWriter.bytecodeView(),
 154                 dcb.constantPool,
 155                 dcb.context,
 156                 buf.getStrictInstanceFields(),
 157                 dcb.handlers);
 158     }
 159 
 160     private static final String OBJECT_INITIALIZER_NAME = "<init>";
 161     private static final int FLAG_THIS_UNINIT = 0x01;
 162     private static final int FRAME_DEFAULT_CAPACITY = 10;
 163     private static final int T_BOOLEAN = 4, T_LONG = 11;
 164     private static final Frame[] EMPTY_FRAME_ARRAY = {};
 165 
 166     public static final int
 167             ITEM_TOP = 0,
 168             ITEM_INTEGER = 1,
 169             ITEM_FLOAT = 2,
 170             ITEM_DOUBLE = 3,
 171             ITEM_LONG = 4,
 172             ITEM_NULL = 5,
 173             ITEM_UNINITIALIZED_THIS = 6,
 174             ITEM_OBJECT = 7,
 175             ITEM_UNINITIALIZED = 8,
 176             ITEM_BOOLEAN = 9,
 177             ITEM_BYTE = 10,
 178             ITEM_SHORT = 11,
 179             ITEM_CHAR = 12,
 180             ITEM_LONG_2ND = 13,
 181             ITEM_DOUBLE_2ND = 14,
 182             ITEM_BOGUS = -1;
 183 
 184     // Ranges represented by these constants are inclusive on both ends
 185     public static final int
 186             SAME_FRAME_END = 63,
 187             SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64,
 188             SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127,
 189             RESERVED_END = 245,
 190             EARLY_LARVAL = 246,
 191             SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
 192             CHOP_FRAME_START = 248,
 193             CHOP_FRAME_END = 250,
 194             SAME_FRAME_EXTENDED = 251,
 195             APPEND_FRAME_START = 252,
 196             APPEND_FRAME_END = 254,
 197             FULL_FRAME = 255;
 198 
 199     private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null,
 200         Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE,
 201         Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE};
 202 
 203     static record RawExceptionCatch(int start, int end, int handler, Type catchType) {}
 204 
 205     private final Type thisType;
 206     private final String methodName;
 207     private final MethodTypeDesc methodDesc;
 208     private final RawBytecodeHelper.CodeRange bytecode;
 209     private final SplitConstantPool cp;
 210     private final boolean isStatic;
 211     private final LabelContext labelContext;
 212     private final List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers;
 213     private final List<RawExceptionCatch> rawHandlers;
 214     private final ClassHierarchyImpl classHierarchy;
 215     private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy!
 216     private final boolean patchDeadCode;
 217     private final boolean filterDeadLabels;
 218     private Frame[] frames = EMPTY_FRAME_ARRAY;
 219     private int framesCount = 0;
 220     private final Frame currentFrame;
 221     private int maxStack, maxLocals;
 222 
 223     /**
 224      * Primary constructor of the <code>Generator</code> class.
 225      * New <code>Generator</code> instance must be created for each individual class/method.
 226      * Instance contains only immutable results, all the calculations are processed during instance construction.
 227      *
 228      * @param labelContext <code>LabelContext</code> instance used to resolve or patch <code>ExceptionHandler</code>
 229      * labels to bytecode offsets (or vice versa)
 230      * @param thisClass class to generate stack maps for
 231      * @param methodName method name to generate stack maps for
 232      * @param methodDesc method descriptor to generate stack maps for
 233      * @param isStatic information whether the method is static
 234      * @param bytecode R/W <code>ByteBuffer</code> wrapping method bytecode, the content is altered in case <code>Generator</code> detects  and patches dead code
 235      * @param cp R/W <code>ConstantPoolBuilder</code> instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps
 236      * @param handlers R/W <code>ExceptionHandler</code> list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers
 237      * and also to be altered when dead code is detected and must be excluded from exception handlers
 238      */
 239     public StackMapGenerator(LabelContext labelContext,
 240                      ClassDesc thisClass,
 241                      String methodName,
 242                      MethodTypeDesc methodDesc,
 243                      boolean isStatic,
 244                      RawBytecodeHelper.CodeRange bytecode,
 245                      SplitConstantPool cp,
 246                      ClassFileImpl context,
 247                      UnsetField[] strictFields,
 248                      List<AbstractPseudoInstruction.ExceptionCatchImpl> handlers) {
 249         this.thisType = Type.referenceType(thisClass);
 250         this.methodName = methodName;
 251         this.methodDesc = methodDesc;
 252         this.isStatic = isStatic;
 253         this.bytecode = bytecode;
 254         this.cp = cp;
 255         this.labelContext = labelContext;
 256         this.handlers = handlers;
 257         this.rawHandlers = new ArrayList<>(handlers.size());
 258         this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolver());
 259         this.patchDeadCode = context.patchDeadCode();
 260         this.filterDeadLabels = context.dropDeadLabels();
 261         this.currentFrame = new Frame(classHierarchy);
 262         if (OBJECT_INITIALIZER_NAME.equals(methodName)) {
 263             this.strictFieldsToPut = strictFields;
 264         } else {
 265             this.strictFieldsToPut = UnsetField.EMPTY_ARRAY;
 266         }
 267         generate();
 268     }
 269 
 270     /**
 271      * Calculated maximum number of the locals required
 272      * @return maximum number of the locals required
 273      */
 274     public int maxLocals() {
 275         return maxLocals;
 276     }
 277 
 278     /**
 279      * Calculated maximum stack size required
 280      * @return maximum stack size required
 281      */
 282     public int maxStack() {
 283         return maxStack;
 284     }
 285 
 286     private Frame getFrame(int offset) {
 287         //binary search over frames ordered by offset
 288         int low = 0;
 289         int high = framesCount - 1;
 290         while (low <= high) {
 291             int mid = (low + high) >>> 1;
 292             var f = frames[mid];
 293             if (f.offset < offset)
 294                 low = mid + 1;
 295             else if (f.offset > offset)
 296                 high = mid - 1;
 297             else
 298                 return f;
 299         }
 300         return null;
 301     }
 302 
 303     private void checkJumpTarget(Frame frame, int target) {
 304         frame.checkAssignableTo(getFrame(target));
 305     }
 306 
 307     private int exMin, exMax;
 308 
 309     private boolean isAnyFrameDirty() {
 310         for (int i = 0; i < framesCount; i++) {
 311             if (frames[i].dirty) return true;
 312         }
 313         return false;
 314     }
 315 
 316     private void generate() {
 317         exMin = bytecode.length();
 318         exMax = -1;
 319         if (!handlers.isEmpty()) {
 320             generateHandlers();
 321         }
 322         detectFrames();
 323         do {
 324             processMethod();
 325         } while (isAnyFrameDirty());
 326         maxLocals = currentFrame.frameMaxLocals;
 327         maxStack = currentFrame.frameMaxStack;
 328 
 329         //dead code patching
 330         for (int i = 0; i < framesCount; i++) {
 331             var frame = frames[i];
 332             if (frame.flags == -1) {
 333                 deadCodePatching(frame, i);
 334             }
 335         }
 336     }
 337 
 338     private void generateHandlers() {
 339         var labelContext = this.labelContext;
 340         for (int i = 0; i < handlers.size(); i++) {
 341             var exhandler = handlers.get(i);
 342             int start_pc = labelContext.labelToBci(exhandler.tryStart());
 343             int end_pc = labelContext.labelToBci(exhandler.tryEnd());
 344             int handler_pc = labelContext.labelToBci(exhandler.handler());
 345             if (start_pc >= 0 && end_pc >= 0 && end_pc > start_pc && handler_pc >= 0) {
 346                 if (start_pc < exMin) exMin = start_pc;
 347                 if (end_pc > exMax) exMax = end_pc;
 348                 var catchType = exhandler.catchType();
 349                 rawHandlers.add(new RawExceptionCatch(start_pc, end_pc, handler_pc,
 350                         catchType.isPresent() ? cpIndexToType(catchType.get().index(), cp)
 351                                 : Type.THROWABLE_TYPE));
 352             }
 353         }
 354     }
 355 
 356     private void deadCodePatching(Frame frame, int i) {
 357         if (!patchDeadCode) throw generatorError("Unable to generate stack map frame for dead code", frame.offset);
 358         //patch frame
 359         frame.pushStack(Type.THROWABLE_TYPE);
 360         if (maxStack < 1) maxStack = 1;
 361         int end = (i < framesCount - 1 ? frames[i + 1].offset : bytecode.length()) - 1;
 362         //patch bytecode
 363         var arr = bytecode.array();
 364         Arrays.fill(arr, frame.offset, end, (byte) NOP);
 365         arr[end] = (byte) ATHROW;
 366         //patch handlers
 367         removeRangeFromExcTable(frame.offset, end + 1);
 368     }
 369 
 370     private void removeRangeFromExcTable(int rangeStart, int rangeEnd) {
 371         var it = handlers.listIterator();
 372         while (it.hasNext()) {
 373             var e = it.next();
 374             int handlerStart = labelContext.labelToBci(e.tryStart());
 375             int handlerEnd = labelContext.labelToBci(e.tryEnd());
 376             if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) {
 377                 //out of range
 378                 continue;
 379             }
 380             if (rangeStart <= handlerStart) {
 381               if (rangeEnd >= handlerEnd) {
 382                   //complete removal
 383                   it.remove();
 384               } else {
 385                   //cut from left
 386                   Label newStart = labelContext.newLabel();
 387                   labelContext.setLabelTarget(newStart, rangeEnd);
 388                   it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
 389               }
 390             } else if (rangeEnd >= handlerEnd) {
 391                 //cut from right
 392                 Label newEnd = labelContext.newLabel();
 393                 labelContext.setLabelTarget(newEnd, rangeStart);
 394                 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
 395             } else {
 396                 //split
 397                 Label newStart = labelContext.newLabel();
 398                 labelContext.setLabelTarget(newStart, rangeEnd);
 399                 Label newEnd = labelContext.newLabel();
 400                 labelContext.setLabelTarget(newEnd, rangeStart);
 401                 it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType()));
 402                 it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType()));
 403             }
 404         }
 405     }
 406 
 407     /**
 408      * Getter of the generated <code>StackMapTableAttribute</code> or null if stack map is empty
 409      * @return <code>StackMapTableAttribute</code> or null if stack map is empty
 410      */
 411     public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
 412         return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
 413             @Override
 414             public void writeBody(BufWriterImpl b) {
 415                 int countPos = b.size();
 416                 if (framesCount != (char) framesCount) {
 417                     throw generatorError("Too many frames: " + framesCount);
 418                 }
 419                 b.writeU2(framesCount);
 420                 Frame prevFrame =  new Frame(classHierarchy);
 421                 prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
 422                 prevFrame.trimAndCompress();
 423                 for (int i = 0; i < framesCount; i++) {
 424                     var fr = frames[i];
 425                     fr.trimAndCompress();
 426                     fr.writeTo(b, prevFrame, cp);
 427                     prevFrame = fr;
 428                 }
 429             }
 430 
 431             @Override
 432             public Utf8Entry attributeName() {
 433                 return cp.utf8Entry(Attributes.NAME_STACK_MAP_TABLE);
 434             }
 435         };
 436     }
 437 
 438     private static Type cpIndexToType(int index, ConstantPoolBuilder cp) {
 439         return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol());
 440     }
 441 
 442     private void processMethod() {
 443         var frames = this.frames;
 444         var currentFrame = this.currentFrame;
 445         currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut);
 446         currentFrame.stackSize = 0;
 447         currentFrame.offset = -1;
 448         int stackmapIndex = 0;
 449         var bcs = bytecode.start();
 450         boolean ncf = false;
 451         while (bcs.next()) {
 452             currentFrame.offset = bcs.bci();
 453             if (stackmapIndex < framesCount) {
 454                 int thisOffset = frames[stackmapIndex].offset;
 455                 if (ncf && thisOffset > bcs.bci()) {
 456                     throw generatorError("Expecting a stack map frame");
 457                 }
 458                 if (thisOffset == bcs.bci()) {
 459                     Frame nextFrame = frames[stackmapIndex++];
 460                     if (!ncf) {
 461                         currentFrame.checkAssignableTo(nextFrame);
 462                     }
 463                     while (!nextFrame.dirty) { //skip unmatched frames
 464                         if (stackmapIndex == framesCount) return; //skip the rest of this round
 465                         nextFrame = frames[stackmapIndex++];
 466                     }
 467                     bcs.reset(nextFrame.offset); //skip code up-to the next frame
 468                     bcs.next();
 469                     currentFrame.offset = bcs.bci();
 470                     currentFrame.copyFrom(nextFrame);
 471                     nextFrame.dirty = false;
 472                 } else if (thisOffset < bcs.bci()) {
 473                     throw generatorError("Bad stack map offset");
 474                 }
 475             } else if (ncf) {
 476                 throw generatorError("Expecting a stack map frame");
 477             }
 478             ncf = processBlock(bcs);
 479         }
 480     }
 481 
 482     private boolean processBlock(RawBytecodeHelper bcs) {
 483         int opcode = bcs.opcode();
 484         boolean ncf = false;
 485         boolean this_uninit = false;
 486         boolean verified_exc_handlers = false;
 487         int bci = bcs.bci();
 488         Type type1, type2, type3, type4;
 489         if ((RawBytecodeHelper.isStoreIntoLocal(opcode) || (opcode == PUTFIELD && OBJECT_INITIALIZER_NAME.equals(methodName)))
 490                 && bci >= exMin && bci < exMax) {
 491             processExceptionHandlerTargets(bci, this_uninit);
 492             verified_exc_handlers = true;
 493         }
 494         switch (opcode) {
 495             case NOP -> {}
 496             case RETURN -> {
 497                 ncf = true;
 498             }
 499             case ACONST_NULL ->
 500                 currentFrame.pushStack(Type.NULL_TYPE);
 501             case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH ->
 502                 currentFrame.pushStack(Type.INTEGER_TYPE);
 503             case LCONST_0, LCONST_1 ->
 504                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 505             case FCONST_0, FCONST_1, FCONST_2 ->
 506                 currentFrame.pushStack(Type.FLOAT_TYPE);
 507             case DCONST_0, DCONST_1 ->
 508                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 509             case LDC ->
 510                 processLdc(bcs.getIndexU1());
 511             case LDC_W, LDC2_W ->
 512                 processLdc(bcs.getIndexU2());
 513             case ILOAD ->
 514                 currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.INTEGER_TYPE);
 515             case ILOAD_0, ILOAD_1, ILOAD_2, ILOAD_3 ->
 516                 currentFrame.checkLocal(opcode - ILOAD_0).pushStack(Type.INTEGER_TYPE);
 517             case LLOAD ->
 518                 currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 519             case LLOAD_0, LLOAD_1, LLOAD_2, LLOAD_3 ->
 520                 currentFrame.checkLocal(opcode - LLOAD_0 + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 521             case FLOAD ->
 522                 currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.FLOAT_TYPE);
 523             case FLOAD_0, FLOAD_1, FLOAD_2, FLOAD_3 ->
 524                 currentFrame.checkLocal(opcode - FLOAD_0).pushStack(Type.FLOAT_TYPE);
 525             case DLOAD ->
 526                 currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 527             case DLOAD_0, DLOAD_1, DLOAD_2, DLOAD_3 ->
 528                 currentFrame.checkLocal(opcode - DLOAD_0 + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 529             case ALOAD ->
 530                 currentFrame.pushStack(currentFrame.getLocal(bcs.getIndex()));
 531             case ALOAD_0, ALOAD_1, ALOAD_2, ALOAD_3 ->
 532                 currentFrame.pushStack(currentFrame.getLocal(opcode - ALOAD_0));
 533             case IALOAD, BALOAD, CALOAD, SALOAD ->
 534                 currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE);
 535             case LALOAD ->
 536                 currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 537             case FALOAD ->
 538                 currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE);
 539             case DALOAD ->
 540                 currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 541             case AALOAD ->
 542                 currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()) == Type.NULL_TYPE ? Type.NULL_TYPE : type1.getComponent());
 543             case ISTORE ->
 544                 currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.INTEGER_TYPE);
 545             case ISTORE_0, ISTORE_1, ISTORE_2, ISTORE_3 ->
 546                 currentFrame.decStack(1).setLocal(opcode - ISTORE_0, Type.INTEGER_TYPE);
 547             case LSTORE ->
 548                 currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.LONG_TYPE, Type.LONG2_TYPE);
 549             case LSTORE_0, LSTORE_1, LSTORE_2, LSTORE_3 ->
 550                 currentFrame.decStack(2).setLocal2(opcode - LSTORE_0, Type.LONG_TYPE, Type.LONG2_TYPE);
 551             case FSTORE ->
 552                 currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.FLOAT_TYPE);
 553             case FSTORE_0, FSTORE_1, FSTORE_2, FSTORE_3 ->
 554                 currentFrame.decStack(1).setLocal(opcode - FSTORE_0, Type.FLOAT_TYPE);
 555             case DSTORE ->
 556                 currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 557             case DSTORE_0, DSTORE_1, DSTORE_2, DSTORE_3 ->
 558                 currentFrame.decStack(2).setLocal2(opcode - DSTORE_0, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 559             case ASTORE ->
 560                 currentFrame.setLocal(bcs.getIndex(), currentFrame.popStack());
 561             case ASTORE_0, ASTORE_1, ASTORE_2, ASTORE_3 ->
 562                 currentFrame.setLocal(opcode - ASTORE_0, currentFrame.popStack());
 563             case LASTORE, DASTORE ->
 564                 currentFrame.decStack(4);
 565             case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE ->
 566                 currentFrame.decStack(3);
 567             case POP, MONITORENTER, MONITOREXIT ->
 568                 currentFrame.decStack(1);
 569             case POP2 ->
 570                 currentFrame.decStack(2);
 571             case DUP ->
 572                 currentFrame.pushStack(type1 = currentFrame.popStack()).pushStack(type1);
 573             case DUP_X1 -> {
 574                 type1 = currentFrame.popStack();
 575                 type2 = currentFrame.popStack();
 576                 currentFrame.pushStack(type1).pushStack(type2).pushStack(type1);
 577             }
 578             case DUP_X2 -> {
 579                 type1 = currentFrame.popStack();
 580                 type2 = currentFrame.popStack();
 581                 type3 = currentFrame.popStack();
 582                 currentFrame.pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1);
 583             }
 584             case DUP2 -> {
 585                 type1 = currentFrame.popStack();
 586                 type2 = currentFrame.popStack();
 587                 currentFrame.pushStack(type2).pushStack(type1).pushStack(type2).pushStack(type1);
 588             }
 589             case DUP2_X1 -> {
 590                 type1 = currentFrame.popStack();
 591                 type2 = currentFrame.popStack();
 592                 type3 = currentFrame.popStack();
 593                 currentFrame.pushStack(type2).pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1);
 594             }
 595             case DUP2_X2 -> {
 596                 type1 = currentFrame.popStack();
 597                 type2 = currentFrame.popStack();
 598                 type3 = currentFrame.popStack();
 599                 type4 = currentFrame.popStack();
 600                 currentFrame.pushStack(type2).pushStack(type1).pushStack(type4).pushStack(type3).pushStack(type2).pushStack(type1);
 601             }
 602             case SWAP -> {
 603                 type1 = currentFrame.popStack();
 604                 type2 = currentFrame.popStack();
 605                 currentFrame.pushStack(type1);
 606                 currentFrame.pushStack(type2);
 607             }
 608             case IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND ->
 609                 currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE);
 610             case INEG, ARRAYLENGTH, INSTANCEOF ->
 611                 currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE);
 612             case LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR ->
 613                 currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 614             case LNEG ->
 615                 currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 616             case LSHL, LSHR, LUSHR ->
 617                 currentFrame.decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 618             case FADD, FSUB, FMUL, FDIV, FREM ->
 619                 currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE);
 620             case FNEG ->
 621                 currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE);
 622             case DADD, DSUB, DMUL, DDIV, DREM ->
 623                 currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 624             case DNEG ->
 625                 currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 626             case IINC ->
 627                 currentFrame.checkLocal(bcs.getIndex());
 628             case I2L ->
 629                 currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 630             case L2I ->
 631                 currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE);
 632             case I2F ->
 633                 currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE);
 634             case I2D ->
 635                 currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 636             case L2F ->
 637                 currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE);
 638             case L2D ->
 639                 currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 640             case F2I ->
 641                 currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE);
 642             case F2L ->
 643                 currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 644             case F2D ->
 645                 currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 646             case D2L ->
 647                 currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 648             case D2F ->
 649                 currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE);
 650             case I2B, I2C, I2S ->
 651                 currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE);
 652             case LCMP, DCMPL, DCMPG ->
 653                 currentFrame.decStack(4).pushStack(Type.INTEGER_TYPE);
 654             case FCMPL, FCMPG, D2I ->
 655                 currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE);
 656             case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE ->
 657                 checkJumpTarget(currentFrame.decStack(2), bcs.dest());
 658             case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL ->
 659                 checkJumpTarget(currentFrame.decStack(1), bcs.dest());
 660             case GOTO -> {
 661                 checkJumpTarget(currentFrame, bcs.dest());
 662                 ncf = true;
 663             }
 664             case GOTO_W -> {
 665                 checkJumpTarget(currentFrame, bcs.destW());
 666                 ncf = true;
 667             }
 668             case TABLESWITCH, LOOKUPSWITCH -> {
 669                 processSwitch(bcs);
 670                 ncf = true;
 671             }
 672             case LRETURN, DRETURN -> {
 673                 currentFrame.decStack(2);
 674                 ncf = true;
 675             }
 676             case IRETURN, FRETURN, ARETURN, ATHROW -> {
 677                 currentFrame.decStack(1);
 678                 ncf = true;
 679             }
 680             case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD ->
 681                 processFieldInstructions(bcs);
 682             case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC ->
 683                 this_uninit = processInvokeInstructions(bcs, (bci >= exMin && bci < exMax), this_uninit);
 684             case NEW ->
 685                 currentFrame.pushStack(Type.uninitializedType(bci));
 686             case NEWARRAY ->
 687                 currentFrame.decStack(1).pushStack(getNewarrayType(bcs.getIndex()));
 688             case ANEWARRAY ->
 689                 processAnewarray(bcs.getIndexU2());
 690             case CHECKCAST ->
 691                 currentFrame.decStack(1).pushStack(cpIndexToType(bcs.getIndexU2(), cp));
 692             case MULTIANEWARRAY -> {
 693                 type1 = cpIndexToType(bcs.getIndexU2(), cp);
 694                 int dim = bcs.getU1Unchecked(bcs.bci() + 3);
 695                 for (int i = 0; i < dim; i++) {
 696                     currentFrame.popStack();
 697                 }
 698                 currentFrame.pushStack(type1);
 699             }
 700             case JSR, JSR_W, RET ->
 701                 throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0");
 702             default ->
 703                 throw generatorError(String.format("Bad instruction: %02x", opcode));
 704         }
 705         if (!verified_exc_handlers && bci >= exMin && bci < exMax) {
 706             processExceptionHandlerTargets(bci, this_uninit);
 707         }
 708         return ncf;
 709     }
 710 
 711     private void processExceptionHandlerTargets(int bci, boolean this_uninit) {
 712         for (var ex : rawHandlers) {
 713             if (bci == ex.start || (currentFrame.localsOrUnsetsChanged && bci > ex.start && bci < ex.end)) {
 714                 int flags = currentFrame.flags;
 715                 if (this_uninit) flags |= FLAG_THIS_UNINIT;
 716                 Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType);
 717                 checkJumpTarget(newFrame, ex.handler);
 718             }
 719         }
 720         currentFrame.localsOrUnsetsChanged = false;
 721     }
 722 
 723     private void processLdc(int index) {
 724         switch (cp.entryByIndex(index).tag()) {
 725             case TAG_UTF8 ->
 726                 currentFrame.pushStack(Type.OBJECT_TYPE);
 727             case TAG_STRING ->
 728                 currentFrame.pushStack(Type.STRING_TYPE);
 729             case TAG_CLASS ->
 730                 currentFrame.pushStack(Type.CLASS_TYPE);
 731             case TAG_INTEGER ->
 732                 currentFrame.pushStack(Type.INTEGER_TYPE);
 733             case TAG_FLOAT ->
 734                 currentFrame.pushStack(Type.FLOAT_TYPE);
 735             case TAG_DOUBLE ->
 736                 currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
 737             case TAG_LONG ->
 738                 currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
 739             case TAG_METHOD_HANDLE ->
 740                 currentFrame.pushStack(Type.METHOD_HANDLE_TYPE);
 741             case TAG_METHOD_TYPE ->
 742                 currentFrame.pushStack(Type.METHOD_TYPE);
 743             case TAG_DYNAMIC ->
 744                 currentFrame.pushStack(cp.entryByIndex(index, ConstantDynamicEntry.class).typeSymbol());
 745             default ->
 746                 throw generatorError("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag()));
 747         }
 748     }
 749 
 750     private void processSwitch(RawBytecodeHelper bcs) {
 751         int bci = bcs.bci();
 752         int alignedBci = RawBytecodeHelper.align(bci + 1);
 753         int defaultOffset = bcs.getIntUnchecked(alignedBci);
 754         int keys, delta;
 755         currentFrame.popStack();
 756         if (bcs.opcode() == TABLESWITCH) {
 757             int low = bcs.getIntUnchecked(alignedBci + 4);
 758             int high = bcs.getIntUnchecked(alignedBci + 2 * 4);
 759             if (low > high) {
 760                 throw generatorError("low must be less than or equal to high in tableswitch");
 761             }
 762             keys = high - low + 1;
 763             if (keys < 0) {
 764                 throw generatorError("too many keys in tableswitch");
 765             }
 766             delta = 1;
 767         } else {
 768             keys = bcs.getIntUnchecked(alignedBci + 4);
 769             if (keys < 0) {
 770                 throw generatorError("number of keys in lookupswitch less than 0");
 771             }
 772             delta = 2;
 773             for (int i = 0; i < (keys - 1); i++) {
 774                 int this_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i) * 4);
 775                 int next_key = bcs.getIntUnchecked(alignedBci + (2 + 2 * i + 2) * 4);
 776                 if (this_key >= next_key) {
 777                     throw generatorError("Bad lookupswitch instruction");
 778                 }
 779             }
 780         }
 781         int target = bci + defaultOffset;
 782         checkJumpTarget(currentFrame, target);
 783         for (int i = 0; i < keys; i++) {
 784             target = bci + bcs.getIntUnchecked(alignedBci + (3 + i * delta) * 4);
 785             checkJumpTarget(currentFrame, target);
 786         }
 787     }
 788 
 789     private void processFieldInstructions(RawBytecodeHelper bcs) {
 790         var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType();
 791         var desc = Util.fieldTypeSymbol(nameAndType.type());
 792         var currentFrame = this.currentFrame;
 793         switch (bcs.opcode()) {
 794             case GETSTATIC ->
 795                 currentFrame.pushStack(desc);
 796             case PUTSTATIC -> {
 797                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 2 : 1);
 798             }
 799             case GETFIELD -> {
 800                 currentFrame.decStack(1);
 801                 currentFrame.pushStack(desc);
 802             }
 803             case PUTFIELD -> {
 804                 if (strictFieldsToPut.length > 0) {
 805                     currentFrame.putStrictField(nameAndType);
 806                 }
 807                 currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2);
 808             }
 809             default -> throw new AssertionError("Should not reach here");
 810         }
 811     }
 812 
 813     private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) {
 814         int index = bcs.getIndexU2();
 815         int opcode = bcs.opcode();
 816         var nameAndType = opcode == INVOKEDYNAMIC
 817                 ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType()
 818                 : cp.entryByIndex(index, MemberRefEntry.class).nameAndType();
 819         var mDesc = Util.methodTypeSymbol(nameAndType.type());
 820         int bci = bcs.bci();
 821         var currentFrame = this.currentFrame;
 822         currentFrame.decStack(Util.parameterSlots(mDesc));
 823         if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) {
 824             if (nameAndType.name().equalsString(OBJECT_INITIALIZER_NAME)) {
 825                 Type type = currentFrame.popStack();
 826                 if (type == Type.UNITIALIZED_THIS_TYPE) {
 827                     if (inTryBlock) {
 828                         processExceptionHandlerTargets(bci, true);
 829                     }
 830                     var owner = cp.entryByIndex(index, MemberRefEntry.class).owner();
 831                     if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName())
 832                             && currentFrame.unsetFieldsSize != 0) {
 833                         throw generatorError("Unset fields mismatch");
 834                     }
 835                     currentFrame.initializeObject(type, thisType);
 836                     currentFrame.unsetFieldsSize = 0;
 837                     currentFrame.unsetFields = UnsetField.EMPTY_ARRAY;
 838                     thisUninit = true;
 839                 } else if (type.tag == ITEM_UNINITIALIZED) {
 840                     Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp);
 841                     if (inTryBlock) {
 842                         processExceptionHandlerTargets(bci, thisUninit);
 843                     }
 844                     currentFrame.initializeObject(type, new_class_type);
 845                 } else {
 846                     throw generatorError("Bad operand type when invoking <init>");
 847                 }
 848             } else {
 849                 currentFrame.decStack(1);
 850             }
 851         }
 852         currentFrame.pushStack(mDesc.returnType());
 853         return thisUninit;
 854     }
 855 
 856     private Type getNewarrayType(int index) {
 857         if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index));
 858         return ARRAY_FROM_BASIC_TYPE[index];
 859     }
 860 
 861     private void processAnewarray(int index) {
 862         currentFrame.popStack();
 863         currentFrame.pushStack(cpIndexToType(index, cp).toArray());
 864     }
 865 
 866     /**
 867      * {@return the generator error with attached details}
 868      * @param msg error message
 869      */
 870     private IllegalArgumentException generatorError(String msg) {
 871         return generatorError(msg, currentFrame.offset);
 872     }
 873 
 874     /**
 875      * {@return the generator error with attached details}
 876      * @param msg error message
 877      * @param offset bytecode offset where the error occurred
 878      */
 879     private IllegalArgumentException generatorError(String msg, int offset) {
 880         var sb = new StringBuilder("%s at bytecode offset %d of method %s(%s)".formatted(
 881                 msg,
 882                 offset,
 883                 methodName,
 884                 methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(","))));
 885         Util.dumpMethod(cp, thisType.sym(), methodName, methodDesc, isStatic ? ACC_STATIC : 0, bytecode, sb::append);
 886         return new IllegalArgumentException(sb.toString());
 887     }
 888 
 889     /**
 890      * Performs detection of mandatory stack map frames in a single bytecode traversing pass
 891      * @return detected frames
 892      */
 893     private void detectFrames() {
 894         var bcs = bytecode.start();
 895         boolean no_control_flow = false;
 896         int opcode, bci = 0;
 897         while (bcs.next()) try {
 898             opcode = bcs.opcode();
 899             bci = bcs.bci();
 900             if (no_control_flow) {
 901                 addFrame(bci);
 902             }
 903             no_control_flow = switch (opcode) {
 904                 case GOTO -> {
 905                             addFrame(bcs.dest());
 906                             yield true;
 907                         }
 908                 case GOTO_W -> {
 909                             addFrame(bcs.destW());
 910                             yield true;
 911                         }
 912                 case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE,
 913                      IF_ICMPGT, IF_ICMPLE, IFEQ, IFNE,
 914                      IFLT, IFGE, IFGT, IFLE, IF_ACMPEQ,
 915                      IF_ACMPNE , IFNULL , IFNONNULL -> {
 916                             addFrame(bcs.dest());
 917                             yield false;
 918                         }
 919                 case TABLESWITCH, LOOKUPSWITCH -> {
 920                             int aligned_bci = RawBytecodeHelper.align(bci + 1);
 921                             int default_ofset = bcs.getIntUnchecked(aligned_bci);
 922                             int keys, delta;
 923                             if (bcs.opcode() == TABLESWITCH) {
 924                                 int low = bcs.getIntUnchecked(aligned_bci + 4);
 925                                 int high = bcs.getIntUnchecked(aligned_bci + 2 * 4);
 926                                 keys = high - low + 1;
 927                                 delta = 1;
 928                             } else {
 929                                 keys = bcs.getIntUnchecked(aligned_bci + 4);
 930                                 delta = 2;
 931                             }
 932                             addFrame(bci + default_ofset);
 933                             for (int i = 0; i < keys; i++) {
 934                                 addFrame(bci + bcs.getIntUnchecked(aligned_bci + (3 + i * delta) * 4));
 935                             }
 936                             yield true;
 937                         }
 938                 case IRETURN, LRETURN, FRETURN, DRETURN,
 939                      ARETURN, RETURN, ATHROW -> true;
 940                 default -> false;
 941             };
 942         } catch (IllegalArgumentException iae) {
 943             throw generatorError("Detected branch target out of bytecode range", bci);
 944         }
 945         for (int i = 0; i < rawHandlers.size(); i++) try {
 946             addFrame(rawHandlers.get(i).handler());
 947         } catch (IllegalArgumentException iae) {
 948             if (!filterDeadLabels)
 949                 throw generatorError("Detected exception handler out of bytecode range");
 950         }
 951     }
 952 
 953     private void addFrame(int offset) {
 954         Preconditions.checkIndex(offset, bytecode.length(), RawBytecodeHelper.IAE_FORMATTER);
 955         var frames = this.frames;
 956         int i = 0, framesCount = this.framesCount;
 957         for (; i < framesCount; i++) {
 958             var frameOffset = frames[i].offset;
 959             if (frameOffset == offset) {
 960                 return;
 961             }
 962             if (frameOffset > offset) {
 963                 break;
 964             }
 965         }
 966         if (framesCount >= frames.length) {
 967             int newCapacity = framesCount + 8;
 968             this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
 969         }
 970         if (i != framesCount) {
 971             System.arraycopy(frames, i, frames, i + 1, framesCount - i);
 972         }
 973         frames[i] = new Frame(offset, classHierarchy);
 974         this.framesCount = framesCount + 1;
 975     }
 976 
 977     private final class Frame {
 978 
 979         int offset;
 980         int localsSize, stackSize, unsetFieldsSize;
 981         int flags;
 982         int frameMaxStack = 0, frameMaxLocals = 0;
 983         boolean dirty = false;
 984         boolean localsOrUnsetsChanged = false;
 985 
 986         private final ClassHierarchyImpl classHierarchy;
 987 
 988         private Type[] locals, stack;
 989         private UnsetField[] unsetFields; // sorted, modifiable oversized array
 990 
 991         Frame(ClassHierarchyImpl classHierarchy) {
 992             this(-1, 0, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
 993         }
 994 
 995         Frame(int offset, ClassHierarchyImpl classHierarchy) {
 996             this(offset, -1, 0, 0, 0, null, null, UnsetField.EMPTY_ARRAY, classHierarchy);
 997         }
 998 
 999         Frame(int offset, int flags, int locals_size, int stack_size, int unsetFieldsSize, Type[] locals, Type[] stack, UnsetField[] unsetFields, ClassHierarchyImpl classHierarchy) {
1000             this.offset = offset;
1001             this.localsSize = locals_size;
1002             this.stackSize = stack_size;
1003             this.unsetFieldsSize = unsetFieldsSize;
1004             this.flags = flags;
1005             this.locals = locals;
1006             this.stack = stack;
1007             this.unsetFields = unsetFields;
1008             this.classHierarchy = classHierarchy;
1009         }
1010 
1011         @Override
1012         public String toString() {
1013             return (dirty ? "frame* @" : "frame @") + offset +
1014                     " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) +
1015                     " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)) +
1016                     " and unset fields " + (unsetFields == null ? "[]" : Arrays.asList(unsetFields).subList(0, unsetFieldsSize));
1017         }
1018 
1019         Frame pushStack(ClassDesc desc) {
1020             if (desc == CD_long)   return pushStack(Type.LONG_TYPE, Type.LONG2_TYPE);
1021             if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
1022             return desc == CD_void ? this
1023                     : pushStack(
1024                     desc.isPrimitive()
1025                             ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
1026                             : Type.referenceType(desc));
1027         }
1028 
1029         Frame pushStack(Type type) {
1030             checkStack(stackSize);
1031             stack[stackSize++] = type;
1032             return this;
1033         }
1034 
1035         Frame pushStack(Type type1, Type type2) {
1036             checkStack(stackSize + 1);
1037             stack[stackSize++] = type1;
1038             stack[stackSize++] = type2;
1039             return this;
1040         }
1041 
1042         Type popStack() {
1043             if (stackSize < 1) throw generatorError("Operand stack underflow");
1044             return stack[--stackSize];
1045         }
1046 
1047         Frame decStack(int size) {
1048             stackSize -= size;
1049             if (stackSize < 0) throw generatorError("Operand stack underflow");
1050             return this;
1051         }
1052 
1053         Frame frameInExceptionHandler(int flags, Type excType) {
1054             return new Frame(offset, flags, localsSize, 1, unsetFieldsSize,
1055                     locals, new Type[] {excType}, unsetFields, classHierarchy);
1056         }
1057 
1058         void initializeObject(Type old_object, Type new_object) {
1059             int i;
1060             for (i = 0; i < localsSize; i++) {
1061                 if (locals[i].equals(old_object)) {
1062                     locals[i] = new_object;
1063                     localsOrUnsetsChanged = true;
1064                 }
1065             }
1066             for (i = 0; i < stackSize; i++) {
1067                 if (stack[i].equals(old_object)) {
1068                     stack[i] = new_object;
1069                 }
1070             }
1071             if (old_object == Type.UNITIALIZED_THIS_TYPE) {
1072                 flags &= ~FLAG_THIS_UNINIT;
1073                 assert flags == 0 : flags;
1074             }
1075         }
1076 
1077         Frame checkLocal(int index) {
1078             if (index >= frameMaxLocals) frameMaxLocals = index + 1;
1079             if (locals == null) {
1080                 locals = new Type[index + FRAME_DEFAULT_CAPACITY];
1081                 Arrays.fill(locals, Type.TOP_TYPE);
1082             } else if (index >= locals.length) {
1083                 int current = locals.length;
1084                 locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY);
1085                 Arrays.fill(locals, current, locals.length, Type.TOP_TYPE);
1086             }
1087             return this;
1088         }
1089 
1090         void putStrictField(NameAndTypeEntry nat) {
1091             int shift = 0;
1092             var array = unsetFields;
1093             for (int i = 0; i < unsetFieldsSize; i++) {
1094                 var f = array[i];
1095                 if (f.name().equals(nat.name()) && f.type().equals(nat.type())) {
1096                     shift++;
1097                 } else if (shift != 0) {
1098                     array[i - shift] = array[i];
1099                     array[i] = null;
1100                 }
1101             }
1102             if (shift > 1) {
1103                 throw generatorError(nat + "; discovered " + shift);
1104             } else if (shift == 1) {
1105                 localsOrUnsetsChanged = true;
1106             }
1107             unsetFieldsSize -= shift;
1108         }
1109 
1110         private void checkStack(int index) {
1111             if (index >= frameMaxStack) frameMaxStack = index + 1;
1112             if (stack == null) {
1113                 stack = new Type[index + FRAME_DEFAULT_CAPACITY];
1114                 Arrays.fill(stack, Type.TOP_TYPE);
1115             } else if (index >= stack.length) {
1116                 int current = stack.length;
1117                 stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY);
1118                 Arrays.fill(stack, current, stack.length, Type.TOP_TYPE);
1119             }
1120         }
1121 
1122         private void setLocalRawInternal(int index, Type type) {
1123             checkLocal(index);
1124             localsOrUnsetsChanged |= !type.equals(locals[index]);
1125             locals[index] = type;
1126         }
1127 
1128         void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) {
1129             int localsSize = 0;
1130             // Pre-emptively create a locals array that encompass all parameter slots
1131             checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0));
1132             Type type;
1133             Type[] locals = this.locals;
1134             if (!isStatic) {
1135                 if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) {
1136                     int strictFieldCount = strictFieldsToPut.length;
1137                     this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount);
1138                     this.unsetFieldsSize = strictFieldCount;
1139                     type = Type.UNITIALIZED_THIS_TYPE;
1140                     this.flags = FLAG_THIS_UNINIT;
1141                 } else {
1142                     this.unsetFields = UnsetField.EMPTY_ARRAY;
1143                     this.unsetFieldsSize = 0;
1144                     type = thisKlass;
1145                     this.flags = 0;
1146                 }
1147                 locals[localsSize++] = type;
1148             }
1149             for (int i = 0; i < methodDesc.parameterCount(); i++) {
1150                 var desc = methodDesc.parameterType(i);
1151                 if (desc == CD_long) {
1152                     locals[localsSize    ] = Type.LONG_TYPE;
1153                     locals[localsSize + 1] = Type.LONG2_TYPE;
1154                     localsSize += 2;
1155                 } else if (desc == CD_double) {
1156                     locals[localsSize    ] = Type.DOUBLE_TYPE;
1157                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
1158                     localsSize += 2;
1159                 } else {
1160                     if (!desc.isPrimitive()) {
1161                         type = Type.referenceType(desc);
1162                     } else if (desc == CD_float) {
1163                         type = Type.FLOAT_TYPE;
1164                     } else {
1165                         type = Type.INTEGER_TYPE;
1166                     }
1167                     locals[localsSize++] = type;
1168                 }
1169             }
1170             this.localsSize = localsSize;
1171         }
1172 
1173         void copyFrom(Frame src) {
1174             if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE);
1175             localsSize = src.localsSize;
1176             checkLocal(src.localsSize - 1);
1177             if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize);
1178             if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE);
1179             stackSize = src.stackSize;
1180             checkStack(src.stackSize - 1);
1181             if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize);
1182             unsetFieldsSize = src.unsetFieldsSize;
1183             unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize);
1184             flags = src.flags;
1185             localsOrUnsetsChanged = true;
1186         }
1187 
1188         void checkAssignableTo(Frame target) {
1189             int localsSize = this.localsSize;
1190             int stackSize = this.stackSize;
1191             int myUnsetFieldsSize = this.unsetFieldsSize;
1192             if (target.flags == -1) {
1193                 target.locals = locals == null ? null : locals.clone();
1194                 target.localsSize = localsSize;
1195                 if (stackSize > 0) {
1196                     target.stack = stack.clone();
1197                     target.stackSize = stackSize;
1198                 }
1199                 target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize);
1200                 target.unsetFieldsSize = myUnsetFieldsSize;
1201                 target.flags = flags;
1202                 target.dirty = true;
1203             } else {
1204                 if (target.localsSize > localsSize) {
1205                     target.localsSize = localsSize;
1206                     target.dirty = true;
1207                 }
1208                 for (int i = 0; i < target.localsSize; i++) {
1209                     merge(locals[i], target.locals, i, target);
1210                 }
1211                 if (stackSize != target.stackSize) {
1212                     throw generatorError("Stack size mismatch");
1213                 }
1214                 for (int i = 0; i < target.stackSize; i++) {
1215                     if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) {
1216                         throw generatorError("Stack content mismatch");
1217                     }
1218                 }
1219                 if (myUnsetFieldsSize != 0) {
1220                     mergeUnsetFields(target);
1221                 }
1222             }
1223         }
1224 
1225         private Type getLocalRawInternal(int index) {
1226             checkLocal(index);
1227             return locals[index];
1228         }
1229 
1230         Type getLocal(int index) {
1231             Type ret = getLocalRawInternal(index);
1232             if (index >= localsSize) {
1233                 localsSize = index + 1;
1234             }
1235             return ret;
1236         }
1237 
1238         void setLocal(int index, Type type) {
1239             Type old = getLocalRawInternal(index);
1240             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1241                 setLocalRawInternal(index + 1, Type.TOP_TYPE);
1242             }
1243             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1244                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1245             }
1246             setLocalRawInternal(index, type);
1247             if (index >= localsSize) {
1248                 localsSize = index + 1;
1249             }
1250         }
1251 
1252         void setLocal2(int index, Type type1, Type type2) {
1253             Type old = getLocalRawInternal(index + 1);
1254             if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) {
1255                 setLocalRawInternal(index + 2, Type.TOP_TYPE);
1256             }
1257             old = getLocalRawInternal(index);
1258             if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) {
1259                 setLocalRawInternal(index - 1, Type.TOP_TYPE);
1260             }
1261             setLocalRawInternal(index, type1);
1262             setLocalRawInternal(index + 1, type2);
1263             if (index >= localsSize - 1) {
1264                 localsSize = index + 2;
1265             }
1266         }
1267 
1268         private Type merge(Type me, Type[] toTypes, int i, Frame target) {
1269             var to = toTypes[i];
1270             var newTo = to.mergeFrom(me, classHierarchy);
1271             if (to != newTo && !to.equals(newTo)) {
1272                 toTypes[i] = newTo;
1273                 target.dirty = true;
1274             }
1275             return newTo;
1276         }
1277 
1278         // Merge this frame's unset fields into the target frame
1279         private void mergeUnsetFields(Frame target) {
1280             int myUnsetSize = unsetFieldsSize;
1281             int targetUnsetSize = target.unsetFieldsSize;
1282             var myUnsets = unsetFields;
1283             var targetUnsets = target.unsetFields;
1284             if (UnsetField.matches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) {
1285                 return; // no merge
1286             }
1287             // merge sort
1288             var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length];
1289             int mergedSize = 0;
1290             int i = 0;
1291             int j = 0;
1292             while (i < myUnsetSize && j < targetUnsetSize) {
1293                 var myCandidate = myUnsets[i];
1294                 var targetCandidate = targetUnsets[j];
1295                 var cmp = myCandidate.compareTo(targetCandidate);
1296                 if (cmp == 0) {
1297                     merged[mergedSize++] = myCandidate;
1298                     i++;
1299                     j++;
1300                 } else if (cmp < 0) {
1301                     merged[mergedSize++] = myCandidate;
1302                     i++;
1303                 } else {
1304                     merged[mergedSize++] = targetCandidate;
1305                     j++;
1306                 }
1307             }
1308             if (i < myUnsetSize) {
1309                 int len = myUnsetSize - i;
1310                 System.arraycopy(myUnsets, i, merged, mergedSize, len);
1311                 mergedSize += len;
1312             } else if (j < targetUnsetSize) {
1313                 int len = targetUnsetSize - j;
1314                 System.arraycopy(targetUnsets, j, merged, mergedSize, len);
1315                 mergedSize += len;
1316             }
1317 
1318             target.unsetFieldsSize = mergedSize;
1319             target.unsetFields = merged;
1320             target.dirty = true;
1321         }
1322 
1323         private static int trimAndCompress(Type[] types, int count) {
1324             while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--;
1325             int compressed = 0;
1326             for (int i = 0; i < count; i++) {
1327                 if (!types[i].isCategory2_2nd()) {
1328                     if (compressed != i) {
1329                         types[compressed] = types[i];
1330                     }
1331                     compressed++;
1332                 }
1333             }
1334             return compressed;
1335         }
1336 
1337         void trimAndCompress() {
1338             localsSize = trimAndCompress(locals, localsSize);
1339             stackSize = trimAndCompress(stack, stackSize);
1340         }
1341 
1342         boolean hasUninitializedThis() {
1343             int size = this.localsSize;
1344             var localVars = this.locals;
1345             for (int i = 0; i < size; i++) {
1346                 if (localVars[i] == Type.UNITIALIZED_THIS_TYPE)
1347                     return true;
1348             }
1349             return false;
1350         }
1351 
1352         private static boolean equals(Type[] l1, Type[] l2, int commonSize) {
1353             if (l1 == null || l2 == null) return commonSize == 0;
1354             return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize);
1355         }
1356 
1357         // In sync with StackMapDecoder::needsLarvalFrameForTransition
1358         private boolean needsLarvalFrame(Frame prevFrame) {
1359             if (UnsetField.matches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize))
1360                 return false;
1361             if (!hasUninitializedThis()) {
1362                 assert unsetFieldsSize == 0 : this; // Should have been handled by processInvokeInstructions
1363                 return false;
1364             }
1365             return true;
1366         }
1367 
1368         void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) {
1369             // enclosing frames
1370             if (needsLarvalFrame(prevFrame)) {
1371                 out.writeU1U2(EARLY_LARVAL, unsetFieldsSize);
1372                 for (int i = 0; i < unsetFieldsSize; i++) {
1373                     var f = unsetFields[i];
1374                     out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type()));
1375                 }
1376             }
1377             // base frame
1378             int localsSize = this.localsSize;
1379             int stackSize = this.stackSize;
1380             int offsetDelta = offset - prevFrame.offset - 1;
1381             if (stackSize == 0) {
1382                 int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize;
1383                 int diffLocalsSize = localsSize - prevFrame.localsSize;
1384                 if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) {
1385                     if (diffLocalsSize == 0 && offsetDelta <= SAME_FRAME_END) { //same frame
1386                         out.writeU1(offsetDelta);
1387                     } else {   //chop, same extended or append frame
1388                         out.writeU1U2(SAME_FRAME_EXTENDED + diffLocalsSize, offsetDelta);
1389                         for (int i=commonLocalsSize; i<localsSize; i++) locals[i].writeTo(out, cp);
1390                     }
1391                     return;
1392                 }
1393             } else if (stackSize == 1 && localsSize == prevFrame.localsSize && equals(locals, prevFrame.locals, localsSize)) {
1394                 if (offsetDelta <= SAME_LOCALS_1_STACK_ITEM_FRAME_END  - SAME_LOCALS_1_STACK_ITEM_FRAME_START) {  //same locals 1 stack item frame
1395                     out.writeU1(SAME_LOCALS_1_STACK_ITEM_FRAME_START + offsetDelta);
1396                 } else {  //same locals 1 stack item extended frame
1397                     out.writeU1U2(SAME_LOCALS_1_STACK_ITEM_EXTENDED, offsetDelta);
1398                 }
1399                 stack[0].writeTo(out, cp);
1400                 return;
1401             }
1402             //full frame
1403             out.writeU1U2U2(FULL_FRAME, offsetDelta, localsSize);
1404             for (int i=0; i<localsSize; i++) locals[i].writeTo(out, cp);
1405             out.writeU2(stackSize);
1406             for (int i=0; i<stackSize; i++) stack[i].writeTo(out, cp);
1407         }
1408     }
1409 
1410     private static record Type(int tag, ClassDesc sym, int bci) {
1411 
1412         //singleton types
1413         static final Type TOP_TYPE = simpleType(ITEM_TOP),
1414                 NULL_TYPE = simpleType(ITEM_NULL),
1415                 INTEGER_TYPE = simpleType(ITEM_INTEGER),
1416                 FLOAT_TYPE = simpleType(ITEM_FLOAT),
1417                 LONG_TYPE = simpleType(ITEM_LONG),
1418                 LONG2_TYPE = simpleType(ITEM_LONG_2ND),
1419                 DOUBLE_TYPE = simpleType(ITEM_DOUBLE),
1420                 BOOLEAN_TYPE = simpleType(ITEM_BOOLEAN),
1421                 BYTE_TYPE = simpleType(ITEM_BYTE),
1422                 CHAR_TYPE = simpleType(ITEM_CHAR),
1423                 SHORT_TYPE = simpleType(ITEM_SHORT),
1424                 DOUBLE2_TYPE = simpleType(ITEM_DOUBLE_2ND),
1425                 UNITIALIZED_THIS_TYPE = simpleType(ITEM_UNINITIALIZED_THIS);
1426 
1427         //frequently used types to reduce footprint
1428         static final Type OBJECT_TYPE = referenceType(CD_Object),
1429             THROWABLE_TYPE = referenceType(CD_Throwable),
1430             INT_ARRAY_TYPE = referenceType(CD_int.arrayType()),
1431             BOOLEAN_ARRAY_TYPE = referenceType(CD_boolean.arrayType()),
1432             BYTE_ARRAY_TYPE = referenceType(CD_byte.arrayType()),
1433             CHAR_ARRAY_TYPE = referenceType(CD_char.arrayType()),
1434             SHORT_ARRAY_TYPE = referenceType(CD_short.arrayType()),
1435             LONG_ARRAY_TYPE = referenceType(CD_long.arrayType()),
1436             DOUBLE_ARRAY_TYPE = referenceType(CD_double.arrayType()),
1437             FLOAT_ARRAY_TYPE = referenceType(CD_float.arrayType()),
1438             STRING_TYPE = referenceType(CD_String),
1439             CLASS_TYPE = referenceType(CD_Class),
1440             METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle),
1441             METHOD_TYPE = referenceType(CD_MethodType);
1442 
1443         private static Type simpleType(int tag) {
1444             return new Type(tag, null, 0);
1445         }
1446 
1447         static Type referenceType(ClassDesc desc) {
1448             return new Type(ITEM_OBJECT, desc, 0);
1449         }
1450 
1451         static Type uninitializedType(int bci) {
1452             return new Type(ITEM_UNINITIALIZED, null, bci);
1453         }
1454 
1455         @Override //mandatory override to avoid use of method reference during JDK bootstrap
1456         public boolean equals(Object o) {
1457             return (o instanceof Type t) && t.tag == tag && t.bci == bci && Objects.equals(sym, t.sym);
1458         }
1459 
1460         boolean isCategory2_2nd() {
1461             return this == DOUBLE2_TYPE || this == LONG2_TYPE;
1462         }
1463 
1464         boolean isReference() {
1465             return tag == ITEM_OBJECT || this == NULL_TYPE;
1466         }
1467 
1468         boolean isObject() {
1469             return tag == ITEM_OBJECT && sym.isClassOrInterface();
1470         }
1471 
1472         boolean isArray() {
1473             return tag == ITEM_OBJECT && sym.isArray();
1474         }
1475 
1476         Type mergeFrom(Type from, ClassHierarchyImpl context) {
1477             if (this == TOP_TYPE || this == from || equals(from)) {
1478                 return this;
1479             } else {
1480                 return switch (tag) {
1481                     case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT ->
1482                         from == INTEGER_TYPE ? this : TOP_TYPE;
1483                     default ->
1484                         isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE;
1485                 };
1486             }
1487         }
1488 
1489         Type mergeComponentFrom(Type from, ClassHierarchyImpl context) {
1490             if (this == TOP_TYPE || this == from || equals(from)) {
1491                 return this;
1492             } else {
1493                 return switch (tag) {
1494                     case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT ->
1495                         TOP_TYPE;
1496                     default ->
1497                         isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE;
1498                 };
1499             }
1500         }
1501 
1502         private static final ClassDesc CD_Cloneable = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Cloneable;");
1503         private static final ClassDesc CD_Serializable = ClassOrInterfaceDescImpl.ofValidated("Ljava/io/Serializable;");
1504 
1505         private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) {
1506             if (from == NULL_TYPE) {
1507                 return this;
1508             } else if (this == NULL_TYPE) {
1509                 return from;
1510             } else if (sym.equals(from.sym)) {
1511                 return this;
1512             } else if (isObject()) {
1513                 if (CD_Object.equals(sym)) {
1514                     return this;
1515                 }
1516                 if (context.isInterface(sym)) {
1517                     if (!from.isArray() || CD_Cloneable.equals(sym) || CD_Serializable.equals(sym)) {
1518                         return this;
1519                     }
1520                 } else if (from.isObject()) {
1521                     var anc = context.commonAncestor(sym, from.sym);
1522                     return anc == null ? this : Type.referenceType(anc);
1523                 }
1524             } else if (isArray() && from.isArray()) {
1525                 Type compThis = getComponent();
1526                 Type compFrom = from.getComponent();
1527                 if (compThis != TOP_TYPE && compFrom != TOP_TYPE) {
1528                     return  compThis.mergeComponentFrom(compFrom, context).toArray();
1529                 }
1530             }
1531             return OBJECT_TYPE;
1532         }
1533 
1534         Type toArray() {
1535             return switch (tag) {
1536                 case ITEM_BOOLEAN -> BOOLEAN_ARRAY_TYPE;
1537                 case ITEM_BYTE -> BYTE_ARRAY_TYPE;
1538                 case ITEM_CHAR -> CHAR_ARRAY_TYPE;
1539                 case ITEM_SHORT -> SHORT_ARRAY_TYPE;
1540                 case ITEM_INTEGER -> INT_ARRAY_TYPE;
1541                 case ITEM_LONG -> LONG_ARRAY_TYPE;
1542                 case ITEM_FLOAT -> FLOAT_ARRAY_TYPE;
1543                 case ITEM_DOUBLE -> DOUBLE_ARRAY_TYPE;
1544                 case ITEM_OBJECT -> Type.referenceType(sym.arrayType());
1545                 default -> OBJECT_TYPE;
1546             };
1547         }
1548 
1549         Type getComponent() {
1550             if (isArray()) {
1551                 var comp = sym.componentType();
1552                 if (comp.isPrimitive()) {
1553                     return switch (comp.descriptorString().charAt(0)) {
1554                         case 'Z' -> Type.BOOLEAN_TYPE;
1555                         case 'B' -> Type.BYTE_TYPE;
1556                         case 'C' -> Type.CHAR_TYPE;
1557                         case 'S' -> Type.SHORT_TYPE;
1558                         case 'I' -> Type.INTEGER_TYPE;
1559                         case 'J' -> Type.LONG_TYPE;
1560                         case 'F' -> Type.FLOAT_TYPE;
1561                         case 'D' -> Type.DOUBLE_TYPE;
1562                         default -> Type.TOP_TYPE;
1563                     };
1564                 }
1565                 return Type.referenceType(comp);
1566             }
1567             return Type.TOP_TYPE;
1568         }
1569 
1570         void writeTo(BufWriterImpl bw, ConstantPoolBuilder cp) {
1571             switch (tag) {
1572                 case ITEM_OBJECT ->
1573                     bw.writeU1U2(tag, cp.classEntry(sym).index());
1574                 case ITEM_UNINITIALIZED ->
1575                     bw.writeU1U2(tag, bci);
1576                 default ->
1577                     bw.writeU1(tag);
1578             }
1579         }
1580     }
1581 }