Skip to content

Commit

Permalink
FindingsMatcher: Add a function to associate exceptions by licenses
Browse files Browse the repository at this point in the history
E.g. ScanCode reports exceptions to licenses as individual license
findings. That is problematic as exceptions on their own are not valid
SPDX expressions, also see [1].

Introduce a new function that fixes up findings by associating
exceptions by their belonging licenses.

[1]: aboutcode-org/scancode-toolkit#2873

Signed-off-by: Sebastian Schuberth <sebastian.schuberth@bosch.io>
  • Loading branch information
sschuberth committed Feb 23, 2022
1 parent f09ee99 commit aac49f8
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
62 changes: 62 additions & 0 deletions model/src/main/kotlin/utils/FindingsMatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import kotlin.math.min
import org.ossreviewtoolkit.model.CopyrightFinding
import org.ossreviewtoolkit.model.LicenseFinding
import org.ossreviewtoolkit.model.TextLocation
import org.ossreviewtoolkit.utils.spdx.SpdxConstants.NOASSERTION
import org.ossreviewtoolkit.utils.spdx.SpdxLicenseException
import org.ossreviewtoolkit.utils.spdx.toSpdx

/**
* A class for matching copyright findings to license findings. Copyright statements may be matched either to license
Expand Down Expand Up @@ -204,3 +207,62 @@ private fun MutableMap<LicenseFinding, MutableSet<CopyrightFinding>>.merge(
getOrPut(licenseFinding) { mutableSetOf() } += copyrightFindings
}
}

/**
* Process [findings] for stand-alone license exceptions and associate them with nearby (according to [toleranceLines])
* applicable licenses. Orphan license exceptions will get associated by [NOASSERTION]. Return the list of resulting
* findings.
*/
fun associateLicensesWithExceptions(
findings: List<LicenseFinding>,
toleranceLines: Int = FindingsMatcher.DEFAULT_TOLERANCE_LINES
): List<LicenseFinding> {
val (exceptions, licenses) = findings.partition { SpdxLicenseException.forId(it.license.toString()) != null }

val remainingExceptions = exceptions.toMutableList()
val fixedLicenses = licenses.toMutableList()

val i = remainingExceptions.iterator()

while (i.hasNext()) {
val exception = i.next()

// Determine all licenses exception is applicable to.
val applicableLicenses = SpdxLicenseException.mapping[exception.license.toString()].orEmpty().map { it.id }

// Determine applicable license findings from the same path.
val applicableLicenseFindings = licenses.filter {
it.location.path == exception.location.path && it.license.toString() in applicableLicenses
}

// Find the closest license within the tolerance.
val associatedLicenseFinding = applicableLicenseFindings
.map { it to it.location.distanceTo(exception.location) }
.sortedBy { it.second }
.firstOrNull { it.second <= toleranceLines }
?.first

if (associatedLicenseFinding != null) {
// Add the fixed-up license with the exception.
fixedLicenses += associatedLicenseFinding.copy(
license = "${associatedLicenseFinding.license} WITH ${exception.license}".toSpdx(),
location = associatedLicenseFinding.location.copy(
startLine = min(associatedLicenseFinding.location.startLine, exception.location.startLine),
endLine = max(associatedLicenseFinding.location.endLine, exception.location.endLine)
)
)

// Remove the original license and the stand-alone exception.
fixedLicenses.remove(associatedLicenseFinding)
i.remove()
}
}

// Associate remaining "orphan" exceptions with "NOASSERTION" to turn them into valid SPDX expressions and at the
// same time "marking" them for review as "NOASSERTION" is not a real license.
remainingExceptions.mapTo(fixedLicenses) { exception ->
exception.copy(license = "$NOASSERTION WITH ${exception.license}".toSpdx())
}

return fixedLicenses
}
39 changes: 39 additions & 0 deletions model/src/test/kotlin/utils/FindingsMatcherTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,44 @@ class FindingsMatcherTest : WordSpec() {
result.getCopyrights("root-license-1").map { it.statement } should containExactly("statement 1")
}
}

"associateLicensesWithExceptions()" should {
"merge with the nearest license" {
associateLicensesWithExceptions(
listOf(
LicenseFinding("Apache-2.0", TextLocation("file", 1)),
LicenseFinding("Apache-2.0", TextLocation("file", 100)),
LicenseFinding("LLVM-exception", TextLocation("file", 5))
)
) should containExactlyInAnyOrder(
LicenseFinding("Apache-2.0 WITH LLVM-exception", TextLocation("file", 1, 5)),
LicenseFinding("Apache-2.0", TextLocation("file", 100))
)
}

"associate orphan exceptions by NOASSERTION" {
associateLicensesWithExceptions(
listOf(
LicenseFinding("GPL-2.0-only", TextLocation("file", 1)),
LicenseFinding("389-exception", TextLocation("file", 100))
)
) should containExactlyInAnyOrder(
LicenseFinding("GPL-2.0-only", TextLocation("file", 1)),
LicenseFinding("NOASSERTION WITH 389-exception", TextLocation("file", 100))
)
}

"not associate findings from different files" {
associateLicensesWithExceptions(
listOf(
LicenseFinding("Apache-2.0", TextLocation("fileA", 1)),
LicenseFinding("LLVM-exception", TextLocation("fileB", 5))
)
) should containExactlyInAnyOrder(
LicenseFinding("Apache-2.0", TextLocation("fileA", 1)),
LicenseFinding("NOASSERTION WITH LLVM-exception", TextLocation("fileB", 5))
)
}
}
}
}

0 comments on commit aac49f8

Please sign in to comment.