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 }