Repository navigation
fix(conditions): extend Fn::Equals mutual-exclusion beyond AllowedValues parameters - #4805
Conversation
|
Hi, the [CI] Test PR run on this PR is parked waiting for a maintainer approval (first-time-contributor gate). Could someone approve it when you get a chance? Thanks. |
17af75b to
a47b18f
Compare
|
Hi @kddejong, the [CI] Test PR run here is still parked waiting for a maintainer approval (first-time-contributor gate) after my follow-up push, and your earlier approval now sits on a superseded commit. Could you approve the workflow run when you get a chance? Thanks. |
5deaa0c to
4354ec9
Compare
…ues parameters The pairwise NAND added in aws-cloudformation#4464 lived inside a loop that skipped parameters without AllowedValues, and pseudo-parameters never entered it at all. Two Fn::Equals comparing the same single-valued Ref to different static values can never both be true, so add the constraint for every Ref, matching the template-level engine. This stops E3014 from validating the impossible scenario where both conditions hold and reporting a spurious requiredXor. Fixes aws-cloudformation#4799
The windows unitlint job's pre-commit ruff-format hook failed on this file. Formatting-only change; no test logic touched. Signed-off-by: Varshith Puli <pulivarshit@gmail.com>
4354ec9 to
5b2f5f8
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4805 +/- ##
=======================================
Coverage 94.67% 94.68%
=======================================
Files 439 439
Lines 15995 16004 +9
Branches 3104 3108 +4
=======================================
+ Hits 15144 15153 +9
Misses 468 468
Partials 383 383
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A condition's `equals` includes the Fn::Equals of every condition it references, so the mutual-exclusion loop added in aws-cloudformation#4805 visited the same Fn::Equals once per referrer and re-emitted its exclusion clauses each time. The clause count grew quadratically in the number of references rather than in the number of distinct Fn::Equals. Skipping an Fn::Equals whose hash has already been paired is logically a no-op: `Equal.cnf` is `Symbol(hash)`, so a repeat visit produces a clause sympy has already been given. On the template from aws-cloudformation#4984 (30 Fn::Equals on AWS::Region, 30 Fn::Or groups of 10) this takes the CNF from 53,025 clauses to 825 and the lint from 17.4s to 1.3s, with identical output. Fixes aws-cloudformation#4984
#4987) * fix(conditions): pair each Fn::Equals once when building condition CNF A condition's `equals` includes the Fn::Equals of every condition it references, so the mutual-exclusion loop added in #4805 visited the same Fn::Equals once per referrer and re-emitted its exclusion clauses each time. The clause count grew quadratically in the number of references rather than in the number of distinct Fn::Equals. Skipping an Fn::Equals whose hash has already been paired is logically a no-op: `Equal.cnf` is `Symbol(hash)`, so a repeat visit produces a clause sympy has already been given. On the template from #4984 (30 Fn::Equals on AWS::Region, 30 Fn::Or groups of 10) this takes the CNF from 53,025 clauses to 825 and the lint from 17.4s to 1.3s, with identical output. Fixes #4984 * refactor(conditions): share one distinct Fn::Equals map across both loops The AllowedValues loop walked the same conditions x equals product and re-visited each Fn::Equals once per referrer, appending duplicate symbols into equals_cnfs. That one was harmless -- sympy's Or is idempotent, so the duplicates collapsed to a single clause -- but it is the same latent shape as the clause blowup, so give it the same guard. Hoist the dedupe into one distinct_equals map built before both loops, mirroring the shared equal_vars dict in the template-level engine (cfnlint/conditions/conditions.py) rather than keeping a separate guard per loop. Also hoist the per-parameter Ref hash out of the inner loop. Behaviour is unchanged: insertion order preserves the previous first-seen iteration order, so the NAND pairs are emitted in the same order, and the AllowedValues coverage check is unaffected because two distinct Fn::Equals on one Ref always carry different values.
Fixes #4799
Bug
E3014 (
requiredXor) fires a false positive when a resource property is guarded by two mutually exclusiveFn::Equalsconditions, unless the compared parameter declaresAllowedValues:UseIpv4andUseIpv6can never both be true for any value ofSource, yet the property-validation engine explored the both-true scenario and reportedrequiredXoron the ingress rule. The template-level engine (cfnlint/conditions/conditions.py) already excluded that scenario, so the two engines disagreed.Root cause
The pairwise NAND that #4464 added in
context/conditions/_conditions.py::create_from_instancesits inside a loop over template parameters thatcontinues past any parameter withoutAllowedValues, and pseudo-parameters (AWS::Region,AWS::AccountId, ...) never enter the loop at all.Fix
The NAND is now collected in a parameter-independent pass: any two
Fn::Equalscomparing the same single-valued reference (grouped by the right-hand side hash, which theEqualsort normalizes to theRefdict) to different static strings are constrained to never both be true. This is sound for parameters with or withoutAllowedValuesand for pseudo-parameters, since aRefresolves to exactly one value. TheAllowedValuesexhaustion rule (Or(...)when every allowed value is covered) is preserved unchanged.Test evidence
test_mutually_exclusive_equals_without_allowed_values): both-true raisesUnsatisfiablefor a plain parameter and forAWS::AccountId, while each condition stays individually reachable.test/fixtures/templates/good/conditions/e3014_exclusive_conditions.yaml, the issue's template A) plus atest_api.pycase asserting zero matches.test/unit/module/context/conditions+test/unit/module/conditions(86 passed),test/unit/rules(1999 passed),test/unit/module(1012 passed; 4 failures inconfig/test_cli_args.pyandmaintenance/test_update_specs_from_pricing.pyare pre-existing on unmodified main and unrelated).