• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.binder.bytecode;
18 
19 import static com.google.common.collect.Iterables.getLast;
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 com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
24 import static java.util.Objects.requireNonNull;
25 
26 import com.google.common.collect.Iterables;
27 import com.google.common.io.ByteStreams;
28 import com.google.turbine.binder.bound.TurbineClassValue;
29 import com.google.turbine.binder.bound.TypeBoundClass;
30 import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
31 import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
32 import com.google.turbine.binder.env.CompoundEnv;
33 import com.google.turbine.binder.env.Env;
34 import com.google.turbine.binder.sym.ClassSymbol;
35 import com.google.turbine.model.TurbineFlag;
36 import com.google.turbine.type.Type;
37 import com.google.turbine.type.Type.ClassTy;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.Serializable;
41 import java.io.UncheckedIOException;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import org.jspecify.nullness.Nullable;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.junit.runners.JUnit4;
49 
50 @RunWith(JUnit4.class)
51 public class BytecodeBoundClassTest {
52 
53   static class NoInterfaces {}
54 
55   abstract static class RawInterfaces implements Serializable {}
56 
57   abstract static class GenericInterfaces implements List<String> {}
58 
59   @Test
interfaceTypes()60   public void interfaceTypes() {
61     TypeBoundClass noInterfaces = getBytecodeBoundClass(NoInterfaces.class);
62     TypeBoundClass rawInterfaces = getBytecodeBoundClass(RawInterfaces.class);
63     TypeBoundClass genericInterfaces = getBytecodeBoundClass(GenericInterfaces.class);
64 
65     assertThat(noInterfaces.interfaceTypes()).isEmpty();
66 
67     assertThat(rawInterfaces.interfaceTypes()).hasSize(1);
68     assertThat(((ClassTy) rawInterfaces.interfaceTypes().get(0)).sym())
69         .isEqualTo(new ClassSymbol("java/io/Serializable"));
70     assertThat(getLast(((ClassTy) rawInterfaces.interfaceTypes().get(0)).classes()).targs())
71         .isEmpty();
72 
73     assertThat(genericInterfaces.interfaceTypes()).hasSize(1);
74     assertThat(((ClassTy) genericInterfaces.interfaceTypes().get(0)).sym())
75         .isEqualTo(new ClassSymbol("java/util/List"));
76     assertThat(getLast(((ClassTy) genericInterfaces.interfaceTypes().get(0)).classes()).targs())
77         .hasSize(1);
78     assertThat(
79             ((ClassTy)
80                     getLast(((ClassTy) genericInterfaces.interfaceTypes().get(0)).classes())
81                         .targs()
82                         .get(0))
83                 .sym())
84         .isEqualTo(new ClassSymbol("java/lang/String"));
85   }
86 
87   @SuppressWarnings({"deprecation", "TypeNameShadowing", "InlineMeSuggester"})
88   static class HasMethod {
89     @Deprecated
foo(@eprecated X bar, Y baz)90     <X, Y extends X, Z extends Throwable> @Nullable X foo(@Deprecated X bar, Y baz)
91         throws IOException, Z {
92       return null;
93     }
94 
baz()95     void baz() throws IOException {
96       throw new IOException();
97     }
98   }
99 
100   @Test
methodTypes()101   public void methodTypes() {
102     MethodInfo m =
103         getBytecodeBoundClass(HasMethod.class).methods().stream()
104             .filter(x -> x.name().equals("foo"))
105             .collect(onlyElement());
106 
107     assertThat(m.tyParams()).hasSize(3);
108     assertThat(m.parameters().get(0).annotations()).hasSize(1);
109     assertThat(m.parameters().get(0).name()).isEqualTo("bar");
110     assertThat(m.exceptions()).hasSize(2);
111 
112     MethodInfo b =
113         getBytecodeBoundClass(HasMethod.class).methods().stream()
114             .filter(x -> x.name().equals("baz"))
115             .collect(onlyElement());
116     assertThat(b.exceptions()).hasSize(1);
117   }
118 
119   @interface VoidAnno {
a()120     Class<?> a() default void.class;
121 
b()122     Class<?> b() default int[].class;
123   }
124 
125   @Test
voidAnno()126   public void voidAnno() {
127     BytecodeBoundClass c = getBytecodeBoundClass(VoidAnno.class);
128 
129     assertThat(c.methods()).hasSize(2);
130     assertThat(((TurbineClassValue) c.methods().get(0).defaultValue()).type().tyKind())
131         .isEqualTo(Type.TyKind.VOID_TY);
132     assertThat(((TurbineClassValue) c.methods().get(1).defaultValue()).type().tyKind())
133         .isEqualTo(Type.TyKind.ARRAY_TY);
134   }
135 
136   static class HasField {
137     @Deprecated List<String> foo;
138   }
139 
140   @Test
fieldTypes()141   public void fieldTypes() {
142     FieldInfo f =
143         getBytecodeBoundClass(HasField.class).fields().stream()
144             .filter(x -> x.name().equals("foo"))
145             .collect(onlyElement());
146 
147     assertThat(Iterables.getLast(((ClassTy) f.type()).classes()).targs()).hasSize(1);
148     assertThat(f.annotations()).hasSize(1);
149   }
150 
151   interface Y {
f()152     Object f();
153   }
154 
155   interface X extends Y {
f()156     String f();
157   }
158 
159   @Test
covariantBridges()160   public void covariantBridges() {
161     assertThat(getBytecodeBoundClass(X.class, Y.class).methods()).hasSize(1);
162   }
163 
164   interface A<T> {
f(T t)165     void f(T t);
166   }
167 
168   interface B<T extends Number> extends A<T> {
169     @Override
f(T t)170     void f(T t);
171   }
172 
173   interface C<T extends Integer> extends B<T> {
174     @Override
f(T t)175     void f(T t);
176   }
177 
178   @Test
genericBridges()179   public void genericBridges() {
180     assertThat(getBytecodeBoundClass(C.class, B.class, A.class).methods()).hasSize(1);
181   }
182 
183   interface D {
f()184     default void f() {}
185   }
186 
187   @Test
defaultMethods()188   public void defaultMethods() {
189     assertThat(
190             (getOnlyElement(getBytecodeBoundClass(D.class).methods()).access()
191                     & TurbineFlag.ACC_DEFAULT)
192                 == TurbineFlag.ACC_DEFAULT)
193         .isTrue();
194   }
195 
toByteArrayOrDie(InputStream is)196   private static byte[] toByteArrayOrDie(InputStream is) {
197     try {
198       return ByteStreams.toByteArray(is);
199     } catch (IOException e) {
200       throw new UncheckedIOException(e);
201     }
202   }
203 
getBytecodeBoundClass( Env<ClassSymbol, BytecodeBoundClass> env, Class<?> clazz)204   private BytecodeBoundClass getBytecodeBoundClass(
205       Env<ClassSymbol, BytecodeBoundClass> env, Class<?> clazz) {
206     String name = clazz.getName().replace('.', '/');
207     String path = "/" + name + ".class";
208     return new BytecodeBoundClass(
209         new ClassSymbol(name),
210         () -> toByteArrayOrDie(requireNonNull(getClass().getResourceAsStream(path), path)),
211         env,
212         "test.jar");
213   }
214 
getBytecodeBoundClass(Class<?> clazz, Class<?>... classpath)215   private BytecodeBoundClass getBytecodeBoundClass(Class<?> clazz, Class<?>... classpath) {
216     Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>();
217     Env<ClassSymbol, BytecodeBoundClass> env =
218         CompoundEnv.of(TURBINE_BOOTCLASSPATH.env())
219             .append(
220                 new Env<ClassSymbol, BytecodeBoundClass>() {
221                   @Override
222                   public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
223                     return map.get(sym);
224                   }
225                 });
226     addClass(clazz, map, env);
227     addClass(BytecodeBoundClassTest.class, map, env);
228     for (Class<?> c : classpath) {
229       addClass(c, map, env);
230     }
231     return getBytecodeBoundClass(env, clazz);
232   }
233 
addClass( Class<?> clazz, Map<ClassSymbol, BytecodeBoundClass> map, Env<ClassSymbol, BytecodeBoundClass> env)234   private void addClass(
235       Class<?> clazz,
236       Map<ClassSymbol, BytecodeBoundClass> map,
237       Env<ClassSymbol, BytecodeBoundClass> env) {
238     map.put(new ClassSymbol(clazz.getName().replace('.', '/')), getBytecodeBoundClass(env, clazz));
239   }
240 }
241