@@ -4,6 +4,7 @@ import org.utbot.common.PathUtil.fileExtension
4
4
import org.utbot.common.PathUtil.toPath
5
5
import org.utbot.framework.UtSettings
6
6
import org.utbot.framework.plugin.api.*
7
+ import java.nio.file.Path
7
8
import kotlin.io.path.nameWithoutExtension
8
9
9
10
/* *
@@ -27,6 +28,41 @@ class SarifReport(
27
28
reports.fold(Sarif .empty()) { sarif: Sarif , report: String ->
28
29
sarif.copy(runs = sarif.runs + Sarif .fromJson(report).runs)
29
30
}.toJson()
31
+
32
+ /* *
33
+ * Minimizes SARIF results between several reports.
34
+ *
35
+ * More complex version of the [SarifReport.minimizeResults].
36
+ */
37
+ fun minimizeSarifResults (srcPathToSarif : MutableMap <Path , Sarif >): MutableMap <Path , Sarif > {
38
+ val pathToSarifResult = srcPathToSarif.entries.flatMap { (path, sarif) ->
39
+ sarif.getAllResults().map { sarifResult ->
40
+ path to sarifResult
41
+ }
42
+ }
43
+ val groupedResults = pathToSarifResult.groupBy { (_, sarifResult) ->
44
+ sarifResult.ruleId to sarifResult.locations
45
+ }
46
+ val minimizedResults = groupedResults.map { (_, sarifResultsGroup) ->
47
+ sarifResultsGroup.minByOrNull { (_, sarifResult) ->
48
+ sarifResult.totalCodeFlowLocations()
49
+ }!!
50
+ }
51
+ val groupedByPath = minimizedResults
52
+ .groupBy { (path, _) -> path }
53
+ .mapValues { (_, resultsWithPath) ->
54
+ resultsWithPath.map { (_, sarifResult) -> sarifResult } // remove redundant path
55
+ }
56
+ val pathToSarifTool = srcPathToSarif.mapValues { (_, sarif) ->
57
+ sarif.runs.first().tool
58
+ }
59
+ val paths = pathToSarifTool.keys intersect groupedByPath.keys
60
+ return paths.associateWith { path ->
61
+ val sarifTool = pathToSarifTool[path]!!
62
+ val sarifResults = groupedByPath[path]!!
63
+ Sarif .fromRun(SarifRun (sarifTool, sarifResults))
64
+ }.toMutableMap()
65
+ }
30
66
}
31
67
32
68
/* *
@@ -67,6 +103,8 @@ class SarifReport(
67
103
*/
68
104
private val relatedLocationId = 1 // for attaching link to generated test in related locations
69
105
106
+ private val stackTraceLengthForStackOverflow = 50 // stack overflow error may have too many elements
107
+
70
108
/* *
71
109
* Minimizes detected errors and removes duplicates.
72
110
*
@@ -125,7 +163,7 @@ class SarifReport(
125
163
[Generated test for this case]($relatedLocationId )
126
164
""" .trimIndent()
127
165
),
128
- getLocations(utExecution, classFqn),
166
+ getLocations(method, utExecution, classFqn),
129
167
getRelatedLocations(utExecution),
130
168
getCodeFlows(method, utExecution, executionFailure)
131
169
)
@@ -146,16 +184,23 @@ class SarifReport(
146
184
return Pair (sarifResult, sarifRule)
147
185
}
148
186
149
- private fun getLocations (utExecution : UtExecution , classFqn : String? ): List <SarifPhysicalLocationWrapper > {
187
+ private fun getLocations (
188
+ method : ExecutableId ,
189
+ utExecution : UtExecution ,
190
+ classFqn : String?
191
+ ): List <SarifLocationWrapper > {
150
192
if (classFqn == null )
151
193
return listOf ()
152
- val sourceRelativePath = sourceFinding.getSourceRelativePath(classFqn)
153
- val startLine = getLastLineNumber(utExecution, classFqn) ? : defaultLineNumber
154
- val sourceCode = sourceFinding.getSourceFile(classFqn)?.readText() ? : " "
194
+ val (startLine, classWithErrorFqn) = getLastLineNumberWithClassFqn(method, utExecution, classFqn)
195
+ val sourceCode = sourceFinding.getSourceFile(classWithErrorFqn)?.readText() ? : " "
155
196
val sourceRegion = SarifRegion .withStartLine(sourceCode, startLine)
197
+ val sourceRelativePath = sourceFinding.getSourceRelativePath(classWithErrorFqn)
156
198
return listOf (
157
199
SarifPhysicalLocationWrapper (
158
200
SarifPhysicalLocation (SarifArtifact (sourceRelativePath), sourceRegion)
201
+ ),
202
+ SarifLogicalLocationsWrapper (
203
+ listOf (SarifLogicalLocation (classWithErrorFqn)) // class name without method name
159
204
)
160
205
)
161
206
}
@@ -181,31 +226,9 @@ class SarifReport(
181
226
utExecution : UtExecution ,
182
227
executionFailure : UtExecutionFailure
183
228
): List <SarifCodeFlow > {
184
- /* Example of a typical stack trace:
185
- - java.lang.Math.multiplyExact(Math.java:867)
186
- - com.abc.Util.multiply(Util.java:10)
187
- - com.abc.Util.multiply(Util.java:6)
188
- - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex`
189
- - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
190
- - ...
191
- */
192
- val stackTrace = executionFailure.exception.stackTrace
193
-
194
- val lastMethodCallIndex = stackTrace.indexOfLast {
195
- it.className == method.classId.name && it.methodName == method.name
196
- }
197
- if (lastMethodCallIndex == - 1 )
198
- return listOf ()
199
-
200
- val stackTraceFiltered = stackTrace
201
- .take(lastMethodCallIndex + 1 ) // taking all elements before the last `method` call
202
- .filter {
203
- ! it.className.startsWith(" org.utbot." ) // filter all internal calls
204
- }
205
-
206
- val stackTraceResolved = stackTraceFiltered.mapNotNull {
207
- findStackTraceElementLocation(it)
208
- }.toMutableList()
229
+ val stackTraceResolved = filterStackTrace(method, utExecution, executionFailure)
230
+ .mapNotNull { findStackTraceElementLocation(it) }
231
+ .toMutableList()
209
232
if (stackTraceResolved.isEmpty())
210
233
return listOf () // empty stack trace is not shown
211
234
@@ -235,6 +258,40 @@ class SarifReport(
235
258
)
236
259
}
237
260
261
+ private fun filterStackTrace (
262
+ method : ExecutableId ,
263
+ utExecution : UtExecution ,
264
+ executionFailure : UtExecutionFailure
265
+ ): List <StackTraceElement > {
266
+ /* Example of a typical stack trace:
267
+ - java.lang.Math.multiplyExact(Math.java:867)
268
+ - com.abc.Util.multiply(Util.java:10)
269
+ - com.abc.Util.multiply(Util.java:6)
270
+ - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex`
271
+ - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
272
+ - ...
273
+ */
274
+ var stackTrace = executionFailure.exception.stackTrace.toList()
275
+
276
+ val lastMethodCallIndex = stackTrace.indexOfLast {
277
+ it.className == method.classId.name && it.methodName == method.name
278
+ }
279
+ if (lastMethodCallIndex != - 1 ) {
280
+ // taking all elements before the last `method` call
281
+ stackTrace = stackTrace.take(lastMethodCallIndex + 1 )
282
+ }
283
+
284
+ if (executionFailure.exception is StackOverflowError ) {
285
+ stackTrace = stackTrace.takeLast(stackTraceLengthForStackOverflow)
286
+ }
287
+
288
+ val stackTraceFiltered = stackTrace.filter {
289
+ ! it.className.startsWith(" org.utbot." ) // filter all internal calls
290
+ }
291
+
292
+ return stackTraceFiltered
293
+ }
294
+
238
295
private fun findStackTraceElementLocation (stackTraceElement : StackTraceElement ): SarifFlowLocationWrapper ? {
239
296
val lineNumber = stackTraceElement.lineNumber
240
297
if (lineNumber < 1 )
@@ -327,15 +384,21 @@ class SarifReport(
327
384
}
328
385
329
386
/* *
330
- * Returns the number of the last line in the execution path which is located in the [classFqn].
387
+ * Returns the number of the last line in the execution path
388
+ * And the name of the class in which it is located.
331
389
*/
332
- private fun getLastLineNumber (utExecution : UtExecution , classFqn : String ): Int? {
333
- val classFqnPath = classFqn.replace(" ." , " /" )
390
+ private fun getLastLineNumberWithClassFqn (
391
+ method : ExecutableId ,
392
+ utExecution : UtExecution ,
393
+ defaultClassFqn : String
394
+ ): Pair <Int , String > {
334
395
val coveredInstructions = utExecution.coverage?.coveredInstructions
335
- val lastCoveredInstruction = coveredInstructions?.lastOrNull { it.className == classFqnPath }
336
- ? : coveredInstructions?.lastOrNull()
396
+ val lastCoveredInstruction = coveredInstructions?.lastOrNull()
337
397
if (lastCoveredInstruction != null )
338
- return lastCoveredInstruction.lineNumber
398
+ return Pair (
399
+ lastCoveredInstruction.lineNumber,
400
+ lastCoveredInstruction.className.replace(' /' , ' .' )
401
+ )
339
402
340
403
// if for some reason we can't extract the last line from the coverage
341
404
val lastPathElementLineNumber = try {
@@ -345,7 +408,20 @@ class SarifReport(
345
408
} catch (t: Throwable ) {
346
409
null
347
410
}
348
- return lastPathElementLineNumber
411
+ if (lastPathElementLineNumber != null ) {
412
+ return Pair (lastPathElementLineNumber, defaultClassFqn)
413
+ }
414
+
415
+ val methodDefinitionLine = getMethodDefinitionLineNumber(method)
416
+ return Pair (methodDefinitionLine ? : defaultLineNumber, defaultClassFqn)
417
+ }
418
+
419
+ private fun getMethodDefinitionLineNumber (method : ExecutableId ): Int? {
420
+ val sourceFile = sourceFinding.getSourceFile(method.classId.canonicalName)
421
+ val lineNumber = sourceFile?.readLines()?.indexOfFirst { line ->
422
+ line.contains(" ${method.name} (" ) // method definition
423
+ }
424
+ return if (lineNumber == null || lineNumber == - 1 ) null else lineNumber + 1 // to one-based
349
425
}
350
426
351
427
private fun shouldProcessExecutionResult (result : UtExecutionResult ): Boolean {
0 commit comments