• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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