Skip to content

Commit bfe7ed0

Browse files
authored
Add logical locations to SARIF reports (#1456)
1 parent 0faf1b5 commit bfe7ed0

File tree

5 files changed

+223
-63
lines changed

5 files changed

+223
-63
lines changed

utbot-framework-test/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class SarifReportTest {
4444
)
4545
Mockito.`when`(mockUtExecutionAIOBE.stateBefore.parameters).thenReturn(listOf())
4646

47+
defaultMockCoverage(mockUtExecutionNPE)
48+
defaultMockCoverage(mockUtExecutionAIOBE)
49+
4750
val testSets = listOf(
4851
UtMethodTestSet(mockExecutableId, listOf(mockUtExecutionNPE)),
4952
UtMethodTestSet(mockExecutableId, listOf(mockUtExecutionAIOBE))
@@ -68,12 +71,13 @@ class SarifReportTest {
6871
)
6972
Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf())
7073
Mockito.`when`(mockUtExecution.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(1337)
74+
Mockito.`when`(mockUtExecution.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main")
7175
Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException")
7276

7377
val report = sarifReportMain.createReport()
7478

7579
val result = report.runs.first().results.first()
76-
val location = result.locations.first().physicalLocation
80+
val location = result.locations.filterIsInstance<SarifPhysicalLocationWrapper>().first().physicalLocation
7781
val relatedLocation = result.relatedLocations.first().physicalLocation
7882

7983
assert(location.artifactLocation.uri.contains("Main.java"))
@@ -98,6 +102,8 @@ class SarifReportTest {
98102
)
99103
)
100104

105+
defaultMockCoverage(mockUtExecution)
106+
101107
val report = sarifReportMain.createReport()
102108

103109
val result = report.runs.first().results.first()
@@ -121,6 +127,8 @@ class SarifReportTest {
121127
)
122128
Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf())
123129

130+
defaultMockCoverage(mockUtExecution)
131+
124132
val report = sarifReportMain.createReport()
125133

126134
val result = report.runs.first().results.first().codeFlows.first().threadFlows.first().locations.map {
@@ -140,6 +148,8 @@ class SarifReportTest {
140148
Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf())
141149
Mockito.`when`(mockUtExecution.result).thenReturn(UtSandboxFailure(uncheckedException))
142150

151+
defaultMockCoverage(mockUtExecution)
152+
143153
val report = sarifReportMain.createReport()
144154
val result = report.runs.first().results.first()
145155
assert(result.message.text.contains("AccessControlException"))
@@ -159,6 +169,8 @@ class SarifReportTest {
159169
Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf())
160170
Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException")
161171

172+
defaultMockCoverage(mockUtExecution)
173+
162174
val report = sarifReportMain.createReport()
163175

164176
val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map {
@@ -183,6 +195,8 @@ class SarifReportTest {
183195
Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf())
184196
Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException")
185197

198+
defaultMockCoverage(mockUtExecution)
199+
186200
val report = sarifReportPrivateMain.createReport()
187201

188202
val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map {
@@ -200,6 +214,8 @@ class SarifReportTest {
200214
val mockUtExecution = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS)
201215
Mockito.`when`(mockUtExecution.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false))
202216

217+
defaultMockCoverage(mockUtExecution)
218+
203219
val testSets = listOf(
204220
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution)),
205221
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution)) // duplicate
@@ -225,6 +241,9 @@ class SarifReportTest {
225241
Mockito.`when`(mockUtExecution1.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false))
226242
Mockito.`when`(mockUtExecution2.result).thenReturn(UtImplicitlyThrownException(ArithmeticException(), false))
227243

