1 /* 2 * Copyright 2016 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.turbine.bytecode; 18 19 import static com.google.common.collect.ImmutableList.toImmutableList; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static com.google.common.collect.MoreCollectors.onlyElement; 22 import static com.google.common.truth.Truth.assertThat; 23 import static java.lang.annotation.ElementType.TYPE_USE; 24 import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 import static java.util.Objects.requireNonNull; 26 27 import com.google.common.base.Strings; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.Iterables; 30 import com.google.common.io.ByteStreams; 31 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue; 32 import com.google.turbine.bytecode.ClassFile.ModuleInfo; 33 import com.google.turbine.bytecode.ClassFile.ModuleInfo.ExportInfo; 34 import com.google.turbine.bytecode.ClassFile.ModuleInfo.OpenInfo; 35 import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo; 36 import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo; 37 import com.google.turbine.model.Const; 38 import com.google.turbine.model.TurbineConstantTypeKind; 39 import com.google.turbine.model.TurbineFlag; 40 import java.io.IOException; 41 import java.io.UncheckedIOException; 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.Target; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.function.Supplier; 47 import java.util.jar.JarFile; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.JUnit4; 51 import org.objectweb.asm.AnnotationVisitor; 52 import org.objectweb.asm.Attribute; 53 import org.objectweb.asm.ByteVector; 54 import org.objectweb.asm.ClassWriter; 55 import org.objectweb.asm.FieldVisitor; 56 import org.objectweb.asm.Handle; 57 import org.objectweb.asm.MethodVisitor; 58 import org.objectweb.asm.ModuleVisitor; 59 import org.objectweb.asm.Opcodes; 60 61 @RunWith(JUnit4.class) 62 public class ClassReaderTest { 63 64 @Test methods()65 public void methods() { 66 ClassWriter cw = new ClassWriter(0); 67 cw.visitAnnotation("Ljava/lang/Deprecated;", true); 68 cw.visit( 69 52, 70 Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, 71 "test/Hello", 72 null, 73 "java/lang/Object", 74 null); 75 MethodVisitor mv = 76 cw.visitMethod( 77 Opcodes.ACC_PUBLIC, 78 "f", 79 "(Ljava/lang/String;)Ljava/lang/String;", 80 "<T:Ljava/lang/String;>(TT;)TT;", 81 null); 82 mv.visitParameter(null, 0); // skip synthetic parameters 83 mv.visitParameter("<no name>", Opcodes.ACC_SYNTHETIC); // skip synthetic parameters 84 mv.visitParameter("parameterName", 42); 85 cw.visitMethod( 86 Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, 87 "g", 88 "(Z)V", 89 "<T:Ljava/lang/Error;>(Z)V^TT;", 90 new String[] {"java/lang/Error"}); 91 cw.visitMethod(0, "h", "(I)V", null, null); 92 byte[] bytes = cw.toByteArray(); 93 94 ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes); 95 96 assertThat(classFile.access()) 97 .isEqualTo(TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL | TurbineFlag.ACC_SUPER); 98 assertThat(classFile.name()).isEqualTo("test/Hello"); 99 assertThat(classFile.signature()).isNull(); 100 assertThat(classFile.superName()).isEqualTo("java/lang/Object"); 101 assertThat(classFile.interfaces()).isEmpty(); 102 103 assertThat(classFile.methods()).hasSize(3); 104 105 ClassFile.MethodInfo f = classFile.methods().get(0); 106 assertThat(f.access()).isEqualTo(TurbineFlag.ACC_PUBLIC); 107 assertThat(f.name()).isEqualTo("f"); 108 assertThat(f.descriptor()).isEqualTo("(Ljava/lang/String;)Ljava/lang/String;"); 109 assertThat(f.signature()).isEqualTo("<T:Ljava/lang/String;>(TT;)TT;"); 110 assertThat(f.exceptions()).isEmpty(); 111 assertThat(f.annotations()).isEmpty(); 112 assertThat(f.parameterAnnotations()).isEmpty(); 113 assertThat(f.defaultValue()).isNull(); 114 assertThat(f.parameters()).hasSize(1); 115 assertThat(f.parameters().get(0).name()).isEqualTo("parameterName"); 116 assertThat(f.parameters().get(0).access()).isEqualTo(42); 117 118 ClassFile.MethodInfo g = classFile.methods().get(1); 119 assertThat(g.access()).isEqualTo(TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_STATIC); 120 assertThat(g.name()).isEqualTo("g"); 121 assertThat(g.descriptor()).isEqualTo("(Z)V"); 122 assertThat(g.signature()).isEqualTo("<T:Ljava/lang/Error;>(Z)V^TT;"); 123 124 ClassFile.MethodInfo h = classFile.methods().get(2); 125 assertThat(h.access()).isEqualTo(0); 126 assertThat(h.name()).isEqualTo("h"); 127 assertThat(h.descriptor()).isEqualTo("(I)V"); 128 assertThat(h.signature()).isNull(); 129 } 130 131 @Test annotationDeclaration()132 public void annotationDeclaration() { 133 ClassWriter cw = new ClassWriter(0); 134 cw.visit( 135 52, 136 Opcodes.ACC_PUBLIC + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE, 137 "test/Hello", 138 null, 139 "java/lang/Object", 140 new String[] {"java/lang/annotation/Annotation"}); 141 AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true); 142 av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", "RUNTIME"); 143 av.visitEnd(); 144 cw.visitEnd(); 145 byte[] bytes = cw.toByteArray(); 146 147 ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes); 148 149 assertThat(classFile.access()) 150 .isEqualTo( 151 TurbineFlag.ACC_PUBLIC 152 | TurbineFlag.ACC_ANNOTATION 153 | TurbineFlag.ACC_ABSTRACT 154 | TurbineFlag.ACC_INTERFACE); 155 assertThat(classFile.name()).isEqualTo("test/Hello"); 156 assertThat(classFile.signature()).isNull(); 157 assertThat(classFile.superName()).isEqualTo("java/lang/Object"); 158 assertThat(classFile.interfaces()).containsExactly("java/lang/annotation/Annotation"); 159 160 assertThat(classFile.annotations()).hasSize(1); 161 ClassFile.AnnotationInfo annotation = Iterables.getOnlyElement(classFile.annotations()); 162 assertThat(annotation.typeName()).isEqualTo("Ljava/lang/annotation/Retention;"); 163 assertThat(annotation.elementValuePairs()).hasSize(1); 164 assertThat(annotation.elementValuePairs()).containsKey("value"); 165 ElementValue value = requireNonNull(annotation.elementValuePairs().get("value")); 166 assertThat(value.kind()).isEqualTo(ElementValue.Kind.ENUM); 167 ElementValue.EnumConstValue enumValue = (ElementValue.EnumConstValue) value; 168 assertThat(enumValue.typeName()).isEqualTo("Ljava/lang/annotation/RetentionPolicy;"); 169 assertThat(enumValue.constName()).isEqualTo("RUNTIME"); 170 } 171 172 @Test fields()173 public void fields() { 174 ClassWriter cw = new ClassWriter(0); 175 cw.visit( 176 52, 177 Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, 178 "test/Hello", 179 "<X:Ljava/lang/Object;>Ljava/lang/Object;", 180 "java/lang/Object", 181 null); 182 FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, "x", "I", null, null); 183 fv.visitAnnotation("Ljava/lang/Deprecated;", true); 184 cw.visitField( 185 Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, 186 "y", 187 "I", 188 null, 189 Integer.valueOf(42)); 190 cw.visitField(Opcodes.ACC_PUBLIC, "z", "Ljava/util/List;", "Ljava/util/List<TX;>;", null); 191 cw.visitEnd(); 192 byte[] bytes = cw.toByteArray(); 193 194 ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes); 195 196 assertThat(classFile.fields()).hasSize(3); 197 198 ClassFile.FieldInfo x = classFile.fields().get(0); 199 assertThat(x.access()).isEqualTo(TurbineFlag.ACC_PUBLIC); 200 assertThat(x.name()).isEqualTo("x"); 201 assertThat(x.descriptor()).isEqualTo("I"); 202 assertThat(x.signature()).isNull(); 203 assertThat(x.value()).isNull(); 204 assertThat(x.annotations()).hasSize(1); 205 ClassFile.AnnotationInfo annotation = Iterables.getOnlyElement(x.annotations()); 206 assertThat(annotation.typeName()).isEqualTo("Ljava/lang/Deprecated;"); 207 208 ClassFile.FieldInfo y = classFile.fields().get(1); 209 assertThat(y.access()) 210 .isEqualTo(TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_STATIC | TurbineFlag.ACC_FINAL); 211 assertThat(y.name()).isEqualTo("y"); 212 assertThat(y.descriptor()).isEqualTo("I"); 213 assertThat(y.value().constantTypeKind()).isEqualTo(TurbineConstantTypeKind.INT); 214 assertThat(((Const.IntValue) y.value()).value()).isEqualTo(42); 215 assertThat(y.annotations()).isEmpty(); 216 217 ClassFile.FieldInfo z = classFile.fields().get(2); 218 assertThat(z.name()).isEqualTo("z"); 219 assertThat(z.descriptor()).isEqualTo("Ljava/util/List;"); 220 assertThat(z.signature()).isEqualTo("Ljava/util/List<TX;>;"); 221 assertThat(z.annotations()).isEmpty(); 222 } 223 224 @Test innerClass()225 public void innerClass() { 226 ClassWriter cw = new ClassWriter(0); 227 cw.visit(52, Opcodes.ACC_SUPER, "test/Hello$Inner", null, "java/lang/Object", null); 228 cw.visitInnerClass( 229 "test/Hello$Inner", "test/Hello", "Inner", Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE); 230 cw.visitInnerClass("test/Hello$Inner$InnerMost", "test/Hello$Inner", "InnerMost", 0); 231 cw.visitInnerClass("test/Hello$Local", null, "Local", 0); 232 byte[] bytes = cw.toByteArray(); 233 234 ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes); 235 236 assertThat(classFile.innerClasses()).hasSize(2); 237 238 ClassFile.InnerClass a = classFile.innerClasses().get(0); 239 assertThat(a.access()).isEqualTo(TurbineFlag.ACC_STATIC | TurbineFlag.ACC_PRIVATE); 240 assertThat(a.innerName()).isEqualTo("Inner"); 241 assertThat(a.innerClass()).isEqualTo("test/Hello$Inner"); 242 assertThat(a.outerClass()).isEqualTo("test/Hello"); 243 244 ClassFile.InnerClass b = classFile.innerClasses().get(1); 245 assertThat(b.innerName()).isEqualTo("InnerMost"); 246 assertThat(b.innerClass()).isEqualTo("test/Hello$Inner$InnerMost"); 247 assertThat(b.outerClass()).isEqualTo("test/Hello$Inner"); 248 } 249 250 @Test largeConstant()251 public void largeConstant() { 252 String jumbo = Strings.repeat("a", Short.MAX_VALUE + 1); 253 254 ClassWriter cw = new ClassWriter(0); 255 cw.visit(52, Opcodes.ACC_SUPER, jumbo, null, "java/lang/Object", null); 256 byte[] bytes = cw.toByteArray(); 257 258 ClassFile cf = ClassReader.read(null, bytes); 259 assertThat(cf.name()).isEqualTo(jumbo); 260 } 261 262 @Test condy()263 public void condy() { 264 ClassWriter cw = new ClassWriter(0); 265 cw.visit(52, Opcodes.ACC_SUPER, "Test", null, "java/lang/Object", null); 266 cw.newConstantDynamic( 267 "f", "Ljava/lang/String;", new Handle(Opcodes.H_INVOKESTATIC, "A", "f", "()V", false)); 268 byte[] bytes = cw.toByteArray(); 269 270 ClassFile cf = ClassReader.read(null, bytes); 271 assertThat(cf.name()).isEqualTo("Test"); 272 } 273 274 @Test v53()275 public void v53() { 276 ClassWriter cw = new ClassWriter(0); 277 cw.visitAnnotation("Ljava/lang/Deprecated;", true); 278 cw.visit( 279 53, 280 Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, 281 "Hello", 282 null, 283 "java/lang/Object", 284 null); 285 ClassFile cf = ClassReader.read(null, cw.toByteArray()); 286 assertThat(cf.name()).isEqualTo("Hello"); 287 } 288 289 @Test module()290 public void module() { 291 ClassWriter cw = new ClassWriter(0); 292 293 cw.visit(53, /* access= */ 53, "module-info", null, null, null); 294 295 ModuleVisitor mv = cw.visitModule("mod", Opcodes.ACC_OPEN, "mod-ver"); 296 297 mv.visitRequire("r1", Opcodes.ACC_TRANSITIVE, "r1-ver"); 298 mv.visitRequire("r2", Opcodes.ACC_STATIC_PHASE, "r2-ver"); 299 mv.visitRequire("r3", Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE, "r3-ver"); 300 301 mv.visitExport("e1", Opcodes.ACC_SYNTHETIC, "e1m1", "e1m2", "e1m3"); 302 mv.visitExport("e2", Opcodes.ACC_MANDATED, "e2m1", "e2m2"); 303 mv.visitExport("e3", /* access= */ 0, "e3m1"); 304 305 mv.visitOpen("o1", Opcodes.ACC_SYNTHETIC, "o1m1", "o1m2", "o1m3"); 306 mv.visitOpen("o2", Opcodes.ACC_MANDATED, "o2m1", "o2m2"); 307 mv.visitOpen("o3", /* access= */ 0, "o3m1"); 308 309 mv.visitUse("u1"); 310 mv.visitUse("u2"); 311 mv.visitUse("u3"); 312 mv.visitUse("u4"); 313 314 mv.visitProvide("p1", "p1i1", "p1i2"); 315 mv.visitProvide("p2", "p2i1", "p2i2", "p2i3"); 316 317 ClassFile cf = ClassReader.read(null, cw.toByteArray()); 318 ModuleInfo module = cf.module(); 319 assertThat(module.name()).isEqualTo("mod"); 320 assertThat(module.flags()).isEqualTo(Opcodes.ACC_OPEN); 321 assertThat(module.version()).isEqualTo("mod-ver"); 322 323 assertThat(module.requires()).hasSize(3); 324 RequireInfo r1 = module.requires().get(0); 325 assertThat(r1.moduleName()).isEqualTo("r1"); 326 assertThat(r1.flags()).isEqualTo(Opcodes.ACC_TRANSITIVE); 327 assertThat(r1.version()).isEqualTo("r1-ver"); 328 RequireInfo r2 = module.requires().get(1); 329 assertThat(r2.moduleName()).isEqualTo("r2"); 330 assertThat(r2.flags()).isEqualTo(Opcodes.ACC_STATIC_PHASE); 331 assertThat(r2.version()).isEqualTo("r2-ver"); 332 RequireInfo r3 = module.requires().get(2); 333 assertThat(r3.moduleName()).isEqualTo("r3"); 334 assertThat(r3.flags()).isEqualTo(Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE); 335 assertThat(r3.version()).isEqualTo("r3-ver"); 336 337 assertThat(module.exports()).hasSize(3); 338 ExportInfo e1 = module.exports().get(0); 339 assertThat(e1.moduleName()).isEqualTo("e1"); 340 assertThat(e1.flags()).isEqualTo(Opcodes.ACC_SYNTHETIC); 341 assertThat(e1.modules()).containsExactly("e1m1", "e1m2", "e1m3").inOrder(); 342 ExportInfo e2 = module.exports().get(1); 343 assertThat(e2.moduleName()).isEqualTo("e2"); 344 assertThat(e2.flags()).isEqualTo(Opcodes.ACC_MANDATED); 345 assertThat(e2.modules()).containsExactly("e2m1", "e2m2").inOrder(); 346 ExportInfo e3 = module.exports().get(2); 347 assertThat(e3.moduleName()).isEqualTo("e3"); 348 assertThat(e3.flags()).isEqualTo(0); 349 assertThat(e3.modules()).containsExactly("e3m1").inOrder(); 350 351 assertThat(module.opens()).hasSize(3); 352 OpenInfo o1 = module.opens().get(0); 353 assertThat(o1.moduleName()).isEqualTo("o1"); 354 assertThat(o1.flags()).isEqualTo(Opcodes.ACC_SYNTHETIC); 355 assertThat(o1.modules()).containsExactly("o1m1", "o1m2", "o1m3").inOrder(); 356 OpenInfo o2 = module.opens().get(1); 357 assertThat(o2.moduleName()).isEqualTo("o2"); 358 assertThat(o2.flags()).isEqualTo(Opcodes.ACC_MANDATED); 359 assertThat(o2.modules()).containsExactly("o2m1", "o2m2").inOrder(); 360 OpenInfo o3 = module.opens().get(2); 361 assertThat(o3.moduleName()).isEqualTo("o3"); 362 assertThat(o3.flags()).isEqualTo(0); 363 assertThat(o3.modules()).containsExactly("o3m1").inOrder(); 364 365 assertThat(module.uses().stream().map(u -> u.descriptor()).collect(toImmutableList())) 366 .containsExactly("u1", "u2", "u3", "u4") 367 .inOrder(); 368 369 assertThat(module.provides()).hasSize(2); 370 ProvideInfo p1 = module.provides().get(0); 371 assertThat(p1.descriptor()).isEqualTo("p1"); 372 assertThat(p1.implDescriptors()).containsExactly("p1i1", "p1i2"); 373 ProvideInfo p2 = module.provides().get(1); 374 assertThat(p2.descriptor()).isEqualTo("p2"); 375 assertThat(p2.implDescriptors()).containsExactly("p2i1", "p2i2", "p2i3"); 376 } 377 378 @Test transitiveJar()379 public void transitiveJar() { 380 ClassWriter cw = new ClassWriter(0); 381 cw.visit( 382 52, 383 Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, 384 "Hello", 385 null, 386 "java/lang/Object", 387 null); 388 cw.visitAttribute( 389 new Attribute("TurbineTransitiveJar") { 390 @Override 391 protected ByteVector write( 392 ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) { 393 ByteVector result = new ByteVector(); 394 result.putShort(classWriter.newUTF8("path/to/transitive.jar")); 395 return result; 396 } 397 }); 398 ClassFile cf = ClassReader.read(null, cw.toByteArray()); 399 assertThat(cf.transitiveJar()).isEqualTo("path/to/transitive.jar"); 400 } 401 402 static class C { 403 @Target(TYPE_USE) 404 @Retention(RUNTIME) 405 @interface A { value()406 int value(); 407 } 408 409 @SuppressWarnings("unused") 410 @A(0x14) f(Object o)411 int f(Object o) { 412 @A(0x40) 413 int local; 414 try (@A(0x41) 415 JarFile jarFile = new JarFile("hello.jar")) { 416 } catch ( 417 @A(0x42) 418 IOException e) { 419 throw new UncheckedIOException(e); 420 } 421 if (o instanceof @A(0x43) String) {} 422 new @A(0x44) ArrayList<>(); 423 Supplier<List<?>> a = @A(0x45) ArrayList::new; 424 Supplier<List<?>> b = @A(0x46) ImmutableList::of; 425 String s = (@A(0x47) String) o; 426 List<?> xs = new ArrayList<@A(0x48) String>(); 427 xs = ImmutableList.<@A(0x49) String>of(); 428 Supplier<List<?>> c = ArrayList<@A(0x4A) String>::new; 429 Supplier<List<?>> d = ImmutableList::<@A(0x4B) String>of; 430 return 0; 431 } 432 } 433 434 // Ensure that we skip over JVMS 4.7.20-B target_types, and handle the single API type annotation 435 @Test nonApiTypeAnnotations()436 public void nonApiTypeAnnotations() throws Exception { 437 byte[] bytes = 438 ByteStreams.toByteArray( 439 getClass().getResourceAsStream("/" + C.class.getName().replace('.', '/') + ".class")); 440 ClassFile cf = ClassReader.read(null, bytes); 441 ClassFile.MethodInfo m = 442 cf.methods().stream().filter(x -> x.name().contains("f")).collect(onlyElement()); 443 ClassFile.TypeAnnotationInfo ta = getOnlyElement(m.typeAnnotations()); 444 assertThat(ta.targetType()).isEqualTo(ClassFile.TypeAnnotationInfo.TargetType.METHOD_RETURN); 445 assertThat(ta.path()).isEqualTo(ClassFile.TypeAnnotationInfo.TypePath.root()); 446 assertThat(ta.target()).isEqualTo(ClassFile.TypeAnnotationInfo.EMPTY_TARGET); 447 assertThat(ta.anno().typeName()).isEqualTo("Lcom/google/turbine/bytecode/ClassReaderTest$C$A;"); 448 assertThat( 449 ((ElementValue.ConstValue) ta.anno().elementValuePairs().get("value")) 450 .value() 451 .getValue()) 452 .isEqualTo(0x14); 453 } 454 } 455