1 /*
  2  * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.classfile.impl;
 27 
 28 import java.lang.classfile.BufWriter;
 29 import java.lang.classfile.ClassModel;
 30 import java.lang.classfile.ClassReader;
 31 import java.lang.classfile.Label;
 32 import java.lang.classfile.MethodModel;
 33 import java.lang.classfile.attribute.StackMapFrameInfo;
 34 import java.lang.classfile.attribute.StackMapFrameInfo.ObjectVerificationTypeInfo;
 35 import java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo;
 36 import java.lang.classfile.attribute.StackMapFrameInfo.UninitializedVerificationTypeInfo;
 37 import java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo;
 38 import java.lang.classfile.constantpool.ClassEntry;
 39 import java.lang.classfile.constantpool.NameAndTypeEntry;
 40 import java.lang.classfile.constantpool.PoolEntry;
 41 import java.lang.classfile.constantpool.Utf8Entry;
 42 import java.lang.constant.ConstantDescs;
 43 import java.lang.constant.MethodTypeDesc;
 44 import java.lang.reflect.AccessFlag;
 45 import java.util.ArrayList;
 46 import java.util.Arrays;
 47 import java.util.Comparator;
 48 import java.util.List;
 49 import java.util.Objects;
 50 
 51 import jdk.internal.access.SharedSecrets;
 52 
 53 import static java.lang.classfile.ClassFile.*;
 54 import static java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo.*;
 55 import static java.util.Objects.requireNonNull;
 56 
 57 public class StackMapDecoder {
 58 
 59     static final int
 60                     EARLY_LARVAL = 246,
 61                     SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
 62                     SAME_EXTENDED = 251;
 63     private static final int BASE_FRAMES_UPPER_LIMIT = SAME_LOCALS_1_STACK_ITEM_EXTENDED; // not inclusive
 64     private static final StackMapFrameInfo[] NO_STACK_FRAME_INFOS = {};
 65 
 66     private final ClassReader classReader;
 67     private final int pos;
 68     private final LabelContext ctx;
 69     private final List<VerificationTypeInfo> initFrameLocals;
 70     private final List<NameAndTypeEntry> initFrameUnsets;
 71     private int p;
 72 
 73     StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List<VerificationTypeInfo> initFrameLocals,
 74                     List<NameAndTypeEntry> initFrameUnsets) {
 75         this.classReader = classReader;
 76         this.pos = pos;
 77         this.ctx = ctx;
 78         this.initFrameLocals = initFrameLocals;
 79         this.initFrameUnsets = initFrameUnsets;
 80     }
 81 
 82     static List<VerificationTypeInfo> initFrameLocals(MethodModel method) {
 83         return initFrameLocals(method.parent().orElseThrow().thisClass(),
 84                 method.methodName().stringValue(),
 85                 method.methodTypeSymbol(),
 86                 method.flags().has(AccessFlag.STATIC));
 87     }
 88 
 89     public static List<VerificationTypeInfo> initFrameLocals(ClassEntry thisClass, String methodName, MethodTypeDesc methodType, boolean isStatic) {
 90         VerificationTypeInfo vtis[];
 91         int i = 0;
 92         if (!isStatic) {
 93             vtis = new VerificationTypeInfo[methodType.parameterCount() + 1];
 94             if ("<init>".equals(methodName) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) {
 95                 vtis[i++] = SimpleVerificationTypeInfo.UNINITIALIZED_THIS;
 96             } else {
 97                 vtis[i++] = new StackMapDecoder.ObjectVerificationTypeInfoImpl(thisClass);
 98             }
 99         } else {
100             vtis = new VerificationTypeInfo[methodType.parameterCount()];
101         }
102         for (int pi = 0; pi < methodType.parameterCount(); pi++) {
103             var arg = methodType.parameterType(pi);
104             vtis[i++] = switch (arg.descriptorString().charAt(0)) {
105                 case 'I', 'S', 'C' ,'B', 'Z' -> SimpleVerificationTypeInfo.INTEGER;
106                 case 'J' -> SimpleVerificationTypeInfo.LONG;
107                 case 'F' -> SimpleVerificationTypeInfo.FLOAT;
108                 case 'D' -> SimpleVerificationTypeInfo.DOUBLE;
109                 case 'V' -> throw new IllegalArgumentException("Illegal method argument type: " + arg);
110                 default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg));
111             };
112         }
113         return List.of(vtis);
114     }
115 
116     static List<NameAndTypeEntry> initFrameUnsets(MethodModel method) {
117         return initFrameUnsets(method.parent().orElseThrow(),
118                 method.methodName());
119     }
120 
121     private static List<NameAndTypeEntry> initFrameUnsets(ClassModel clazz, Utf8Entry methodName) {
122         if (!methodName.equalsString(ConstantDescs.INIT_NAME))
123             return List.of();
124         if (clazz.minorVersion() != PREVIEW_MINOR_VERSION || clazz.majorVersion() < Util.VALUE_OBJECTS_MAJOR)
125             return List.of();
126         var l = new ArrayList<NameAndTypeEntry>(clazz.fields().size());
127         for (var field : clazz.fields()) {
128             if ((field.flags().flagsMask() & (ACC_STATIC | ACC_STRICT_INIT)) == ACC_STRICT_INIT) { // instance strict
129                 l.add(TemporaryConstantPool.INSTANCE.nameAndTypeEntry(field.fieldName(), field.fieldType()));
130             }
131         }
132         return List.copyOf(l);
133     }
134 
135     private static List<NameAndTypeEntry> initFrameUnsets(MethodInfo mi, WritableField.UnsetField[] unsets) {
136         if (!mi.methodName().equalsString(ConstantDescs.INIT_NAME))
137             return List.of();
138         var l = new ArrayList<NameAndTypeEntry>(unsets.length);
139         for (var field : unsets) {
140             l.add(TemporaryConstantPool.INSTANCE.nameAndTypeEntry(field.name(), field.type()));
141         }
142         return List.copyOf(l);
143     }
144 
145     public static void writeFrames(BufWriter b, List<StackMapFrameInfo> entries) {
146         var buf = (BufWriterImpl)b;
147         var dcb = (DirectCodeBuilder)buf.labelContext();
148         var mi = dcb.methodInfo();
149         var prevLocals = StackMapDecoder.initFrameLocals(buf.thisClass(),
150                 mi.methodName().stringValue(),
151                 mi.methodTypeSymbol(),
152                 (mi.methodFlags() & ACC_STATIC) != 0);
153         var prevUnsets = initFrameUnsets(mi, buf.getStrictInstanceFields());
154         int prevOffset = -1;
155         // avoid using method handles due to early bootstrap
156         StackMapFrameInfo[] infos = entries.toArray(NO_STACK_FRAME_INFOS);
157         //sort by resolved label offsets first to allow unordered entries
158         Arrays.sort(infos, new Comparator<StackMapFrameInfo>() {
159             public int compare(final StackMapFrameInfo o1, final StackMapFrameInfo o2) {
160                 return Integer.compare(dcb.labelToBci(o1.target()), dcb.labelToBci(o2.target()));
161             }
162         });
163         b.writeU2(infos.length);
164         for (var fr : infos) {
165             int offset = dcb.labelToBci(fr.target());
166             if (offset == prevOffset) {
167                 throw new IllegalArgumentException("Duplicated stack frame bytecode index: " + offset);
168             }
169             writeFrame(buf, offset - prevOffset - 1, prevLocals, prevUnsets, fr);
170             prevOffset = offset;
171             prevLocals = fr.locals();
172             prevUnsets = fr.unsetFields();
173         }
174     }
175 
176     // In sync with StackMapGenerator::needsLarvalFrame
177     private static boolean needsLarvalFrameForTransition(List<NameAndTypeEntry> prevUnsets, StackMapFrameInfo fr) {
178         if (prevUnsets.equals(fr.unsetFields()))
179             return false;
180         if (!fr.locals().contains(SimpleVerificationTypeInfo.UNINITIALIZED_THIS)) {
181             assert fr.unsetFields().isEmpty() : fr; // should be checked in StackMapFrameInfo constructor
182             return false;
183         }
184         return true;
185     }
186 
187     private static void writeFrame(BufWriterImpl out, int offsetDelta, List<VerificationTypeInfo> prevLocals, List<NameAndTypeEntry> prevUnsets, StackMapFrameInfo fr) {
188         if (offsetDelta < 0) throw new IllegalArgumentException("Invalid stack map frames order");
189         // enclosing frames
190         if (needsLarvalFrameForTransition(prevUnsets, fr)) {
191             out.writeU1(EARLY_LARVAL);
192             Util.writeListIndices(out, fr.unsetFields());
193         }
194         // base frame
195         if (fr.stack().isEmpty()) {
196             int commonLocalsSize = Math.min(prevLocals.size(), fr.locals().size());
197             int diffLocalsSize = fr.locals().size() - prevLocals.size();
198             if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(fr.locals(), prevLocals, commonLocalsSize)) {
199                 if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame
200                     out.writeU1(offsetDelta);
201                 } else {   //chop, same extended or append frame
202                     out.writeU1U2(251 + diffLocalsSize, offsetDelta);
203                     for (int i=commonLocalsSize; i<fr.locals().size(); i++) writeTypeInfo(out, fr.locals().get(i));
204                 }
205                 return;
206             }
207         } else if (fr.stack().size() == 1 && fr.locals().equals(prevLocals)) {
208             if (offsetDelta < 64) {  //same locals 1 stack item frame
209                 out.writeU1(64 + offsetDelta);
210             } else {  //same locals 1 stack item extended frame
211                 out.writeU1U2(247, offsetDelta);
212             }
213             writeTypeInfo(out, fr.stack().get(0));
214             return;
215         }
216         //full frame
217         out.writeU1U2U2(255, offsetDelta, fr.locals().size());
218         for (var l : fr.locals()) writeTypeInfo(out, l);
219         out.writeU2(fr.stack().size());
220         for (var s : fr.stack()) writeTypeInfo(out, s);
221     }
222 
223     private static boolean equals(List<VerificationTypeInfo> l1, List<VerificationTypeInfo> l2, int compareSize) {
224         for (int i = 0; i < compareSize; i++) {
225             if (!l1.get(i).equals(l2.get(i))) return false;
226         }
227         return true;
228     }
229 
230     private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) {
231         int tag = vti.tag();
232         switch (tag) {
233             case ITEM_TOP, ITEM_INTEGER, ITEM_FLOAT, ITEM_DOUBLE, ITEM_LONG, ITEM_NULL,
234                  ITEM_UNINITIALIZED_THIS ->
235                 bw.writeU1(tag);
236             case ITEM_OBJECT ->
237                 bw.writeU1U2(tag, bw.cpIndex(((ObjectVerificationTypeInfo)vti).className()));
238             case ITEM_UNINITIALIZED ->
239                 bw.writeU1U2(tag, bw.labelContext().labelToBci(((UninitializedVerificationTypeInfo)vti).newTarget()));
240             default -> throw new IllegalArgumentException("Invalid verification type tag: " + vti.tag());
241         }
242     }
243 
244     // Copied from BoundAttribute
245     <E extends PoolEntry> List<E> readEntryList(int p, Class<E> type) {
246         int cnt = classReader.readU2(p);
247         p += 2;
248         var entries = new Object[cnt];
249         int end = p + (cnt * 2);
250         for (int i = 0; p < end; i++, p += 2) {
251             entries[i] = classReader.readEntry(p, type);
252         }
253         return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries);
254     }
255 
256     List<StackMapFrameInfo> entries() {
257         p = pos;
258         List<VerificationTypeInfo> locals = initFrameLocals, stack = List.of();
259         List<NameAndTypeEntry> unsetFields = initFrameUnsets;
260         int bci = -1;
261         var entries = new StackMapFrameInfo[u2()];
262         for (int ei = 0; ei < entries.length; ei++) {
263             int actualFrameType = classReader.readU1(p++);
264             int frameType = actualFrameType; // effective frame type for parsing
265             // enclosing frames handling
266             if (frameType == EARLY_LARVAL) {
267                 unsetFields = readEntryList(p, NameAndTypeEntry.class);
268                 p += 2 + unsetFields.size() * 2;
269                 frameType = classReader.readU1(p++);
270             }
271             // base frame handling
272             if (frameType < 64) {
273                 bci += frameType + 1;
274                 stack = List.of();
275             } else if (frameType < 128) {
276                 bci += frameType - 63;
277                 stack = List.of(readVerificationTypeInfo());
278             } else {
279                 if (frameType < BASE_FRAMES_UPPER_LIMIT)
280                     throw new IllegalArgumentException("Invalid base frame type: " + frameType);
281                 bci += u2() + 1;
282                 if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) {
283                     stack = List.of(readVerificationTypeInfo());
284                 } else if (frameType < SAME_EXTENDED) {
285                     locals = locals.subList(0, locals.size() + frameType - SAME_EXTENDED);
286                     stack = List.of();
287                 } else if (frameType == SAME_EXTENDED) {
288                     stack = List.of();
289                 } else if (frameType < SAME_EXTENDED + 4) {
290                     int actSize = locals.size();
291                     var newLocals = locals.toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]);
292                     for (int i = actSize; i < newLocals.length; i++)
293                         newLocals[i] = readVerificationTypeInfo();
294                     locals = List.of(newLocals);
295                     stack = List.of();
296                 } else {
297                     var newLocals = new VerificationTypeInfo[u2()];
298                     for (int i=0; i<newLocals.length; i++)
299                         newLocals[i] = readVerificationTypeInfo();
300                     var newStack = new VerificationTypeInfo[u2()];
301                     for (int i=0; i<newStack.length; i++)
302                         newStack[i] = readVerificationTypeInfo();
303                     locals = List.of(newLocals);
304                     stack = List.of(newStack);
305                 }
306             }
307             if (actualFrameType != EARLY_LARVAL && !unsetFields.isEmpty() && !locals.contains(SimpleVerificationTypeInfo.UNINITIALIZED_THIS)) {
308                 // clear unsets post larval
309                 unsetFields = List.of();
310             }
311             entries[ei] = new StackMapFrameImpl(actualFrameType,
312                     ctx.getLabel(bci),
313                     locals,
314                     stack,
315                     unsetFields);
316         }
317         return List.of(entries);
318     }
319 
320     private VerificationTypeInfo readVerificationTypeInfo() {
321         int tag = classReader.readU1(p++);
322         return switch (tag) {
323             case ITEM_TOP -> SimpleVerificationTypeInfo.TOP;
324             case ITEM_INTEGER -> SimpleVerificationTypeInfo.INTEGER;
325             case ITEM_FLOAT -> SimpleVerificationTypeInfo.FLOAT;
326             case ITEM_DOUBLE -> SimpleVerificationTypeInfo.DOUBLE;
327             case ITEM_LONG -> SimpleVerificationTypeInfo.LONG;
328             case ITEM_NULL -> SimpleVerificationTypeInfo.NULL;
329             case ITEM_UNINITIALIZED_THIS -> SimpleVerificationTypeInfo.UNINITIALIZED_THIS;
330             case ITEM_OBJECT -> new ObjectVerificationTypeInfoImpl(classReader.entryByIndex(u2(), ClassEntry.class));
331             case ITEM_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(ctx.getLabel(u2()));
332             default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag);
333         };
334     }
335 
336     public static record ObjectVerificationTypeInfoImpl(
337             ClassEntry className) implements ObjectVerificationTypeInfo {
338         public ObjectVerificationTypeInfoImpl {
339             requireNonNull(className);
340         }
341 
342         @Override
343         public int tag() { return ITEM_OBJECT; }
344 
345         @Override
346         public boolean equals(Object o) {
347             if (this == o) return true;
348             if (o instanceof ObjectVerificationTypeInfoImpl that) {
349                 return Objects.equals(className, that.className);
350             }
351             return false;
352         }
353 
354         @Override
355         public int hashCode() {
356             return Objects.hash(className);
357         }
358 
359         @Override
360         public String toString() {
361             return className.asInternalName();
362         }
363     }
364 
365     public static record UninitializedVerificationTypeInfoImpl(Label newTarget) implements UninitializedVerificationTypeInfo {
366         public UninitializedVerificationTypeInfoImpl {
367             requireNonNull(newTarget);
368         }
369 
370         @Override
371         public int tag() { return ITEM_UNINITIALIZED; }
372 
373         @Override
374         public String toString() {
375             return "UNINIT(" + newTarget +")";
376         }
377     }
378 
379     private int u2() {
380         int v = classReader.readU2(p);
381         p += 2;
382         return v;
383     }
384 
385     public static record StackMapFrameImpl(int frameType,
386                                            Label target,
387                                            List<VerificationTypeInfo> locals,
388                                            List<VerificationTypeInfo> stack,
389                                            List<NameAndTypeEntry> unsetFields)
390             implements StackMapFrameInfo {
391         public StackMapFrameImpl {
392             requireNonNull(target);
393             locals = Util.sanitizeU2List(locals);
394             stack = Util.sanitizeU2List(stack);
395             unsetFields = Util.sanitizeU2List(unsetFields);
396 
397             uninitializedThisCheck:
398             if (!unsetFields.isEmpty()) {
399                 for (var local : locals) {
400                     if (local == SimpleVerificationTypeInfo.UNINITIALIZED_THIS) {
401                         break uninitializedThisCheck;
402                     }
403                 }
404                 throw new IllegalArgumentException("unset fields requires uninitializedThis in locals");
405             }
406         }
407 
408         public StackMapFrameImpl(int frameType,
409                                  Label target,
410                                  List<VerificationTypeInfo> locals,
411                                  List<VerificationTypeInfo> stack) {
412             this(frameType, target, locals, stack, List.of());
413         }
414     }
415 }