• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 package com.android.tools.r8.utils;
5 
6 import com.android.tools.r8.errors.CompilationError;
7 import com.android.tools.r8.errors.Unreachable;
8 import com.android.tools.r8.graph.ClassKind;
9 import com.android.tools.r8.graph.DexClass;
10 import com.android.tools.r8.graph.DexType;
11 import com.google.common.collect.Sets;
12 import java.util.ArrayList;
13 import java.util.IdentityHashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.function.Predicate;
19 import java.util.function.Supplier;
20 
21 /**
22  * Represents a collection of classes. Collection can be fully loaded,
23  * lazy loaded or have preloaded classes along with lazy loaded content.
24  */
25 public abstract class ClassMap<T extends DexClass> {
26   // For each type which has ever been queried stores one class loaded from
27   // resources provided by different resource providers.
28   //
29   // NOTE: all access must be synchronized on `classes`.
30   private final Map<DexType, Supplier<T>> classes;
31 
32   // Class provider if available.
33   //
34   // If the class provider is `null` it indicates that all classes are already present
35   // in a map referenced by `classes` and thus the collection is fully loaded.
36   //
37   // NOTE: all access must be synchronized on `classes`.
38   private ClassProvider<T> classProvider;
39 
ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider)40   ClassMap(Map<DexType, Supplier<T>> classes, ClassProvider<T> classProvider) {
41     this.classes = classes == null ? new IdentityHashMap<>() : classes;
42     this.classProvider = classProvider;
43     assert this.classProvider == null || this.classProvider.getClassKind() == getClassKind();
44   }
45 
46   /** Resolves a class conflict by selecting a class, may generate compilation error. */
resolveClassConflict(T a, T b)47   abstract T resolveClassConflict(T a, T b);
48 
49   /** Return supplier for preloaded class. */
getTransparentSupplier(T clazz)50   abstract Supplier<T> getTransparentSupplier(T clazz);
51 
52   /** Kind of the classes supported by this collection. */
getClassKind()53   abstract ClassKind getClassKind();
54 
55   @Override
toString()56   public String toString() {
57     synchronized (classes) {
58       return classes.size() + " loaded, provider: " +
59           (classProvider == null ? "none" : classProvider.toString());
60     }
61   }
62 
63   /** Returns a definition for a class or `null` if there is no such class in the collection. */
get(DexType type)64   public T get(DexType type) {
65     Supplier<T> supplier;
66 
67     synchronized (classes) {
68       supplier = classes.get(type);
69 
70       // Get class supplier, create it if it does not
71       // exist and the collection is NOT fully loaded.
72       if (supplier == null) {
73         if (classProvider == null) {
74           // There is no supplier, but the collection is fully loaded.
75           return null;
76         }
77 
78         supplier = new ConcurrentClassLoader<>(this, this.classProvider, type);
79         classes.put(type, supplier);
80       }
81     }
82 
83     return supplier.get();
84   }
85 
86   /** Returns all classes from the collection. The collection must be force-loaded. */
getAllClasses()87   public List<T> getAllClasses() {
88     List<T> loadedClasses = new ArrayList<>();
89     synchronized (classes) {
90       if (classProvider != null) {
91         throw new Unreachable("Getting all classes from not fully loaded collection.");
92       }
93       for (Supplier<T> supplier : classes.values()) {
94         // Since the class map is fully loaded, all suppliers must be
95         // loaded and non-null.
96         T clazz = supplier.get();
97         assert clazz != null;
98         loadedClasses.add(clazz);
99       }
100     }
101     return loadedClasses;
102   }
103 
104   /**
105    * Forces loading of all the classes satisfying the criteria specified.
106    *
107    * NOTE: after this method finishes, the class map is considered to be fully-loaded
108    * and thus sealed. This has one side-effect: if we filter out some of the classes
109    * with `load` predicate, these classes will never be loaded.
110    */
forceLoad(Predicate<DexType> load)111   public void forceLoad(Predicate<DexType> load) {
112     Set<DexType> knownClasses;
113     ClassProvider<T> classProvider;
114 
115     synchronized (classes) {
116       classProvider = this.classProvider;
117       if (classProvider == null) {
118         return;
119       }
120 
121       // Collects the types which might be represented in fully loaded class map.
122       knownClasses = Sets.newIdentityHashSet();
123       knownClasses.addAll(classes.keySet());
124     }
125 
126     // Add all types the class provider provides. Note that it may take time for class
127     // provider to collect these types, so ve do it outside synchronized context.
128     knownClasses.addAll(classProvider.collectTypes());
129 
130     // Make sure all the types in `knownClasses` are loaded.
131     //
132     // We just go and touch every class, thus triggering their loading if they
133     // are not loaded so far. In case the class has already been loaded,
134     // touching the class will be a no-op with minimal overhead.
135     for (DexType type : knownClasses) {
136       if (load.test(type)) {
137         get(type);
138       }
139     }
140 
141     synchronized (classes) {
142       if (this.classProvider == null) {
143         return; // Has been force-loaded concurrently.
144       }
145 
146       // We avoid calling get() on a class supplier unless we know it was loaded.
147       // At this time `classes` may have more types then `knownClasses`, but for
148       // all extra classes we expect the supplier to return 'null' after loading.
149       Iterator<Map.Entry<DexType, Supplier<T>>> iterator = classes.entrySet().iterator();
150       while (iterator.hasNext()) {
151         Map.Entry<DexType, Supplier<T>> e = iterator.next();
152 
153         if (knownClasses.contains(e.getKey())) {
154           // Get the class (it is expected to be loaded by this time).
155           T clazz = e.getValue().get();
156           if (clazz != null) {
157             // Since the class is already loaded, get rid of possible wrapping suppliers.
158             assert clazz.type == e.getKey();
159             e.setValue(getTransparentSupplier(clazz));
160             continue;
161           }
162         }
163 
164         // If the type is not in `knownClasses` or resolves to `null`,
165         // just remove the record from the map.
166         iterator.remove();
167       }
168 
169       // Mark the class map as fully loaded.
170       this.classProvider = null;
171     }
172   }
173 
174   // Supplier implementing a thread-safe loader for a class loaded from a
175   // class provider. Helps avoid synchronizing on the whole class map
176   // when loading a class.
177   private static class ConcurrentClassLoader<T extends DexClass> implements Supplier<T> {
178     private ClassMap<T> classMap;
179     private ClassProvider<T> provider;
180     private DexType type;
181 
182     private T clazz = null;
183     private volatile boolean ready = false;
184 
ConcurrentClassLoader(ClassMap<T> classMap, ClassProvider<T> provider, DexType type)185     ConcurrentClassLoader(ClassMap<T> classMap, ClassProvider<T> provider, DexType type) {
186       this.classMap = classMap;
187       this.provider = provider;
188       this.type = type;
189     }
190 
191     @Override
get()192     public T get() {
193       if (ready) {
194         return clazz;
195       }
196 
197       synchronized (this) {
198         if (!ready) {
199           assert classMap != null && provider != null && type != null;
200           provider.collectClass(type, createdClass -> {
201             assert createdClass != null;
202             assert classMap.getClassKind().isOfKind(createdClass);
203             assert !ready;
204 
205             if (createdClass.type != type) {
206               throw new CompilationError(
207                   "Class content provided for type descriptor " + type.toSourceString() +
208                       " actually defines class " + createdClass.type.toSourceString());
209             }
210 
211             if (clazz == null) {
212               clazz = createdClass;
213             } else {
214               // The class resolution *may* generate a compilation error as one of
215               // possible resolutions. In this case we leave `value` in (false, null)
216               // state so in rare case of another thread trying to get the same class
217               // before this error is propagated it will get the same conflict.
218               T oldClass = clazz;
219               clazz = null;
220               clazz = classMap.resolveClassConflict(oldClass, createdClass);
221             }
222           });
223 
224           classMap = null;
225           provider = null;
226           type = null;
227           ready = true;
228         }
229       }
230 
231       assert ready;
232       assert classMap == null && provider == null && type == null;
233       return clazz;
234     }
235   }
236 }
237