@@ -2,18 +2,17 @@ package mill.codesig
2
2
3
3
import mill .codesig .JvmModel .*
4
4
import mill .internal .{SpanningForest , Tarjans }
5
- import ujson .Obj
5
+ import ujson .{ Obj , Arr }
6
6
import upickle .default .{Writer , writer }
7
7
8
8
import scala .collection .immutable .SortedMap
9
+ import scala .collection .mutable
9
10
10
11
class CallGraphAnalysis (
11
- localSummary : LocalSummary ,
12
- resolved : ResolvedCalls ,
13
- externalSummary : ExternalSummary ,
14
- ignoreCall : (Option [MethodDef ], MethodSig ) => Boolean ,
15
- logger : Logger ,
16
- prevTransitiveCallGraphHashesOpt : () => Option [Map [String , Int ]]
12
+ val localSummary : LocalSummary ,
13
+ val resolved : ResolvedCalls ,
14
+ val externalSummary : ExternalSummary ,
15
+ ignoreCall : (Option [MethodDef ], MethodSig ) => Boolean
17
16
)(implicit st : SymbolTable ) {
18
17
19
18
val methods : Map [MethodDef , LocalSummary .MethodInfo ] = for {
@@ -40,17 +39,13 @@ class CallGraphAnalysis(
40
39
lazy val methodCodeHashes : SortedMap [String , Int ] =
41
40
methods.map { case (k, vs) => (k.toString, vs.codeHash) }.to(SortedMap )
42
41
43
- logger.mandatoryLog(methodCodeHashes)
44
-
45
42
lazy val prettyCallGraph : SortedMap [String , Array [CallGraphAnalysis .Node ]] = {
46
43
indexGraphEdges.zip(indexToNodes).map { case (vs, k) =>
47
44
(k.toString, vs.map(indexToNodes))
48
45
}
49
46
.to(SortedMap )
50
47
}
51
48
52
- logger.mandatoryLog(prettyCallGraph)
53
-
54
49
def transitiveCallGraphValues [V : scala.reflect.ClassTag ](
55
50
nodeValues : Array [V ],
56
51
reduce : (V , V ) => V ,
@@ -78,44 +73,45 @@ class CallGraphAnalysis(
78
73
.collect { case (CallGraphAnalysis .LocalDef (d), v) => (d.toString, v) }
79
74
.to(SortedMap )
80
75
81
- logger.mandatoryLog(transitiveCallGraphHashes0)
82
- logger.log(transitiveCallGraphHashes)
83
-
84
- lazy val spanningInvalidationTree : Obj = prevTransitiveCallGraphHashesOpt() match {
85
- case Some (prevTransitiveCallGraphHashes) =>
86
- CallGraphAnalysis .spanningInvalidationTree(
87
- prevTransitiveCallGraphHashes,
88
- transitiveCallGraphHashes0,
89
- indexToNodes,
90
- indexGraphEdges
91
- )
92
- case None => ujson.Obj ()
76
+ def calculateSpanningInvalidationTree (
77
+ prevTransitiveCallGraphHashesOpt : => Option [Map [String , Int ]]
78
+ ): Obj = {
79
+ prevTransitiveCallGraphHashesOpt match {
80
+ case Some (prevTransitiveCallGraphHashes) =>
81
+ CallGraphAnalysis .spanningInvalidationTree(
82
+ prevTransitiveCallGraphHashes,
83
+ transitiveCallGraphHashes0,
84
+ indexToNodes,
85
+ indexGraphEdges
86
+ )
87
+ case None => ujson.Obj ()
88
+ }
93
89
}
94
90
95
- logger.mandatoryLog(spanningInvalidationTree)
91
+ def calculateInvalidClassName (
92
+ prevTransitiveCallGraphHashesOpt : => Option [Map [String , Int ]]
93
+ ): Set [String ] = {
94
+ prevTransitiveCallGraphHashesOpt match {
95
+ case Some (prevTransitiveCallGraphHashes) =>
96
+ CallGraphAnalysis .invalidClassNames(
97
+ prevTransitiveCallGraphHashes,
98
+ transitiveCallGraphHashes0,
99
+ indexToNodes,
100
+ indexGraphEdges
101
+ )
102
+ case None => Set .empty
103
+ }
104
+ }
96
105
}
97
106
98
107
object CallGraphAnalysis {
99
108
100
- /**
101
- * Computes the minimal spanning forest of the that covers the nodes in the
102
- * call graph whose transitive call graph hashes has changed since the last
103
- * run, rendered as a JSON dictionary tree. This provides a great "debug
104
- * view" that lets you easily Cmd-F to find a particular node and then trace
105
- * it up the JSON hierarchy to figure out what upstream node was the root
106
- * cause of the change in the callgraph.
107
- *
108
- * There are typically multiple possible spanning forests for a given graph;
109
- * one is chosen arbitrarily. This is usually fine, since when debugging you
110
- * typically are investigating why there's a path to a node at all where none
111
- * should exist, rather than trying to fully analyse all possible paths
112
- */
113
- def spanningInvalidationTree (
109
+ private def getSpanningForest (
114
110
prevTransitiveCallGraphHashes : Map [String , Int ],
115
111
transitiveCallGraphHashes0 : Array [(CallGraphAnalysis .Node , Int )],
116
112
indexToNodes : Array [Node ],
117
113
indexGraphEdges : Array [Array [Int ]]
118
- ): ujson. Obj = {
114
+ ) = {
119
115
val transitiveCallGraphHashes0Map = transitiveCallGraphHashes0.toMap
120
116
121
117
val nodesWithChangedHashes = indexGraphEdges
@@ -135,12 +131,64 @@ object CallGraphAnalysis {
135
131
val reverseGraphEdges =
136
132
indexGraphEdges.indices.map(reverseGraphMap.getOrElse(_, Array [Int ]())).toArray
137
133
134
+ SpanningForest .apply(reverseGraphEdges, nodesWithChangedHashes, false )
135
+ }
136
+
137
+ /**
138
+ * Computes the minimal spanning forest of the that covers the nodes in the
139
+ * call graph whose transitive call graph hashes has changed since the last
140
+ * run, rendered as a JSON dictionary tree. This provides a great "debug
141
+ * view" that lets you easily Cmd-F to find a particular node and then trace
142
+ * it up the JSON hierarchy to figure out what upstream node was the root
143
+ * cause of the change in the callgraph.
144
+ *
145
+ * There are typically multiple possible spanning forests for a given graph;
146
+ * one is chosen arbitrarily. This is usually fine, since when debugging you
147
+ * typically are investigating why there's a path to a node at all where none
148
+ * should exist, rather than trying to fully analyse all possible paths
149
+ */
150
+ def spanningInvalidationTree (
151
+ prevTransitiveCallGraphHashes : Map [String , Int ],
152
+ transitiveCallGraphHashes0 : Array [(CallGraphAnalysis .Node , Int )],
153
+ indexToNodes : Array [Node ],
154
+ indexGraphEdges : Array [Array [Int ]]
155
+ ): ujson.Obj = {
138
156
SpanningForest .spanningTreeToJsonTree(
139
- SpanningForest .apply(reverseGraphEdges, nodesWithChangedHashes, false ),
157
+ getSpanningForest(prevTransitiveCallGraphHashes, transitiveCallGraphHashes0, indexToNodes, indexGraphEdges ),
140
158
k => indexToNodes(k).toString
141
159
)
142
160
}
143
161
162
+ /**
163
+ * Get all class names that have their hashcode changed compared to prevTransitiveCallGraphHashes
164
+ */
165
+ def invalidClassNames (
166
+ prevTransitiveCallGraphHashes : Map [String , Int ],
167
+ transitiveCallGraphHashes0 : Array [(CallGraphAnalysis .Node , Int )],
168
+ indexToNodes : Array [Node ],
169
+ indexGraphEdges : Array [Array [Int ]]
170
+ ): Set [String ] = {
171
+ val rootNode = getSpanningForest(prevTransitiveCallGraphHashes, transitiveCallGraphHashes0, indexToNodes, indexGraphEdges)
172
+
173
+ val jsonValueQueue = mutable.ArrayDeque [(Int , SpanningForest .Node )]()
174
+ jsonValueQueue.appendAll(rootNode.values.toSeq)
175
+ val invalidClassNames = Set .newBuilder[String ]
176
+
177
+ while (jsonValueQueue.nonEmpty) {
178
+ val (nodeIndex, node) = jsonValueQueue.removeHead()
179
+ node.values.foreach { case (childIndex, childNode) =>
180
+ jsonValueQueue.append((childIndex, childNode))
181
+ }
182
+ indexToNodes(nodeIndex) match {
183
+ case CallGraphAnalysis .LocalDef (methodDef) => invalidClassNames.addOne(methodDef.cls.name)
184
+ case CallGraphAnalysis .Call (methodCall) => invalidClassNames.addOne(methodCall.cls.name)
185
+ case CallGraphAnalysis .ExternalClsCall (externalCls) => invalidClassNames.addOne(externalCls.name)
186
+ }
187
+ }
188
+
189
+ invalidClassNames.result()
190
+ }
191
+
144
192
def indexGraphEdges (
145
193
indexToNodes : Array [Node ],
146
194
methods : Map [MethodDef , LocalSummary .MethodInfo ],
0 commit comments