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.ClassReader;
 30 import java.lang.classfile.Label;
 31 import java.lang.classfile.MethodModel;
 32 import java.lang.classfile.attribute.StackMapFrameInfo;
 33 import java.lang.classfile.attribute.StackMapFrameInfo.ObjectVerificationTypeInfo;
 34 import java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo;
 35 import java.lang.classfile.attribute.StackMapFrameInfo.UninitializedVerificationTypeInfo;
 36 import java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo;
 37 import java.lang.classfile.constantpool.ClassEntry;
 38 import java.lang.classfile.constantpool.NameAndTypeEntry;
 39 import java.lang.classfile.constantpool.PoolEntry;
 40 import java.lang.constant.ConstantDescs;
 41 import java.lang.constant.MethodTypeDesc;
 42 import java.lang.reflect.AccessFlag;
 43 import java.util.ArrayList;
 44 import java.util.Arrays;
 45 import java.util.Comparator;
 46 import java.util.List;
 47 import java.util.Objects;
 48 
 49 import jdk.internal.access.SharedSecrets;
 50 
 51 import static java.lang.classfile.ClassFile.ACC_STATIC;
 52 import static java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo.*;
 53 import static java.util.Objects.requireNonNull;
 54 
 55 public class StackMapDecoder {
 56 
 57     static final int
 58                     ASSERT_UNSET_FIELDS = 246,
 59                     SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247,
 60                     SAME_EXTENDED = 251;
 61     private static final int RESERVED_TAGS_UPPER_LIMIT = ASSERT_UNSET_FIELDS; // not inclusive
 62     private static final StackMapFrameInfo[] NO_STACK_FRAME_INFOS = {};
 63 
 64     private final ClassReader classReader;
 65     private final int pos;
 66     private final LabelContext ctx;
 67     private final List<VerificationTypeInfo> initFrameLocals;
 68     private int p;
 69 
 70     StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List<VerificationTypeInfo> initFrameLocals) {
 71         this.classReader = classReader;
 72         this.pos = pos;
 73         this.ctx = ctx;
 74         this.initFrameLocals = initFrameLocals;
 75     }
 76 
 77     static List<VerificationTypeInfo> initFrameLocals(MethodModel method) {
 78         return initFrameLocals(method.parent().orElseThrow().thisClass(),
 79                 method.methodName().stringValue(),
 80                 method.methodTypeSymbol(),
 81                 method.flags().has(AccessFlag.STATIC));
 82     }
 83 
 84     public static List<VerificationTypeInfo> initFrameLocals(ClassEntry thisClass, String methodName, MethodTypeDesc methodType, boolean isStatic) {
 85         VerificationTypeInfo vtis[];
 86         int i = 0;
 87         if (!isStatic) {
 88             vtis = new VerificationTypeInfo[methodType.parameterCount() + 1];
 89             if ("<init>".equals(methodName) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) {
 90                 vtis[i++] = SimpleVerificationTypeInfo.UNINITIALIZED_THIS;
 91             } else {
 92                 vtis[i++] = new StackMapDecoder.ObjectVerificationTypeInfoImpl(thisClass);
 93             }
 94         } else {
 95             vtis = new VerificationTypeInfo[methodType.parameterCount()];
 96         }
 97         for (int pi = 0; pi < methodType.parameterCount(); pi++) {
 98             var arg = methodType.parameterType(pi);
 99             vtis[i++] = switch (arg.descriptorString().charAt(0)) {
100                 case 'I', 'S', 'C' ,'B', 'Z' -> SimpleVerificationTypeInfo.INTEGER;
101                 case 'J' -> SimpleVerificationTypeInfo.LONG;
102                 case 'F' -> SimpleVerificationTypeInfo.FLOAT;
103                 case 'D' -> SimpleVerificationTypeInfo.DOUBLE;
104                 case 'V' -> throw new IllegalArgumentException("Illegal method argument type: " + arg);
105                 default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg));
106             };
107         }
108         return List.of(vtis);
109     }
110 
111     public static void writeFrames(BufWriter b, List<StackMapFrameInfo> entries) {
112         var buf = (BufWriterImpl)b;
113         var dcb = (DirectCodeBuilder)buf.labelContext();
114         var mi = dcb.methodInfo();
115         var prevLocals = StackMapDecoder.initFrameLocals(buf.thisClass(),
116                 mi.methodName().stringValue(),
117                 mi.methodTypeSymbol(),
118                 (mi.methodFlags() & ACC_STATIC) != 0);
119         int prevOffset = -1;
120         // avoid using method handles due to early bootstrap
121         StackMapFrameInfo[] infos = entries.toArray(NO_STACK_FRAME_INFOS);
122         //sort by resolved label offsets first to allow unordered entries
123         Arrays.sort(infos, new Comparator<StackMapFrameInfo>() {
124             public int compare(final StackMapFrameInfo o1, final StackMapFrameInfo o2) {
125                 return Integer.compare(dcb.labelToBci(o1.target()), dcb.labelToBci(o2.target()));
126             }
127         });
128         b.writeU2(infos.length);
129         for (var fr : infos) {
130             int offset = dcb.labelToBci(fr.target());
131             if (offset == prevOffset) {
132                 throw new IllegalArgumentException("Duplicated stack frame bytecode index: " + offset);
133             }
134             writeFrame(buf, offset - prevOffset - 1, prevLocals, fr);
135             prevOffset = offset;
136             prevLocals = fr.locals();
137         }
138     }
139 
140     private static void writeFrame(BufWriterImpl out, int offsetDelta, List<VerificationTypeInfo> prevLocals, StackMapFrameInfo fr) {
141         if (offsetDelta < 0) throw new IllegalArgumentException("Invalid stack map frames order");
142         if (fr.stack().isEmpty()) {
143             int commonLocalsSize = Math.min(prevLocals.size(), fr.locals().size());
144             int diffLocalsSize = fr.locals().size() - prevLocals.size();
145             if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(fr.locals(), prevLocals, commonLocalsSize)) {
146                 if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame
147                     out.writeU1(offsetDelta);
148                 } else {   //chop, same extended or append frame
149                     out.writeU1U2(251 + diffLocalsSize, offsetDelta);
150                     for (int i=commonLocalsSize; i<fr.locals().size(); i++) writeTypeInfo(out, fr.locals().get(i));
151                 }
152                 return;
153             }
154         } else if (fr.stack().size() == 1 && fr.locals().equals(prevLocals)) {
155             if (offsetDelta < 64) {  //same locals 1 stack item frame
156                 out.writeU1(64 + offsetDelta);
157             } else {  //same locals 1 stack item extended frame
158                 out.writeU1U2(247, offsetDelta);
159             }
160             writeTypeInfo(out, fr.stack().get(0));
161             return;
162         }
163         //full frame
164         out.writeU1U2U2(255, offsetDelta, fr.locals().size());
165         for (var l : fr.locals()) writeTypeInfo(out, l);
166         out.writeU2(fr.stack().size());
167         for (var s : fr.stack()) writeTypeInfo(out, s);
168     }
169 
170     private static boolean equals(List<VerificationTypeInfo> l1, List<VerificationTypeInfo> l2, int compareSize) {
171         for (int i = 0; i < compareSize; i++) {
172             if (!l1.get(i).equals(l2.get(i))) return false;
173         }
174         return true;
175     }
176 
177     private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) {
178         int tag = vti.tag();
179         switch (tag) {
180             case ITEM_TOP, ITEM_INTEGER, ITEM_FLOAT, ITEM_DOUBLE, ITEM_LONG, ITEM_NULL,
181                  ITEM_UNINITIALIZED_THIS ->
182                 bw.writeU1(tag);
183             case ITEM_OBJECT ->
184                 bw.writeU1U2(tag, bw.cpIndex(((ObjectVerificationTypeInfo)vti).className()));
185             case ITEM_UNINITIALIZED ->
186                 bw.writeU1U2(tag, bw.labelContext().labelToBci(((UninitializedVerificationTypeInfo)vti).newTarget()));
187             default -> throw new IllegalArgumentException("Invalid verification type tag: " + vti.tag());
188         }
189     }
190 
191     // Copied from BoundAttribute
192     <E extends PoolEntry> List<E> readEntryList(int p, Class<E> type) {
193         int cnt = classReader.readU2(p);
194         p += 2;
195         var entries = new Object[cnt];
196         int end = p + (cnt * 2);
197         for (int i = 0; p < end; i++, p += 2) {
198             entries[i] = classReader.readEntry(p, type);
199         }
200         return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries);
201     }
202 
203     List<StackMapFrameInfo> entries() {
204         p = pos;
205         List<VerificationTypeInfo> locals = initFrameLocals, stack = List.of();
206         List<NameAndTypeEntry> unsetFields = List.of();
207         int bci = -1;
208         int len = u2();
209         var entries = new ArrayList<StackMapFrameInfo>(len);
210         List<List<NameAndTypeEntry>> deferredUnsetFields = new ArrayList<>();
211         for (int ei = 0; ei < len; ei++) {
212             var oldLocals = locals;
213             var oldStack = stack;
214             int frameType = classReader.readU1(p++);
215             if (frameType < 64) {
216                 bci += frameType + 1;
217                 stack = List.of();
218             } else if (frameType < 128) {
219                 bci += frameType - 63;
220                 stack = List.of(readVerificationTypeInfo());
221             } else {
222                 if (frameType < RESERVED_TAGS_UPPER_LIMIT)
223                     throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType);
224                 if (frameType == ASSERT_UNSET_FIELDS) {
225                     unsetFields = readEntryList(p, NameAndTypeEntry.class);
226                     p += 2 + unsetFields.size() * 2;
227                     deferredUnsetFields.add(unsetFields);
228                     continue; // defer entry until we can get the bci
229                 }
230                 bci += u2() + 1;
231                 if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) {
232                     stack = List.of(readVerificationTypeInfo());
233                 } else if (frameType < SAME_EXTENDED) {
234                     locals = locals.subList(0, locals.size() + frameType - SAME_EXTENDED);
235                     stack = List.of();
236                 } else if (frameType == SAME_EXTENDED) {
237                     stack = List.of();
238                 } else if (frameType < SAME_EXTENDED + 4) {
239                     int actSize = locals.size();
240                     var newLocals = locals.toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]);
241                     for (int i = actSize; i < newLocals.length; i++)
242                         newLocals[i] = readVerificationTypeInfo();
243                     locals = List.of(newLocals);
244                     stack = List.of();
245                 } else {
246                     var newLocals = new VerificationTypeInfo[u2()];
247                     for (int i=0; i<newLocals.length; i++)
248                         newLocals[i] = readVerificationTypeInfo();
249                     var newStack = new VerificationTypeInfo[u2()];
250                     for (int i=0; i<newStack.length; i++)
251                         newStack[i] = readVerificationTypeInfo();
252                     locals = List.of(newLocals);
253                     stack = List.of(newStack);
254                 }
255             }
256             Label label = ctx.getLabel(bci);
257             if (!deferredUnsetFields.isEmpty()) {
258                 // technically we only have one assert at once, just in case
259                 // of duplicate asserts...
260                 for (var deferredList : deferredUnsetFields) {
261                     entries.add(new StackMapFrameImpl(ASSERT_UNSET_FIELDS,
262                                 label, oldLocals, oldStack, deferredList));
263                 }
264                 deferredUnsetFields.clear();
265             }
266             entries.add(new StackMapFrameImpl(frameType,
267                         label,
268                         locals,
269                         stack));
270         }
271         return List.copyOf(entries);
272     }
273 
274     private VerificationTypeInfo readVerificationTypeInfo() {
275         int tag = classReader.readU1(p++);
276         return switch (tag) {
277             case ITEM_TOP -> SimpleVerificationTypeInfo.TOP;
278             case ITEM_INTEGER -> SimpleVerificationTypeInfo.INTEGER;
279             case ITEM_FLOAT -> SimpleVerificationTypeInfo.FLOAT;
280             case ITEM_DOUBLE -> SimpleVerificationTypeInfo.DOUBLE;
281             case ITEM_LONG -> SimpleVerificationTypeInfo.LONG;
282             case ITEM_NULL -> SimpleVerificationTypeInfo.NULL;
283             case ITEM_UNINITIALIZED_THIS -> SimpleVerificationTypeInfo.UNINITIALIZED_THIS;
284             case ITEM_OBJECT -> new ObjectVerificationTypeInfoImpl(classReader.entryByIndex(u2(), ClassEntry.class));
285             case ITEM_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(ctx.getLabel(u2()));
286             default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag);
287         };
288     }
289 
290     public static record ObjectVerificationTypeInfoImpl(
291             ClassEntry className) implements ObjectVerificationTypeInfo {
292         public ObjectVerificationTypeInfoImpl {
293             requireNonNull(className);
294         }
295 
296         @Override
297         public int tag() { return ITEM_OBJECT; }
298 
299         @Override
300         public boolean equals(Object o) {
301             if (this == o) return true;
302             if (o instanceof ObjectVerificationTypeInfoImpl that) {
303                 return Objects.equals(className, that.className);
304             }
305             return false;
306         }
307 
308         @Override
309         public int hashCode() {
310             return Objects.hash(className);
311         }
312 
313         @Override
314         public String toString() {
315             return className.asInternalName();
316         }
317     }
318 
319     public static record UninitializedVerificationTypeInfoImpl(Label newTarget) implements UninitializedVerificationTypeInfo {
320         public UninitializedVerificationTypeInfoImpl {
321             requireNonNull(newTarget);
322         }
323 
324         @Override
325         public int tag() { return ITEM_UNINITIALIZED; }
326 
327         @Override
328         public String toString() {
329             return "UNINIT(" + newTarget +")";
330         }
331     }
332 
333     private int u2() {
334         int v = classReader.readU2(p);
335         p += 2;
336         return v;
337     }
338 
339     public static record StackMapFrameImpl(int frameType,
340                                            Label target,
341                                            List<VerificationTypeInfo> locals,
342                                            List<VerificationTypeInfo> stack,
343                                            List<NameAndTypeEntry> unsetFields)
344             implements StackMapFrameInfo {
345         public StackMapFrameImpl {
346             requireNonNull(target);
347             locals = List.copyOf(locals);
348             stack = List.copyOf(stack);
349             unsetFields = List.copyOf(unsetFields);
350         }
351 
352         public StackMapFrameImpl(int frameType,
353                                  Label target,
354                                  List<VerificationTypeInfo> locals,
355                                  List<VerificationTypeInfo> stack) {
356             this(frameType, target, locals, stack, List.of());
357         }
358     }
359 }