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