• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
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.android.tools.metalava.stub
18 
19 import com.android.tools.metalava.model.CallableItem
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.ConstructorItem
23 import com.android.tools.metalava.model.FilterPredicate
24 import com.android.tools.metalava.model.PackageList
25 import com.android.tools.metalava.model.VisibilityLevel
26 
27 class StubConstructorManager(codebase: Codebase) {
28 
29     private val packages: PackageList = codebase.getPackages()
30 
31     /** Map from [ClassItem] to [StubConstructors]. */
32     private val classToStubConstructors = mutableMapOf<ClassItem, StubConstructors>()
33 
34     /**
35      * Contains information about constructors needed when generating stubs for a specific class.
36      */
37     private class StubConstructors(
38         /**
39          * The default constructor to invoke on the class from subclasses.
40          *
41          * Note that in some cases [stubConstructor] may not be in [ClassItem.constructors], e.g.
42          * when we need to create a constructor to match a public parent class with a non-default
43          * constructor and the one in the code is not a match, e.g. is marked `@hide`.
44          *
45          * Is `null` if the class has a default constructor that is accessible.
46          */
47         val stubConstructor: ConstructorItem?,
48 
49         /**
50          * The constructor that constructors in a stub class must delegate to in their `super` call.
51          *
52          * Is `null` if the super class has a default constructor.
53          */
54         val superConstructor: ConstructorItem?,
55     ) {
56         companion object {
57             val EMPTY = StubConstructors(null, null)
58         }
59     }
60 
61     fun addConstructors(filter: FilterPredicate) {
62         // Let's say we have
63         //  class GrandParent { public GrandParent(int) {} }
64         //  class Parent {  Parent(int) {} }
65         //  class Child { public Child(int) {} }
66         //
67         // Here Parent's constructor is not public. For normal stub generation we'd end up with
68         // this:
69         //  class GrandParent { public GrandParent(int) {} }
70         //  class Parent { }
71         //  class Child { public Child(int) {} }
72         //
73         // This doesn't compile - Parent can't have a default constructor since there isn't
74         // one for it to invoke on GrandParent.
75         //
76         // we can generate a fake constructor instead, such as
77         //   Parent() { super(0); }
78         //
79         // But it's hard to do this lazily; what if we're generating the Child class first?
80         // Therefore, we'll instead walk over the hierarchy and insert these constructors into the
81         // Item hierarchy such that code generation can find them.
82         //
83         // We also need to handle the throws list, so we can't just unconditionally insert package
84         // private constructors
85 
86         // Add constructors to the classes by walking up the super hierarchy and recursively add
87         // constructors; we'll do it recursively to make sure that the superclass has had its
88         // constructors initialized first (such that we can match the parameter lists and throws
89         // signatures), and we use the tag fields to avoid looking at all the internal classes more
90         // than once.
91         packages.allClasses().filter { filter.test(it) }.forEach { addConstructors(it, filter) }
92     }
93 
94     /**
95      * Handle computing constructor hierarchy.
96      *
97      * We'll be setting several attributes: [StubConstructors.stubConstructor] : The default
98      * constructor to invoke in this class from subclasses. **NOTE**: This constructor may not be
99      * part of the [ClassItem.constructors] list, e.g. for package private default constructors
100      * we've inserted (because there were no public constructors or constructors not using hidden
101      * parameter types.)
102      *
103      * [StubConstructors.superConstructor] : The super constructor to invoke.
104      */
105     private fun addConstructors(
106         cls: ClassItem,
107         filter: FilterPredicate,
108     ): StubConstructors {
109 
110         // Don't add constructors to interfaces, enums, annotations, etc
111         if (!cls.isClass()) {
112             return StubConstructors.EMPTY
113         }
114 
115         // What happens if we have
116         //  package foo:
117         //     public class A { public A(int) }
118         //  package bar
119         //     public class B extends A { public B(int) }
120         // If we just try inserting package private constructors here things will NOT work:
121         //  package foo:
122         //     public class A { public A(int); A() {} }
123         //  package bar
124         //     public class B extends A { public B(int); B() }
125         // because A <() is not accessible from B() -- it's outside the same package.
126         //
127         // So, we'll need to model the real constructors for all the scenarios where that works.
128         //
129         // The remaining challenge is that there will be some gaps: when we don't have a default
130         // constructor, subclass constructors will have to have an explicit super(args) call to pick
131         // the parent constructor to use. And which one? It generally doesn't matter; just pick one,
132         // but unfortunately, the super constructor can throw exceptions, and in that case the
133         // subclass constructor must also throw all those exceptions (you can't surround a super
134         // call with try/catch.)
135         //
136         // Luckily, this does not seem to be an actual problem with any of the source code that
137         // metalava currently processes. If it did become a problem then the solution would be to
138         // pick super constructors with a compatible set of throws.
139 
140         // If this class has already been visited then return the StubConstructors that was created.
141         classToStubConstructors[cls]?.let {
142             return it
143         }
144 
145         // Remember that we have visited this class so that it is not visited again. This does not
146         // strictly need to be done before visiting the super classes as there should not be cycles
147         // in the class hierarchy. However, if due to some invalid input there is then doing this
148         // here will prevent those cycles from causing a stack overflow. This will be overridden
149         // with the actual constructors below.
150         classToStubConstructors[cls] = StubConstructors.EMPTY
151 
152         // First handle its super class hierarchy to make sure that we've already constructed super
153         // classes.
154         val superClass = cls.filteredSuperclass(filter)
155         val superClassConstructors = superClass?.let { addConstructors(it, filter) }
156 
157         val superDefaultConstructor = superClassConstructors?.stubConstructor
158 
159         // Find constructor subclasses should delegate to, creating one if necessary. If the stub
160         // will contain a no-args constructor then that is represented as `null` to allow it to be
161         // optimized below.
162         val filteredConstructors = cls.filteredConstructors(filter).toList()
163         val stubConstructor =
164             if (filteredConstructors.isNotEmpty()) {
165                 // Pick the best constructor. If that is a no-args constructor then represent that
166                 // as `null`.
167                 pickBest(filteredConstructors).takeUnless { it.parameters().isEmpty() }
168             } else {
169                 // No accessible constructors are available (not even a default implicit
170                 // constructor) so a package private constructor is needed. Technically, this will
171                 // result in the stub class having a constructor that isn't available at runtime,
172                 // but creating subclasses in API packages is not supported.
173                 cls.createDefaultConstructor(VisibilityLevel.PACKAGE_PRIVATE)
174             }
175 
176         // If neither the constructors in this class nor its subclasses need to add a `super(...)`
177         // call then use a shared object.
178         if (stubConstructor == null && superDefaultConstructor == null) {
179             return StubConstructors.EMPTY
180         }
181 
182         return StubConstructors(
183                 stubConstructor = stubConstructor,
184                 superConstructor = superDefaultConstructor,
185             )
186             .also {
187                 // Save it away for retrieval by subclasses.
188                 classToStubConstructors[cls] = it
189             }
190     }
191 
192     companion object {
193         /**
194          * Comparator to pick the best [ConstructorItem] to which derived stub classes will
195          * delegate.
196          *
197          * Uses the following rules:
198          * 1. Fewest throwables as they have to be propagated down to constructors that delegate to
199          *    it.
200          * 2. Fewest parameters to reduce the size of the `super(...)` call.
201          * 3. Shortest erased parameter types as that should reduce the size of the `super(...)`
202          *    call.
203          * 4. Total ordering defined by [CallableItem.comparator] to ensure consistent behavior.
204          *
205          * Returns less than zero if the first [ConstructorItem] passed to `compare(c1, c2)` is the
206          * best option, more if the second [ConstructorItem] is the best option and zero if they are
207          * the same.
208          */
209         private val bestStubConstructorComparator: Comparator<ConstructorItem> =
210             Comparator.comparingInt<ConstructorItem?>({ it.throwsTypes().size })
211                 .thenComparingInt({ it.parameters().size })
212                 .thenComparingInt({
213                     it.parameters().sumOf { it.type().toErasedTypeString().length }
214                 })
215                 .thenComparing(CallableItem.comparator)
216     }
217 
218     /**
219      * Pick the best [ConstructorItem] to which derived stub classes will delegate.
220      *
221      * Selects the first [ConstructorItem] in [constructors] which compares less to or equal to all
222      * the other [ConstructorItem]s in the list when compared using [bestStubConstructorComparator].
223      * That defines a total order so the result is independent of the order of [constructors].
224      */
225     private fun pickBest(constructors: List<ConstructorItem>): ConstructorItem {
226         // Try to pick the best constructor to which derived stub classes can delegate.
227         return constructors.reduce { first, second ->
228             val result = bestStubConstructorComparator.compare(first, second)
229             if (result <= 0) first else second
230         }
231     }
232 
233     /**
234      * Get the optional synthetic constructor, if created, for [classItem].
235      *
236      * If a [ClassItem] does not have an accessible constructor then one will be synthesized for use
237      * by subclasses. This method returns that constructor, or `null` if there was no synthetic
238      * constructor.
239      */
240     fun optionalSyntheticConstructor(classItem: ClassItem): ConstructorItem? {
241         val stubConstructor = classToStubConstructors[classItem]?.stubConstructor ?: return null
242         if (stubConstructor in classItem.constructors()) return null
243         return stubConstructor
244     }
245 
246     /** Get the optional super constructor, if needed, for [classItem]. */
247     fun optionalSuperConstructor(classItem: ClassItem): ConstructorItem? {
248         return classToStubConstructors[classItem]?.superConstructor
249     }
250 }
251