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
17package com.google.turbine.binder;
18
19import static com.google.common.base.Verify.verifyNotNull;
20
21import com.google.common.base.Optional;
22import com.google.common.base.Splitter;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableMap;
25import com.google.common.collect.ImmutableSet;
26import com.google.turbine.binder.CompUnitPreprocessor.PreprocessedCompUnit;
27import com.google.turbine.binder.Resolve.CanonicalResolver;
28import com.google.turbine.binder.bound.BoundClass;
29import com.google.turbine.binder.bound.HeaderBoundClass;
30import com.google.turbine.binder.bound.PackageSourceBoundClass;
31import com.google.turbine.binder.bound.SourceBoundClass;
32import com.google.turbine.binder.bound.SourceHeaderBoundClass;
33import com.google.turbine.binder.bound.SourceTypeBoundClass;
34import com.google.turbine.binder.bound.TypeBoundClass;
35import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
36import com.google.turbine.binder.bytecode.BytecodeBoundClass;
37import com.google.turbine.binder.env.CompoundEnv;
38import com.google.turbine.binder.env.Env;
39import com.google.turbine.binder.env.LazyEnv;
40import com.google.turbine.binder.env.SimpleEnv;
41import com.google.turbine.binder.lookup.CanonicalSymbolResolver;
42import com.google.turbine.binder.lookup.CompoundScope;
43import com.google.turbine.binder.lookup.ImportIndex;
44import com.google.turbine.binder.lookup.ImportScope;
45import com.google.turbine.binder.lookup.MemberImportIndex;
46import com.google.turbine.binder.lookup.Scope;
47import com.google.turbine.binder.lookup.TopLevelIndex;
48import com.google.turbine.binder.lookup.WildImportIndex;
49import com.google.turbine.binder.sym.ClassSymbol;
50import com.google.turbine.binder.sym.FieldSymbol;
51import com.google.turbine.model.Const;
52import com.google.turbine.model.TurbineFlag;
53import com.google.turbine.tree.Tree;
54import com.google.turbine.tree.Tree.CompUnit;
55import com.google.turbine.type.Type;
56import java.io.IOException;
57import java.nio.file.Path;
58import java.util.Collection;
59import java.util.List;
60
61/** The entry point for analysis. */
62public class Binder {
63
64  /** Binds symbols and types to the given compilation units. */
65  public static BindingResult bind(
66      List<CompUnit> units, Collection<Path> classpath, Collection<Path> bootclasspath)
67      throws IOException {
68
69    ImmutableList<PreprocessedCompUnit> preProcessedUnits = CompUnitPreprocessor.preprocess(units);
70
71    TopLevelIndex.Builder tliBuilder = TopLevelIndex.builder();
72
73    SimpleEnv<ClassSymbol, SourceBoundClass> ienv =
74        bindSourceBoundClasses(preProcessedUnits, tliBuilder);
75
76    ImmutableSet<ClassSymbol> syms = ienv.asMap().keySet();
77
78    CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv =
79        ClassPathBinder.bind(classpath, bootclasspath, tliBuilder);
80
81    // Insertion order into the top-level index is important:
82    // * the first insert into the TLI wins
83    // * we search sources, bootclasspath, and classpath in that order
84    // * the first entry within a location wins.
85
86    TopLevelIndex tli = tliBuilder.build();
87
88    SimpleEnv<ClassSymbol, PackageSourceBoundClass> psenv =
89        bindPackages(ienv, tli, preProcessedUnits, classPathEnv);
90
91    Env<ClassSymbol, SourceHeaderBoundClass> henv = bindHierarchy(syms, psenv, classPathEnv);
92
93    Env<ClassSymbol, SourceTypeBoundClass> tenv =
94        bindTypes(
95            syms, henv, CompoundEnv.<ClassSymbol, HeaderBoundClass>of(classPathEnv).append(henv));
96
97    tenv =
98        constants(
99            syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv));
100    tenv =
101        disambiguateTypeAnnotations(
102            syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv));
103    tenv =
104        canonicalizeTypes(
105            syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv));
106
107    ImmutableMap.Builder<ClassSymbol, SourceTypeBoundClass> result = ImmutableMap.builder();
108    for (ClassSymbol sym : syms) {
109      result.put(sym, tenv.get(sym));
110    }
111    return new BindingResult(result.build(), classPathEnv);
112  }
113
114  /** Records enclosing declarations of member classes, and group classes by compilation unit. */
115  static SimpleEnv<ClassSymbol, SourceBoundClass> bindSourceBoundClasses(
116      ImmutableList<PreprocessedCompUnit> units, TopLevelIndex.Builder tliBuilder) {
117    SimpleEnv.Builder<ClassSymbol, SourceBoundClass> envBuilder = SimpleEnv.builder();
118    for (PreprocessedCompUnit unit : units) {
119      for (SourceBoundClass type : unit.types()) {
120        envBuilder.put(type.sym(), type);
121        tliBuilder.insert(type.sym());
122      }
123    }
124    return envBuilder.build();
125  }
126
127  /** Initializes scopes for compilation unit and package-level lookup. */
128  private static SimpleEnv<ClassSymbol, PackageSourceBoundClass> bindPackages(
129      Env<ClassSymbol, SourceBoundClass> ienv,
130      TopLevelIndex tli,
131      ImmutableList<PreprocessedCompUnit> units,
132      CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) {
133
134    SimpleEnv.Builder<ClassSymbol, PackageSourceBoundClass> env = SimpleEnv.builder();
135    Scope javaLang = verifyNotNull(tli.lookupPackage(ImmutableList.of("java", "lang")));
136    CompoundScope topLevel = CompoundScope.base(tli).append(javaLang);
137    for (PreprocessedCompUnit unit : units) {
138      ImmutableList<String> packagename =
139          ImmutableList.copyOf(Splitter.on('/').omitEmptyStrings().split(unit.packageName()));
140      Scope packageScope = tli.lookupPackage(packagename);
141      CanonicalSymbolResolver importResolver =
142          new CanonicalResolver(
143              packagename, CompoundEnv.<ClassSymbol, BoundClass>of(classPathEnv).append(ienv));
144      ImportScope importScope =
145          ImportIndex.create(unit.source(), importResolver, tli, unit.imports());
146      ImportScope wildImportScope = WildImportIndex.create(importResolver, tli, unit.imports());
147      MemberImportIndex memberImports =
148          new MemberImportIndex(unit.source(), importResolver, tli, unit.imports());
149      ImportScope scope =
150          ImportScope.fromScope(topLevel)
151              .append(wildImportScope)
152              .append(ImportScope.fromScope(packageScope))
153              .append(importScope);
154      for (SourceBoundClass type : unit.types()) {
155        env.put(type.sym(), new PackageSourceBoundClass(type, scope, memberImports, unit.source()));
156      }
157    }
158    return env.build();
159  }
160
161  /** Binds the type hierarchy (superclasses and interfaces) for all classes in the compilation. */
162  private static Env<ClassSymbol, SourceHeaderBoundClass> bindHierarchy(
163      Iterable<ClassSymbol> syms,
164      final SimpleEnv<ClassSymbol, PackageSourceBoundClass> psenv,
165      CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) {
166    ImmutableMap.Builder<
167            ClassSymbol, LazyEnv.Completer<ClassSymbol, HeaderBoundClass, SourceHeaderBoundClass>>
168        completers = ImmutableMap.builder();
169    for (ClassSymbol sym : syms) {
170      completers.put(
171          sym,
172          new LazyEnv.Completer<ClassSymbol, HeaderBoundClass, SourceHeaderBoundClass>() {
173            @Override
174            public SourceHeaderBoundClass complete(
175                Env<ClassSymbol, HeaderBoundClass> henv, ClassSymbol sym) {
176              return HierarchyBinder.bind(sym, psenv.get(sym), henv);
177            }
178          });
179    }
180    return new LazyEnv<>(completers.build(), classPathEnv);
181  }
182
183  private static Env<ClassSymbol, SourceTypeBoundClass> bindTypes(
184      ImmutableSet<ClassSymbol> syms,
185      Env<ClassSymbol, SourceHeaderBoundClass> shenv,
186      Env<ClassSymbol, HeaderBoundClass> henv) {
187    SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
188    for (ClassSymbol sym : syms) {
189      builder.put(sym, TypeBinder.bind(henv, sym, shenv.get(sym)));
190    }
191    return builder.build();
192  }
193
194  private static Env<ClassSymbol, SourceTypeBoundClass> canonicalizeTypes(
195      ImmutableSet<ClassSymbol> syms,
196      Env<ClassSymbol, SourceTypeBoundClass> stenv,
197      Env<ClassSymbol, TypeBoundClass> tenv) {
198    SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
199    for (ClassSymbol sym : syms) {
200      builder.put(sym, CanonicalTypeBinder.bind(sym, stenv.get(sym), tenv));
201    }
202    return builder.build();
203  }
204
205  private static Env<ClassSymbol, SourceTypeBoundClass> constants(
206      ImmutableSet<ClassSymbol> syms,
207      Env<ClassSymbol, SourceTypeBoundClass> env,
208      CompoundEnv<ClassSymbol, TypeBoundClass> baseEnv) {
209
210    // Prepare to lazily evaluate constant fields in each compilation unit.
211    // The laziness is necessary since constant fields can reference other
212    // constant fields.
213    ImmutableMap.Builder<FieldSymbol, LazyEnv.Completer<FieldSymbol, Const.Value, Const.Value>>
214        completers = ImmutableMap.builder();
215    for (ClassSymbol sym : syms) {
216      SourceTypeBoundClass info = env.get(sym);
217      for (FieldInfo field : info.fields()) {
218        if (!isConst(field)) {
219          continue;
220        }
221        completers.put(
222            field.sym(),
223            new LazyEnv.Completer<FieldSymbol, Const.Value, Const.Value>() {
224              @Override
225              public Const.Value complete(Env<FieldSymbol, Const.Value> env1, FieldSymbol k) {
226                try {
227                  return new ConstEvaluator(sym, sym, info, info.scope(), env1, baseEnv)
228                      .evalFieldInitializer(field.decl().init().get(), field.type());
229                } catch (LazyEnv.LazyBindingError e) {
230                  // fields initializers are allowed to reference the field being initialized,
231                  // but if they do they aren't constants
232                  return null;
233                }
234              }
235            });
236      }
237    }
238
239    // Create an environment of constant field values that combines
240    // lazily evaluated fields in the current compilation unit with
241    // constant fields in the classpath (which don't require evaluation).
242    Env<FieldSymbol, Const.Value> constenv =
243        new LazyEnv<>(completers.build(), SimpleEnv.<FieldSymbol, Const.Value>builder().build());
244
245    SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
246    for (ClassSymbol sym : syms) {
247      builder.put(sym, new ConstBinder(constenv, sym, baseEnv, env.get(sym)).bind());
248    }
249    return builder.build();
250  }
251
252  static boolean isConst(FieldInfo field) {
253    if ((field.access() & TurbineFlag.ACC_FINAL) == 0) {
254      return false;
255    }
256    if (field.decl() == null) {
257      return false;
258    }
259    final Optional<Tree.Expression> init = field.decl().init();
260    if (!init.isPresent()) {
261      return false;
262    }
263    switch (field.type().tyKind()) {
264      case PRIM_TY:
265        break;
266      case CLASS_TY:
267        if (((Type.ClassTy) field.type()).sym().equals(ClassSymbol.STRING)) {
268          break;
269        }
270        // fall through
271      default:
272        return false;
273    }
274    return true;
275  }
276
277  /**
278   * Disambiguate annotations on field types and method return types that could be declaration or
279   * type annotations.
280   */
281  private static Env<ClassSymbol, SourceTypeBoundClass> disambiguateTypeAnnotations(
282      ImmutableSet<ClassSymbol> syms,
283      Env<ClassSymbol, SourceTypeBoundClass> stenv,
284      Env<ClassSymbol, TypeBoundClass> tenv) {
285    SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
286    for (ClassSymbol sym : syms) {
287      builder.put(sym, DisambiguateTypeAnnotations.bind(stenv.get(sym), tenv));
288    }
289    return builder.build();
290  }
291
292  /** The result of binding: bound nodes for sources in the compilation, and the classpath. */
293  public static class BindingResult {
294    private final ImmutableMap<ClassSymbol, SourceTypeBoundClass> units;
295    private final CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv;
296
297    public BindingResult(
298        ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
299        CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) {
300      this.units = units;
301      this.classPathEnv = classPathEnv;
302    }
303
304    /** Bound nodes for sources in the compilation. */
305    public ImmutableMap<ClassSymbol, SourceTypeBoundClass> units() {
306      return units;
307    }
308
309    /** The classpath. */
310    public CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv() {
311      return classPathEnv;
312    }
313  }
314}
315