• 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.binder.bytecode;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableListMultimap;
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.collect.Iterables;
25 import com.google.turbine.binder.bound.EnumConstantValue;
26 import com.google.turbine.binder.bound.ModuleInfo;
27 import com.google.turbine.binder.bound.TurbineAnnotationValue;
28 import com.google.turbine.binder.bound.TurbineClassValue;
29 import com.google.turbine.binder.sym.ClassSymbol;
30 import com.google.turbine.binder.sym.FieldSymbol;
31 import com.google.turbine.binder.sym.TyVarSymbol;
32 import com.google.turbine.bytecode.ClassFile;
33 import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
34 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue;
35 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ArrayValue;
36 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineClassValue;
37 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstValue;
38 import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue;
39 import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
40 import com.google.turbine.bytecode.ClassReader;
41 import com.google.turbine.bytecode.sig.Sig;
42 import com.google.turbine.bytecode.sig.Sig.LowerBoundTySig;
43 import com.google.turbine.bytecode.sig.Sig.UpperBoundTySig;
44 import com.google.turbine.bytecode.sig.Sig.WildTySig;
45 import com.google.turbine.bytecode.sig.SigParser;
46 import com.google.turbine.model.Const;
47 import com.google.turbine.model.Const.ArrayInitValue;
48 import com.google.turbine.model.Const.Value;
49 import com.google.turbine.type.AnnoInfo;
50 import com.google.turbine.type.Type;
51 import java.util.ArrayDeque;
52 import java.util.ArrayList;
53 import java.util.LinkedHashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.function.Supplier;
57 import org.jspecify.annotations.Nullable;
58 
59 /** Bind {@link Type}s from bytecode. */
60 public final class BytecodeBinder {
61 
62   /** Context that is required to create types from type signatures in bytecode. */
63   interface Scope {
64     /** Look up a type variable by name on an enclosing method or class. */
apply(String input)65     TyVarSymbol apply(String input);
66 
67     /**
68      * Returns the enclosing class for a nested class, or {@code null}.
69      *
70      * <p>Locating type annotations on nested classes requires knowledge of their enclosing types.
71      */
outer(ClassSymbol sym)72     @Nullable ClassSymbol outer(ClassSymbol sym);
73   }
74 
bindClassTy( Sig.ClassTySig sig, Scope scope, ImmutableList<TypeAnnotationInfo> annotations)75   public static Type.ClassTy bindClassTy(
76       Sig.ClassTySig sig, Scope scope, ImmutableList<TypeAnnotationInfo> annotations) {
77     return bindClassTy(
78         sig, scope, typeAnnotationsByPath(annotations, scope), TypeAnnotationInfo.TypePath.root());
79   }
80 
bindClassTy( Sig.ClassTySig sig, Scope scope, ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations, TypeAnnotationInfo.TypePath typePath)81   private static Type.ClassTy bindClassTy(
82       Sig.ClassTySig sig,
83       Scope scope,
84       ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations,
85       TypeAnnotationInfo.TypePath typePath) {
86     StringBuilder sb = new StringBuilder();
87     if (!sig.pkg().isEmpty()) {
88       sb.append(sig.pkg()).append('/');
89     }
90     boolean first = true;
91     Map<ClassSymbol, Sig.SimpleClassTySig> syms = new LinkedHashMap<>();
92     for (Sig.SimpleClassTySig s : sig.classes()) {
93       if (!first) {
94         sb.append('$');
95       }
96       sb.append(s.simpleName());
97       ClassSymbol sym = new ClassSymbol(sb.toString());
98       syms.put(sym, s);
99       first = false;
100     }
101     ArrayDeque<ClassSymbol> outers = new ArrayDeque<>();
102     for (ClassSymbol curr = Iterables.getLast(syms.keySet());
103         curr != null;
104         curr = scope.outer(curr)) {
105       outers.addFirst(curr);
106     }
107     List<Type.ClassTy.SimpleClassTy> classes = new ArrayList<>();
108     for (ClassSymbol curr : outers) {
109       ImmutableList.Builder<Type> tyArgs = ImmutableList.builder();
110       Sig.SimpleClassTySig s = syms.get(curr);
111       if (s != null) {
112         for (int i = 0; i < s.tyArgs().size(); i++) {
113           tyArgs.add(bindTy(s.tyArgs().get(i), scope, annotations, typePath.typeArgument(i)));
114         }
115       }
116       classes.add(
117           Type.ClassTy.SimpleClassTy.create(curr, tyArgs.build(), annotations.get(typePath)));
118       typePath = typePath.nested();
119     }
120     return Type.ClassTy.create(classes);
121   }
122 
wildTy( WildTySig sig, Scope scope, ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations, TypeAnnotationInfo.TypePath typePath)123   private static Type wildTy(
124       WildTySig sig,
125       Scope scope,
126       ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations,
127       TypeAnnotationInfo.TypePath typePath) {
128     switch (sig.boundKind()) {
129       case NONE:
130         return Type.WildUnboundedTy.create(annotations.get(typePath));
131       case LOWER:
132         return Type.WildLowerBoundedTy.create(
133             bindTy(((LowerBoundTySig) sig).bound(), scope, annotations, typePath.wild()),
134             annotations.get(typePath));
135       case UPPER:
136         return Type.WildUpperBoundedTy.create(
137             bindTy(((UpperBoundTySig) sig).bound(), scope, annotations, typePath.wild()),
138             annotations.get(typePath));
139     }
140     throw new AssertionError(sig.boundKind());
141   }
142 
bindTy( Sig.TySig sig, Scope scope, ImmutableList<TypeAnnotationInfo> annotations)143   public static Type bindTy(
144       Sig.TySig sig, Scope scope, ImmutableList<TypeAnnotationInfo> annotations) {
145     return bindTy(
146         sig, scope, typeAnnotationsByPath(annotations, scope), TypeAnnotationInfo.TypePath.root());
147   }
148 
bindTy( Sig.TySig sig, Scope scope, ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations, TypeAnnotationInfo.TypePath typePath)149   static Type bindTy(
150       Sig.TySig sig,
151       Scope scope,
152       ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations,
153       TypeAnnotationInfo.TypePath typePath) {
154     switch (sig.kind()) {
155       case BASE_TY_SIG:
156         return Type.PrimTy.create(((Sig.BaseTySig) sig).type(), annotations.get(typePath));
157       case CLASS_TY_SIG:
158         return bindClassTy((Sig.ClassTySig) sig, scope, annotations, typePath);
159       case TY_VAR_SIG:
160         return Type.TyVar.create(
161             scope.apply(((Sig.TyVarSig) sig).name()), annotations.get(typePath));
162       case ARRAY_TY_SIG:
163         return bindArrayTy((Sig.ArrayTySig) sig, scope, annotations, typePath);
164       case WILD_TY_SIG:
165         return wildTy((WildTySig) sig, scope, annotations, typePath);
166       case VOID_TY_SIG:
167         return Type.VOID;
168     }
169     throw new AssertionError(sig.kind());
170   }
171 
bindArrayTy( Sig.ArrayTySig arrayTySig, Scope scope, ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations, TypeAnnotationInfo.TypePath typePath)172   private static Type bindArrayTy(
173       Sig.ArrayTySig arrayTySig,
174       Scope scope,
175       ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations,
176       TypeAnnotationInfo.TypePath typePath) {
177     return Type.ArrayTy.create(
178         bindTy(arrayTySig.elementType(), scope, annotations, typePath.array()),
179         annotations.get(typePath));
180   }
181 
typeAnnotationsByPath( ImmutableList<TypeAnnotationInfo> typeAnnotations, Scope scope)182   private static ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> typeAnnotationsByPath(
183       ImmutableList<TypeAnnotationInfo> typeAnnotations, Scope scope) {
184     if (typeAnnotations.isEmpty()) {
185       return ImmutableListMultimap.of();
186     }
187     ImmutableListMultimap.Builder<TypeAnnotationInfo.TypePath, AnnoInfo> result =
188         ImmutableListMultimap.builder();
189     for (TypeAnnotationInfo typeAnnotation : typeAnnotations) {
190       result.put(typeAnnotation.path(), bindAnnotationValue(typeAnnotation.anno(), scope).info());
191     }
192     return result.build();
193   }
194 
195   /**
196    * Similar to {@link Type.ClassTy#asNonParametricClassTy}, but handles any provided type
197    * annotations and attaches them to the corresponding {@link Type.ClassTy.SimpleClassTy}.
198    */
asNonParametricClassTy( ClassSymbol sym, ImmutableList<TypeAnnotationInfo> annotations, Scope scope)199   public static Type.ClassTy asNonParametricClassTy(
200       ClassSymbol sym, ImmutableList<TypeAnnotationInfo> annotations, Scope scope) {
201     return asNonParametricClassTy(sym, scope, typeAnnotationsByPath(annotations, scope));
202   }
203 
asNonParametricClassTy( ClassSymbol sym, Scope scope, ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations)204   private static Type.ClassTy asNonParametricClassTy(
205       ClassSymbol sym,
206       Scope scope,
207       ImmutableListMultimap<TypeAnnotationInfo.TypePath, AnnoInfo> annotations) {
208     if (annotations.isEmpty()) {
209       // fast path if there are no type annotations
210       return Type.ClassTy.asNonParametricClassTy(sym);
211     }
212     ArrayDeque<ClassSymbol> outers = new ArrayDeque<>();
213     for (ClassSymbol curr = sym; curr != null; curr = scope.outer(curr)) {
214       outers.addFirst(curr);
215     }
216     List<Type.ClassTy.SimpleClassTy> classes = new ArrayList<>();
217     TypeAnnotationInfo.TypePath typePath = TypeAnnotationInfo.TypePath.root();
218     for (ClassSymbol curr : outers) {
219       classes.add(
220           Type.ClassTy.SimpleClassTy.create(curr, ImmutableList.of(), annotations.get(typePath)));
221       typePath = typePath.nested();
222     }
223     return Type.ClassTy.create(classes);
224   }
225 
bindValue(ElementValue value, Scope scope)226   public static Const bindValue(ElementValue value, Scope scope) {
227     switch (value.kind()) {
228       case ENUM:
229         return bindEnumValue((EnumConstValue) value);
230       case CONST:
231         return ((ConstValue) value).value();
232       case ARRAY:
233         return bindArrayValue((ArrayValue) value, scope);
234       case CLASS:
235         return new TurbineClassValue(
236             bindTy(
237                 new SigParser(((ConstTurbineClassValue) value).className()).parseType(),
238                 new Scope() {
239                   @Override
240                   public TyVarSymbol apply(String x) {
241                     throw new IllegalStateException(x);
242                   }
243 
244                   @Override
245                   public @Nullable ClassSymbol outer(ClassSymbol sym) {
246                     return scope.outer(sym);
247                   }
248                 },
249                 /* annotations= */ ImmutableList.of()));
250       case ANNOTATION:
251         return bindAnnotationValue(
252             ((ElementValue.ConstTurbineAnnotationValue) value).annotation(), scope);
253     }
254     throw new AssertionError(value.kind());
255   }
256 
257   static TurbineAnnotationValue bindAnnotationValue(AnnotationInfo value, Scope scope) {
258     ClassSymbol sym = asClassSymbol(value.typeName());
259     ImmutableMap.Builder<String, Const> values = ImmutableMap.builder();
260     for (Map.Entry<String, ElementValue> e : value.elementValuePairs().entrySet()) {
261       values.put(e.getKey(), bindValue(e.getValue(), scope));
262     }
263     return new TurbineAnnotationValue(new AnnoInfo(null, sym, null, values.buildOrThrow()));
264   }
265 
266   static ImmutableList<AnnoInfo> bindAnnotations(List<AnnotationInfo> input, Scope scope) {
267     ImmutableList.Builder<AnnoInfo> result = ImmutableList.builder();
268     for (AnnotationInfo annotation : input) {
269       TurbineAnnotationValue anno = bindAnnotationValue(annotation, scope);
270       if (!shouldSkip(anno)) {
271         result.add(anno.info());
272       }
273     }
274     return result.build();
275   }
276 
277   private static boolean shouldSkip(TurbineAnnotationValue anno) {
278     // ct.sym contains fake annotations without corresponding class files.
279     return anno.sym().equals(ClassSymbol.PROFILE_ANNOTATION)
280         || anno.sym().equals(ClassSymbol.PROPRIETARY_ANNOTATION);
281   }
282 
283   private static ClassSymbol asClassSymbol(String s) {
284     return new ClassSymbol(s.substring(1, s.length() - 1));
285   }
286 
287   private static Const bindArrayValue(ArrayValue value, Scope scope) {
288     ImmutableList.Builder<Const> elements = ImmutableList.builder();
289     for (ElementValue element : value.elements()) {
290       elements.add(bindValue(element, scope));
291     }
292     return new ArrayInitValue(elements.build());
293   }
294 
295   public static Const.Value bindConstValue(Type type, Const.Value value) {
296     if (type.tyKind() != Type.TyKind.PRIM_TY) {
297       return value;
298     }
299     // Deficient numeric types and booleans are all stored as ints in the class file,
300     // coerce them to the target type.
301     switch (((Type.PrimTy) type).primkind()) {
302       case CHAR:
303         return new Const.CharValue((char) asInt(value));
304       case SHORT:
305         return new Const.ShortValue((short) asInt(value));
306       case BOOLEAN:
307         // boolean constants are encoded as integers, see also JDK-8171132
308         return new Const.BooleanValue(asInt(value) != 0);
309       case BYTE:
310         return new Const.ByteValue((byte) asInt(value));
311       default:
312         return value;
313     }
314   }
315 
316   private static int asInt(Value value) {
317     return ((Const.IntValue) value).value();
318   }
319 
320   private static Const bindEnumValue(EnumConstValue value) {
321     return new EnumConstantValue(
322         new FieldSymbol(asClassSymbol(value.typeName()), value.constName()));
323   }
324 
325   /**
326    * Returns a {@link ModuleInfo} given a module-info class file. Currently only the module's name,
327    * version, and flags are populated, since the directives are not needed by turbine at compile
328    * time.
329    */
330   public static ModuleInfo bindModuleInfo(String path, Supplier<byte[]> bytes) {
331     ClassFile classFile = ClassReader.read(path, bytes.get());
332     ClassFile.ModuleInfo module = classFile.module();
333     requireNonNull(module, path);
334     return new ModuleInfo(
335         module.name(),
336         module.version(),
337         module.flags(),
338         /* annos= */ ImmutableList.of(),
339         /* requires= */ ImmutableList.of(),
340         /* exports= */ ImmutableList.of(),
341         /* opens= */ ImmutableList.of(),
342         /* uses= */ ImmutableList.of(),
343         /* provides= */ ImmutableList.of());
344   }
345 
346   private BytecodeBinder() {}
347 }
348