244+
defaultMockCoverage(mockUtExecution1)
245+
defaultMockCoverage(mockUtExecution2)
246+
228247
val testSets = listOf(
229248
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)),
230249
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution2)) // not a duplicate
@@ -252,7 +271,9 @@ class SarifReportTest {
252271

253272
// different locations
254273
Mockito.`when`(mockUtExecution1.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(11)
274+
Mockito.`when`(mockUtExecution1.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main")
255275
Mockito.`when`(mockUtExecution2.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(22)
276+
Mockito.`when`(mockUtExecution2.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main")
256277

257278
val testSets = listOf(
258279
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)),
@@ -288,6 +309,9 @@ class SarifReportTest {
288309
Mockito.`when`(mockNPE1.stackTrace).thenReturn(arrayOf(stackTraceElement1))
289310
Mockito.`when`(mockNPE2.stackTrace).thenReturn(arrayOf(stackTraceElement1, stackTraceElement2))
290311

312+
defaultMockCoverage(mockUtExecution1)
313+
defaultMockCoverage(mockUtExecution2)
314+
291315
val testSets = listOf(
292316
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)),
293317
UtMethodTestSet(mockExecutableId, listOf(mockUtExecution2)) // duplicate with a longer stack trace
@@ -316,6 +340,11 @@ class SarifReportTest {
316340
Mockito.`when`(mockExecutableId.classId.name).thenReturn("Main")
317341
}
318342

343+
private fun defaultMockCoverage(mockExecution: UtExecution) {
344+
Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(1)
345+
Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main")
346+
}
347+
319348
// constants
320349

321350
private val sourceFindingEmpty = SourceFindingStrategyDefault(

utbot-framework/src/main/kotlin/org/utbot/sarif/DataClasses.kt

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.utbot.sarif
22

3-
import com.fasterxml.jackson.annotation.JsonInclude
4-
import com.fasterxml.jackson.annotation.JsonProperty
5-
import com.fasterxml.jackson.annotation.JsonValue
3+
import com.fasterxml.jackson.annotation.*
4+
import com.fasterxml.jackson.core.JsonParser
5+
import com.fasterxml.jackson.databind.DeserializationContext
6+
import com.fasterxml.jackson.databind.JsonDeserializer
7+
import com.fasterxml.jackson.databind.JsonNode
8+
import com.fasterxml.jackson.databind.module.SimpleModule
69
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
710
import com.fasterxml.jackson.module.kotlin.readValue
811

@@ -17,6 +20,12 @@ data class Sarif(
1720
val runs: List<SarifRun>
1821
) {
1922
companion object {
23+
24+
private val jsonMapper = jacksonObjectMapper()
25+
.registerModule(SimpleModule()
26+
.addDeserializer(SarifLocationWrapper::class.java, SarifLocationWrapperDeserializer())
27+
)
28+
2029
private const val defaultSchema =
2130
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
2231
private const val defaultVersion =
@@ -29,11 +38,11 @@ data class Sarif(
2938
Sarif(defaultSchema, defaultVersion, listOf(run))
3039

3140
fun fromJson(reportInJson: String): Sarif =
32-
jacksonObjectMapper().readValue(reportInJson)
41+
jsonMapper.readValue(reportInJson)
3342
}
3443

3544
fun toJson(): String =
36-
jacksonObjectMapper()
45+
jsonMapper
3746
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
3847
.writerWithDefaultPrettyPrinter()
3948
.writeValueAsString(this)
@@ -111,7 +120,7 @@ data class SarifResult(
111120
val ruleId: String,
112121
val level: Level,
113122
val message: Message,
114-
val locations: List<SarifPhysicalLocationWrapper> = listOf(),
123+
val locations: List<SarifLocationWrapper> = listOf(),
115124
val relatedLocations: List<SarifRelatedPhysicalLocationWrapper> = listOf(),
116125
val codeFlows: List<SarifCodeFlow> = listOf()
117126
) {
@@ -145,14 +154,41 @@ data class Message(
145154
val markdown: String? = null
146155
)
147156

148-
// physical location
157+
// location
149158

150159
/**
151160
* [Documentation](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#location-object)
152161
*/
162+
sealed class SarifLocationWrapper
163+
153164
data class SarifPhysicalLocationWrapper(
154-
val physicalLocation: SarifPhysicalLocation,
155-
)
165+
val physicalLocation: SarifPhysicalLocation // this name used in the SarifLocationWrapperDeserializer
166+
) : SarifLocationWrapper()
167+
168+
data class SarifLogicalLocationsWrapper(
169+
val logicalLocations: List<SarifLogicalLocation> // this name used in the SarifLocationWrapperDeserializer
170+
) : SarifLocationWrapper()
171+
172+
/**
173+
* Custom JSON deserializer for sealed class [SarifLocationWrapper].
174+
* Returns [SarifPhysicalLocationWrapper] or [SarifLogicalLocationsWrapper].
175+
*/
176+
class SarifLocationWrapperDeserializer : JsonDeserializer<SarifLocationWrapper>() {
177+
override fun deserialize(jp: JsonParser, context: DeserializationContext?): SarifLocationWrapper {
178+
val node: JsonNode = jp.codec.readTree(jp)
179+
val isPhysicalLocation = node.get("physicalLocation") != null // field name
180+
val isLogicalLocations = node.get("logicalLocations") != null // field name
181+
return when {
182+
isPhysicalLocation -> {
183+
jacksonObjectMapper().readValue<SarifPhysicalLocationWrapper>(node.toString())
184+
}
185+
isLogicalLocations -> {
186+
return jacksonObjectMapper().readValue<SarifLogicalLocationsWrapper>(node.toString())
187+
}
188+
else -> error("SarifLocationWrapperDeserializer: Cannot parse ${node.toPrettyString()}")
189+
}
190+
}
191+
}
156192

157193
/**
158194
* [Documentation](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#physicallocation-object)
@@ -189,6 +225,15 @@ data class SarifRegion(
189225
}
190226
}
191227

228+
// logical locations
229+
230+
/**
231+
* [Documentation](https://github.com/microsoft/sarif-tutorials/blob/main/docs/2-Basics.md#physical-and-logical-locations)
232+
*/
233+
data class SarifLogicalLocation(
234+
val fullyQualifiedName: String
235+
)
236+
192237
// related locations
193238

194239
/**

0 commit comments

Comments
 (0)