Skip to content

Commit c0da958

Browse files
refactor: move code from BinaryClassCapability to the checker.
Better encapsulation, less complexity in the Capability.kt file. If further changes are needed, they can all be in one place.
1 parent 89df171 commit c0da958

File tree

2 files changed

+121
-118
lines changed

2 files changed

+121
-118
lines changed

src/main/kotlin/com/autonomousapps/internal/binary/BinaryCompatibilityChecker.kt

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.autonomousapps.internal.binary
44

5+
import com.autonomousapps.internal.strings.dotty
56
import com.autonomousapps.internal.unsafeLazy
7+
import com.autonomousapps.internal.utils.efficient
68
import com.autonomousapps.internal.utils.filterToOrderedSet
79
import com.autonomousapps.internal.utils.mapToOrderedSet
810
import com.autonomousapps.internal.utils.mapToSet
@@ -13,6 +15,10 @@ import com.autonomousapps.model.internal.intermediates.consumer.MemberAccess
1315
import com.autonomousapps.model.internal.intermediates.producer.BinaryClass
1416
import com.autonomousapps.visitor.GraphViewVisitor
1517

18+
/**
19+
* TODO(tsr): there are [reports](https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/1604) that
20+
* this analysis is blowing up heap usage and leading to OOMs.
21+
*/
1622
internal class BinaryCompatibilityChecker(
1723
private val coordinates: Coordinates,
1824
private val binaryClassCapability: BinaryClassCapability,
@@ -25,6 +31,28 @@ internal class BinaryCompatibilityChecker(
2531
val isBinaryCompatible: Boolean,
2632
)
2733

34+
private data class PartitionResult(
35+
val matchingClasses: Set<BinaryClass>,
36+
val nonMatchingClasses: Set<BinaryClass>,
37+
) {
38+
39+
companion object {
40+
fun empty(): PartitionResult = PartitionResult(emptySet(), emptySet())
41+
}
42+
43+
class Builder {
44+
val matchingClasses = sortedSetOf<BinaryClass>()
45+
val nonMatchingClasses = sortedSetOf<BinaryClass>()
46+
47+
fun build(): PartitionResult {
48+
return PartitionResult(
49+
matchingClasses = matchingClasses.efficient(),
50+
nonMatchingClasses = nonMatchingClasses.efficient(),
51+
)
52+
}
53+
}
54+
}
55+
2856
val result: Result? by unsafeLazy { compute() }
2957

3058
private fun compute(): Result? {
@@ -77,7 +105,7 @@ internal class BinaryCompatibilityChecker(
77105
)
78106
}
79107

80-
private fun Set<BinaryClassCapability.PartitionResult>.reduce(): BinaryClassCapability.PartitionResult {
108+
private fun Set<PartitionResult>.reduce(): PartitionResult {
81109
val matches = sortedSetOf<BinaryClass>()
82110
val nonMatches = sortedSetOf<BinaryClass>()
83111

@@ -86,7 +114,7 @@ internal class BinaryCompatibilityChecker(
86114
nonMatches.addAll(result.nonMatchingClasses)
87115
}
88116

89-
return BinaryClassCapability.PartitionResult(
117+
return PartitionResult(
90118
matchingClasses = matches.reduce(),
91119
nonMatchingClasses = nonMatches.reduce(),
92120
)
@@ -115,4 +143,95 @@ internal class BinaryCompatibilityChecker(
115143

116144
return builders.values.mapToOrderedSet { it.build() }
117145
}
146+
147+
private fun BinaryClassCapability.findMatchingClasses(memberAccess: MemberAccess): PartitionResult {
148+
val relevant = findRelevantBinaryClasses(memberAccess)
149+
150+
// lenient
151+
if (relevant.isEmpty()) return PartitionResult.empty()
152+
153+
return relevant
154+
.map { bin -> bin.partition(memberAccess) }
155+
.fold(PartitionResult.Builder()) { acc, (match, nonMatch) ->
156+
acc.apply {
157+
match?.let { matchingClasses.add(it) }
158+
nonMatch?.let { nonMatchingClasses.add(it) }
159+
}
160+
}
161+
.build()
162+
}
163+
164+
/**
165+
* Example:
166+
* 1. [memberAccess] is for `groovy/lang/MetaClass#getProperty`.
167+
* 2. That method is actually provided by `groovy/lang/MetaObjectProtocol`, which `groovy/lang/MetaClass` implements.
168+
*
169+
* All of the above ("this" class, its super class, and its interfaces) are relevant for search purposes. Note we
170+
* don't inspect the member names for this check.
171+
*/
172+
private fun BinaryClassCapability.findRelevantBinaryClasses(memberAccess: MemberAccess): Set<BinaryClass> {
173+
// direct references
174+
val relevant = binaryClasses.filterTo(mutableSetOf()) { bin ->
175+
bin.className == memberAccess.owner.dotty()
176+
}
177+
178+
// Walk up the class hierarchy
179+
fun walkUp(): Int {
180+
binaryClasses.filterTo(relevant) { bin ->
181+
bin.className in relevant.map { it.superClassName }
182+
|| bin.className in relevant.flatMap { it.interfaces }
183+
}
184+
return relevant.size
185+
}
186+
187+
// TODO(tsr): this could be more performant
188+
do {
189+
val size = relevant.size
190+
val newSize = walkUp()
191+
} while (newSize > size)
192+
193+
return relevant
194+
}
195+
196+
/**
197+
* Partitions and returns artificial pair of [BinaryClasses][BinaryClass]. Non-null elements indicate relevant (to
198+
* [memberAccess]) matching and non-matching members of this `BinaryClass`. Matching members are binary-compatible;
199+
* and non-matching members have the same [name][com.autonomousapps.model.internal.intermediates.producer.Member.name]
200+
* but incompatible [descriptors][com.autonomousapps.model.internal.intermediates.producer.Member.descriptor], and are
201+
* therefore binary-incompatible.
202+
*
203+
* nb: We don't want this as a method directly in BinaryClass because it can't safely assert the prerequisite that
204+
* it's only called on "relevant" classes. THIS class, however, can, via [findRelevantBinaryClasses].
205+
*/
206+
private fun BinaryClass.partition(memberAccess: MemberAccess): Pair<BinaryClass?, BinaryClass?> {
207+
// There can be only one match
208+
val matchingFields = effectivelyPublicFields.firstOrNull { it.matches(memberAccess) }
209+
val matchingMethods = effectivelyPublicMethods.firstOrNull { it.matches(memberAccess) }
210+
211+
// There can be many non-matches
212+
val nonMatchingFields = effectivelyPublicFields.filterToOrderedSet { it.doesNotMatch(memberAccess) }
213+
val nonMatchingMethods = effectivelyPublicMethods.filterToOrderedSet { it.doesNotMatch(memberAccess) }
214+
215+
// Create a view of the binary class containing only the matching members.
216+
val match = if (matchingFields != null || matchingMethods != null) {
217+
copy(
218+
effectivelyPublicFields = matchingFields?.let { setOf(it) }.orEmpty(),
219+
effectivelyPublicMethods = matchingMethods?.let { setOf(it) }.orEmpty()
220+
)
221+
} else {
222+
null
223+
}
224+
225+
// Create a view of the binary class containing only the non-matching members.
226+
val nonMatch = if (nonMatchingFields.isNotEmpty() || nonMatchingMethods.isNotEmpty()) {
227+
copy(
228+
effectivelyPublicFields = nonMatchingFields,
229+
effectivelyPublicMethods = nonMatchingMethods,
230+
)
231+
} else {
232+
null
233+
}
234+
235+
return match to nonMatch
236+
}
118237
}

src/main/kotlin/com/autonomousapps/model/internal/Capability.kt

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.autonomousapps.model.internal
44

5-
import com.autonomousapps.internal.strings.dotty
65
import com.autonomousapps.internal.unsafeLazy
76
import com.autonomousapps.internal.utils.LexicographicIterableComparator
87
import com.autonomousapps.internal.utils.efficient
9-
import com.autonomousapps.internal.utils.filterToOrderedSet
108
import com.autonomousapps.internal.utils.mapToOrderedSet
11-
import com.autonomousapps.model.internal.intermediates.consumer.MemberAccess
129
import com.autonomousapps.model.internal.intermediates.producer.BinaryClass
1310
import com.autonomousapps.model.internal.intermediates.producer.Constant
1411
import com.autonomousapps.model.internal.intermediates.producer.ReflectingDependency
@@ -152,124 +149,11 @@ internal data class BinaryClassCapability(
152149

153150
val classes by unsafeLazy { binaryClasses.mapToOrderedSet { it.className } }
154151

155-
internal data class PartitionResult(
156-
val matchingClasses: Set<BinaryClass>,
157-
val nonMatchingClasses: Set<BinaryClass>,
158-
) {
159-
160-
companion object {
161-
fun empty(): PartitionResult = PartitionResult(emptySet(), emptySet())
162-
}
163-
164-
class Builder {
165-
val matchingClasses = sortedSetOf<BinaryClass>()
166-
val nonMatchingClasses = sortedSetOf<BinaryClass>()
167-
168-
fun build(): PartitionResult {
169-
return PartitionResult(
170-
matchingClasses = matchingClasses.efficient(),
171-
nonMatchingClasses = nonMatchingClasses.efficient(),
172-
)
173-
}
174-
}
175-
}
176-
177152
override fun merge(other: Capability): Capability {
178153
return newInstance(
179154
binaryClasses = (binaryClasses + (other as BinaryClassCapability).binaryClasses).efficient(),
180155
)
181156
}
182-
183-
internal fun findMatchingClasses(memberAccess: MemberAccess): PartitionResult {
184-
val relevant = findRelevantBinaryClasses(memberAccess)
185-
186-
// lenient
187-
if (relevant.isEmpty()) return PartitionResult.empty()
188-
189-
return relevant
190-
.map { bin -> bin.partition(memberAccess) }
191-
.fold(PartitionResult.Builder()) { acc, (match, nonMatch) ->
192-
acc.apply {
193-
match?.let { matchingClasses.add(it) }
194-
nonMatch?.let { nonMatchingClasses.add(it) }
195-
}
196-
}
197-
.build()
198-
}
199-
200-
/**
201-
* Example:
202-
* 1. [memberAccess] is for `groovy/lang/MetaClass#getProperty`.
203-
* 2. That method is actually provided by `groovy/lang/MetaObjectProtocol`, which `groovy/lang/MetaClass` implements.
204-
*
205-
* All of the above ("this" class, its super class, and its interfaces) are relevant for search purposes. Note we
206-
* don't inspect the member names for this check.
207-
*/
208-
private fun findRelevantBinaryClasses(memberAccess: MemberAccess): Set<BinaryClass> {
209-
// direct references
210-
val relevant = binaryClasses.filterTo(mutableSetOf()) { bin ->
211-
bin.className == memberAccess.owner.dotty()
212-
}
213-
214-
// Walk up the class hierarchy
215-
fun walkUp(): Int {
216-
binaryClasses.filterTo(relevant) { bin ->
217-
bin.className in relevant.map { it.superClassName }
218-
|| bin.className in relevant.flatMap { it.interfaces }
219-
}
220-
return relevant.size
221-
}
222-
223-
// TODO(tsr): this could be more performant
224-
do {
225-
val size = relevant.size
226-
val newSize = walkUp()
227-
} while (newSize > size)
228-
229-
return relevant
230-
}
231-
232-
/**
233-
* Partitions and returns artificial pair of [BinaryClasses][BinaryClass]. Non-null elements indicate relevant (to
234-
* [memberAccess]) matching and non-matching members of this `BinaryClass`. Matching members are binary-compatible;
235-
* and non-matching members have the same [name][com.autonomousapps.model.internal.intermediates.producer.Member.name]
236-
* but incompatible [descriptors][com.autonomousapps.model.internal.intermediates.producer.Member.descriptor], and are
237-
* therefore binary-incompatible.
238-
*
239-
* nb: We don't want this as a method directly in BinaryClass because it can't safely assert the prerequisite that
240-
* it's only called on "relevant" classes. THIS class, however, can, via [findRelevantBinaryClasses].
241-
*/
242-
private fun BinaryClass.partition(memberAccess: MemberAccess): Pair<BinaryClass?, BinaryClass?> {
243-
// There can be only one match
244-
val matchingFields = effectivelyPublicFields.firstOrNull { it.matches(memberAccess) }
245-
val matchingMethods = effectivelyPublicMethods.firstOrNull { it.matches(memberAccess) }
246-
247-
// There can be many non-matches
248-
val nonMatchingFields = effectivelyPublicFields.filterToOrderedSet { it.doesNotMatch(memberAccess) }
249-
val nonMatchingMethods = effectivelyPublicMethods.filterToOrderedSet { it.doesNotMatch(memberAccess) }
250-
251-
// Create a view of the binary class containing only the matching members.
252-
val match = if (matchingFields != null || matchingMethods != null) {
253-
copy(
254-
effectivelyPublicFields = matchingFields?.let { setOf(it) }.orEmpty(),
255-
effectivelyPublicMethods = matchingMethods?.let { setOf(it) }.orEmpty()
256-
)
257-
} else {
258-
null
259-
}
260-
261-
// Create a view of the binary class containing only the non-matching members.
262-
val nonMatch = if (nonMatchingFields.isNotEmpty() || nonMatchingMethods.isNotEmpty()) {
263-
copy(
264-
effectivelyPublicFields = nonMatchingFields,
265-
effectivelyPublicMethods = nonMatchingMethods,
266-
)
267-
} else {
268-
null
269-
}
270-
271-
return match to nonMatch
272-
}
273157
}
274158

275159
@TypeLabel("const")

0 commit comments

Comments
 (0)