-
Notifications
You must be signed in to change notification settings - Fork 360
spdx-utils: Fix license choice order-dependence in compound expressions #11101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
spdx-utils: Fix license choice order-dependence in compound expressions #11101
Conversation
a1d9a52 to
3296ba8
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11101 +/- ##
============================================
+ Coverage 57.38% 57.45% +0.06%
- Complexity 1703 1708 +5
============================================
Files 346 346
Lines 12825 12844 +19
Branches 1214 1218 +4
============================================
+ Hits 7360 7379 +19
Misses 4997 4997
Partials 468 468
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
52b1218 to
1666e6e
Compare
sschuberth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically LGTM, let me make some slight adjustments straight to your branch to get it merged soon.
| this != subExpression && | ||
| children.any { it.isSubExpression(subExpression) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so these two lines seem to be the only different to the solution in my branch (for I created a draft PR now for easier comparison).
|
|
||
| "apply choice regardless of license order in OR expression" { | ||
| val expression = "(GPL-2.0-only OR MIT) AND Apache-2.0".toSpdx() | ||
|
|
||
| val choices1 = listOf( | ||
| SpdxLicenseChoice("GPL-2.0-only OR MIT".toSpdx(), "MIT".toSpdx()) | ||
| ) | ||
| val choices2 = listOf( | ||
| SpdxLicenseChoice("MIT OR GPL-2.0-only".toSpdx(), "MIT".toSpdx()) | ||
| ) | ||
|
|
||
| val result1 = expression.applyChoices(choices1) | ||
| result1 shouldBe "MIT AND Apache-2.0".toSpdx() | ||
|
|
||
| val result2 = expression.applyChoices(choices2) | ||
| result2 shouldBe "MIT AND Apache-2.0".toSpdx() | ||
| } | ||
|
|
||
| "resolve the choice for a sub-expression in a compound expression" { | ||
| // This test ensures that when applying a choice to a subexpression that appears multiple times, | ||
| // only the matching occurrence is replaced, not similar-looking expressions with WITH exceptions. | ||
| // This prevents the over-simplification bug where string replacement would affect too much. | ||
| val expression = "(CDDL-1.1 OR GPL-2.0-only) AND (CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0)" | ||
| .toSpdx() | ||
| val choices = listOf( | ||
| SpdxLicenseChoice("CDDL-1.1 OR GPL-2.0-only".toSpdx(), "CDDL-1.1".toSpdx()) | ||
| ) | ||
|
|
||
| val result = expression.applyChoices(choices) | ||
|
|
||
| // The second OR expression should remain unchanged because it has a WITH exception modifier | ||
| // and doesn't exactly match the subexpression being chosen. | ||
| result shouldBe "CDDL-1.1 AND (CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0)".toSpdx() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests are new as well, but in exchange mine is omitted.
ORT had an architectural mismatch where `SpdxCompoundExpression` used
order-invariant equality ("MIT OR Apache-2.0" == "Apache-2.0 OR
MIT"), but the license choice matching used order-dependent matching
based on strings.
That is, if the expression contained "MIT OR Apache-2.0" but the user
gave "Apache-2.0 OR MIT", the string match failed, triggering a
fallback that rebuilt the expression by OR-ing all valid choices.
With multiple license choices, this caused exponential expression
growth (> 1000 characters in length).
This solution replaces the string-based algorithm with set-based
operations in `replaceSubexpressionWithChoice()`:
1. Decompose expressions into a set using `validChoices()`.
2. Remove dismissed choices from the set (no string operations).
3. Rebuild from remaining choices with controlled reduction.
4. Only recurse to AND-children if the sub-expression is actually
contained in a child to prevent inappropriate recursion for the
entire expression or derived sub-expression choices.
Since sets are order-invariant ({A,B} == {B,A}), both orderings
execute identical code paths, guaranteeing deterministic behavior.
Fixes oss-review-toolkit#10888.
Signed-off-by: Antoni <168914426+cow-lang@users.noreply.github.com>
Co-authored-by: Sebastian Schuberth <sebastian@doubleopen.org>
1666e6e to
63242d9
Compare
|
Merging despite the unrelated test failure(s). |
ORT has a bug where license choice application is non-deterministic depending on the order of licenses in SPDX expressions. When "MIT OR Apache-2.0" appears in the scan result but the user configured "Apache-2.0 OR MIT", string matching fails, triggering a fallback that causes exponential expression growth.
Fixes: #10888