1 /* 2 * Copyright (c) 2022, 2024, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary Testing ClassFile local variable table. 27 * @compile -g testdata/Lvt.java 28 * @run junit LvtTest 29 */ 30 import helpers.ClassRecord; 31 import helpers.Transforms; 32 import java.lang.classfile.*; 33 34 import java.io.*; 35 import java.lang.constant.ClassDesc; 36 import java.net.URI; 37 import java.nio.file.Files; 38 import java.nio.file.Paths; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 import java.lang.classfile.Attributes; 43 import java.lang.classfile.attribute.SourceFileAttribute; 44 import java.lang.classfile.constantpool.ConstantPoolBuilder; 45 import java.lang.classfile.constantpool.Utf8Entry; 46 import java.lang.classfile.instruction.LocalVariable; 47 import java.lang.classfile.instruction.LocalVariableType; 48 import java.lang.reflect.AccessFlag; 49 import org.junit.jupiter.api.Test; 50 51 import static helpers.TestConstants.CD_ArrayList; 52 import static helpers.TestConstants.CD_PrintStream; 53 import static helpers.TestConstants.CD_System; 54 import static helpers.TestConstants.MTD_INT_VOID; 55 import static helpers.TestConstants.MTD_VOID; 56 import static helpers.TestUtil.ExpectedLvRecord; 57 import static helpers.TestUtil.ExpectedLvtRecord; 58 import static java.lang.classfile.ClassFile.ACC_PUBLIC; 59 import static java.lang.classfile.ClassFile.ACC_STATIC; 60 import static java.lang.constant.ConstantDescs.*; 61 import java.lang.constant.MethodTypeDesc; 62 import static org.junit.jupiter.api.Assertions.*; 63 64 class LvtTest { 65 static byte[] fileBytes; 66 67 static { 68 try { 69 fileBytes = Files.readAllBytes(Paths.get(URI.create(testdata.Lvt.class.getResource("Lvt.class").toString()))); 70 } 71 catch (IOException e) { 72 throw new ExceptionInInitializerError(e); 73 } 74 } 75 76 @Test 77 void getLVTEntries() { 78 ClassModel c = ClassFile.of().parse(fileBytes); 79 CodeModel co = c.methods().stream() 80 .filter(mm -> mm.methodName().stringValue().equals("m")) 81 .map(MethodModel::code) 82 .findFirst() 83 .get() 84 .orElseThrow(); 85 86 List<LocalVariable> lvs = new ArrayList<>(); 87 co.forEach(e -> { 88 if (e instanceof LocalVariable l) lvs.add(l); 89 }); 90 91 List<ExpectedLvRecord> expected = List.of( 92 ExpectedLvRecord.of(5, "j", "I", 9, 21), 93 ExpectedLvRecord.of(0, "this", "Ltestdata/Lvt;", 0, 31), 94 ExpectedLvRecord.of(1, "a", "Ljava/lang/String;", 0, 31), 95 ExpectedLvRecord.of(4, "d", "[C", 6, 25)); 96 97 // Exploits non-symmetric "equals" in ExpectedLvRecord 98 assertTrue(expected.equals(lvs)); 99 } 100 101 @Test 102 void buildLVTEntries() throws Exception { 103 var cc = ClassFile.of(); 104 ClassModel c = cc.parse(fileBytes); 105 106 // Compare transformed model and original with CodeBuilder filter 107 byte[] newClass = cc.transformClass(c, Transforms.threeLevelNoop); 108 ClassRecord orig = ClassRecord.ofClassModel(cc.parse(fileBytes), ClassRecord.CompatibilityFilter.By_ClassBuilder); 109 ClassRecord transformed = ClassRecord.ofClassModel(cc.parse(newClass), ClassRecord.CompatibilityFilter.By_ClassBuilder); 110 ClassRecord.assertEqualsDeep(transformed, orig); 111 } 112 113 @Test 114 void testCreateLoadLVT() throws Exception { 115 var cc = ClassFile.of(); 116 byte[] bytes = cc.build(ClassDesc.of("MyClass"), cb -> { 117 cb.withFlags(AccessFlag.PUBLIC); 118 cb.withVersion(52, 0); 119 cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) 120 .withMethod("<init>", MethodTypeDesc.of(CD_void), 0, mb -> mb 121 .withCode(codeb -> codeb.aload(0) 122 .invokespecial(CD_Object, "<init>", MTD_VOID, false) 123 .return_() 124 ) 125 ) 126 .withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()), 127 ACC_PUBLIC | ACC_STATIC, 128 mb -> mb 129 .withCode(c0 -> { 130 ConstantPoolBuilder cpb = cb.constantPool(); 131 Utf8Entry slotName = cpb.utf8Entry("this"); 132 Utf8Entry desc = cpb.utf8Entry("LMyClass;"); 133 Utf8Entry i1n = cpb.utf8Entry("res"); 134 Utf8Entry i2 = cpb.utf8Entry("i"); 135 Utf8Entry intSig = cpb.utf8Entry("I"); 136 Label start = c0.newLabel(); 137 Label end = c0.newLabel(); 138 Label i1 = c0.newLabel(); 139 Label preEnd = c0.newLabel(); 140 Label loopTop = c0.newLabel(); 141 Label loopEnd = c0.newLabel(); 142 c0.localVariable(1, i1n, intSig, i1, preEnd) // LV Entries can be added before the labels 143 .localVariable(2, i2, intSig, loopTop, preEnd) 144 .labelBinding(start) 145 .iconst_1() // 0 146 .istore(1) // 1 147 .labelBinding(i1) 148 .iconst_1() // 2 149 .istore(2) // 3 150 .labelBinding(loopTop) 151 .iload(2) // 4 152 .bipush(10) // 5 153 .if_icmpge(loopEnd) // 6 154 .iload(1) // 7 155 .iload(2) // 8 156 .imul() // 9 157 .istore(1) // 10 158 .iinc(2, 1) // 11 159 .goto_(loopTop) // 12 160 .labelBinding(loopEnd) 161 .getstatic(CD_System, "out", CD_PrintStream) // 13 162 .iload(1) 163 .invokevirtual(CD_PrintStream, "println", MTD_INT_VOID) // 15 164 .labelBinding(preEnd) 165 .return_() 166 .labelBinding(end) 167 .localVariable(0, slotName, desc, start, end); // and lv entries can be added after the labels 168 })); 169 }); 170 171 var c = cc.parse(bytes); 172 var main = c.methods().get(1); 173 var lvt = main.code().get().findAttribute(Attributes.localVariableTable()).get(); 174 var lvs = lvt.localVariables(); 175 176 assertEquals(lvs.size(), 3); 177 List<ExpectedLvRecord> expected = List.of( 178 ExpectedLvRecord.of(1, "res", "I", 2, 25), 179 ExpectedLvRecord.of(2, "i", "I", 4, 23), 180 ExpectedLvRecord.of(0, "this", "LMyClass;", 0, 28)); 181 182 // Exploits non-symmetric "equals" in ExpectedLvRecord 183 assertTrue(expected.equals(lvs)); 184 } 185 186 @Test 187 void getLVTTEntries() { 188 ClassModel c = ClassFile.of().parse(fileBytes); 189 CodeModel co = c.methods().stream() 190 .filter(mm -> mm.methodName().stringValue().equals("n")) 191 .map(MethodModel::code) 192 .findFirst() 193 .get() 194 .orElseThrow(); 195 196 List<LocalVariableType> lvts = new ArrayList<>(); 197 co.forEach(e -> { 198 if (e instanceof LocalVariableType l) lvts.add(l); 199 }); 200 201 /* From javap: 202 203 LocalVariableTypeTable: 204 Start Length Slot Name Signature 205 51 8 6 f Ljava/util/List<*>; 206 0 64 1 u TU; 207 0 64 2 z Ljava/lang/Class<+Ljava/util/List<*>;>; 208 8 56 3 v Ljava/util/ArrayList<Ljava/lang/Integer;>; 209 17 47 4 s Ljava/util/Set<-Ljava/util/Set;>; 210 */ 211 212 List<ExpectedLvtRecord> expected = List.of( 213 ExpectedLvtRecord.of(6, "f", "Ljava/util/List<*>;", 51, 8), 214 ExpectedLvtRecord.of(1, "u", "TU;", 0, 64), 215 ExpectedLvtRecord.of(2, "z", "Ljava/lang/Class<+Ljava/util/List<*>;>;", 0, 64), 216 ExpectedLvtRecord.of(3, "v", "Ljava/util/ArrayList<Ljava/lang/Integer;>;", 8, 56), 217 ExpectedLvtRecord.of(4, "s", "Ljava/util/Set<-Ljava/util/Set<*>;>;", 17, 47) 218 ); 219 220 // Exploits non-symmetric "equals" in ExpectedLvRecord 221 for (int i = 0; i < lvts.size(); i++) { 222 assertTrue(expected.get(i).equals(lvts.get(i))); 223 } 224 } 225 226 @Test 227 void testCreateLoadLVTT() throws Exception { 228 var cc = ClassFile.of(); 229 byte[] bytes = cc.build(ClassDesc.of("MyClass"), cb -> { 230 cb.withFlags(AccessFlag.PUBLIC); 231 cb.withVersion(52, 0); 232 cb.with(SourceFileAttribute.of(cb.constantPool().utf8Entry(("MyClass.java")))) 233 234 .withMethod("<init>", MethodTypeDesc.of(CD_void), 0, mb -> mb 235 .withCode(codeb -> codeb.aload(0) 236 .invokespecial(CD_Object, "<init>", MTD_VOID, false) 237 .return_() 238 ) 239 ) 240 241 .withMethod("m", MethodTypeDesc.of(CD_Object, CD_Object.arrayType()), 242 ACC_PUBLIC, 243 mb -> mb.withFlags(AccessFlag.PUBLIC) 244 .withCode(c0 -> { 245 ConstantPoolBuilder cpb = cb.constantPool(); 246 Utf8Entry slotName = cpb.utf8Entry("this"); 247 Utf8Entry desc = cpb.utf8Entry("LMyClass;"); 248 Utf8Entry juList = cpb.utf8Entry("Ljava/util/List;"); 249 Utf8Entry TU = cpb.utf8Entry("TU;"); 250 Utf8Entry sig = cpb.utf8Entry("Ljava/util/List<+Ljava/lang/Object;>;"); 251 Utf8Entry l = cpb.utf8Entry("l"); 252 Utf8Entry jlObject = cpb.utf8Entry("Ljava/lang/Object;"); 253 Utf8Entry u = cpb.utf8Entry("u"); 254 255 Label start = c0.newLabel(); 256 Label end = c0.newLabel(); 257 Label beforeRet = c0.newLabel(); 258 259 c0.localVariable(2, l, juList, beforeRet, end) 260 .localVariableType(1, u, TU, start, end) 261 .labelBinding(start) 262 .new_(ClassDesc.of("java.util.ArrayList")) 263 .dup() 264 .invokespecial(CD_ArrayList, "<init>", MTD_VOID, false) 265 .astore(2) 266 .labelBinding(beforeRet) 267 .localVariableType(2, l, sig, beforeRet, end) 268 .aload(1) 269 .areturn() 270 .labelBinding(end) 271 .localVariable(0, slotName, desc, start, end) 272 .localVariable(1, u, jlObject, start, end); 273 })); 274 }); 275 var c = cc.parse(bytes); 276 var main = c.methods().get(1); 277 var lvtt = main.code().get().findAttribute(Attributes.localVariableTypeTable()).get(); 278 var lvts = lvtt.localVariableTypes(); 279 280 /* From javap: 281 282 LocalVariableTypeTable: 283 Start Length Slot Name Signature 284 0 10 1 u TU; 285 8 2 2 l Ljava/util/List<+Ljava/lang/Object;>; 286 */ 287 288 List<ExpectedLvtRecord> expected = List.of( 289 ExpectedLvtRecord.of(1, "u", "TU;", 0, 10), 290 ExpectedLvtRecord.of(2, "l", "Ljava/util/List<+Ljava/lang/Object;>;", 8, 2) 291 ); 292 293 // Exploits non-symmetric "equals" in ExpectedLvRecord 294 for (int i = 0; i < lvts.size(); i++) { 295 assertTrue(expected.get(i).equals(lvts.get(i))); 296 } 297 } 298 299 @Test 300 void skipDebugSkipsLVT() { 301 ClassModel c = ClassFile.of(ClassFile.DebugElementsOption.DROP_DEBUG).parse(fileBytes); 302 303 c.forEach(e -> { 304 if (e instanceof MethodModel m) { 305 m.forEach(el -> { 306 if (el instanceof CodeModel cm) { 307 cm.forEach(elem -> { 308 assertFalse(elem instanceof LocalVariable); 309 assertFalse(elem instanceof LocalVariableType); 310 }); 311 } 312 }); 313 } 314 }); 315 } 316 }