Fix #13084 follow-up: fix mutable map leak in compat sandbox context, pre-filter missing()-condition profiles in CONSUMER_DEPENDENCY builds - #13164
Conversation
…ntext, pre-filter missing()-condition profiles in CONSUMER_DEPENDENCY builds Forward-port of findings from the review of PR apache#13158 (4.0.x backport): - compat: externalActivationContext() returned mutable map from getSystemProperties() on both the empty-projectProps branch (raw delegate.getSystemProperties()) and the non-empty branch (unwrapped HashMap). Wrap both with Collections.unmodifiableMap(). - impl: the nonFileProfiles pre-filter only excluded profiles with file activation. A condition profile using missing(path) passes the filter and enters the sandbox where context.exists() always returns false, so missing() = !false = true fires unconditionally — the same footgun as <file><missing>. Add hasFileConditionExpression() helper and extend the filter to also exclude condition profiles containing exists()/missing(). Tests: extended testProjectBuildEvaluatesAllActivators (missing-condition fires in project build) and testDependencyModelActivatesOnlyEnvironmentIndependentProfiles (missing-condition suppressed in CONSUMER_DEPENDENCY).
gnodet-bot
left a comment
There was a problem hiding this comment.
Both fixes are correct and well-reasoned.
compat — mutable map leak fix: The lazy-init on the anonymous class (mergedSystemProperties field) is single-threaded per instance — externalActivationContext() creates a new anonymous object per call, so the non-volatile field is safe. The Collections.unmodifiableMap() wrapping on the empty-projectProps branch closes the last mutable-map exposure. Straightforward.
impl — missing()-condition pre-filter: The design is correct. withoutUserPropertiesAndFilesystem() returns exists()=false as a safety net, but that alone incorrectly activates missing(path) profiles (since missing = !false = true). The hasFileConditionExpression() pre-filter eliminates these profiles before they reach the sandbox, which is the right fix. The substring check (contains("exists(") / contains("missing(")) is a conservative over-approximation — false positives (filtering profiles that don't strictly need it) are safe. The compat layer omitting this check is correct: Maven 3 Activation has no <condition> element, so there are no condition-based profiles to worry about there.
Tests: The missing-condition profile added to the fixture exercises both code paths (fires in BUILD_PROJECT, suppressed in CONSUMER_DEPENDENCY). Assertions are specific and accurate.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
Forward-port of findings from the review of #13158 (4.0.x backport).
Related: #13163 (Windows glob fix for
exists()/missing()conditions, also targets master).Changes
compat — mutable map leak in
externalActivationContext()The lazy
getSystemProperties()implementation returned a mutable map in both branches:delegate.getSystemProperties()HashMapA caller who mutates the underlying system-properties map after creating the sandbox would see those changes inside, silently violating the "system wins over model properties" invariant. Fixed by wrapping both branches with
Collections.unmodifiableMap().impl —
missing()condition profiles incorrectly activate inCONSUMER_DEPENDENCYbuildsThe
nonFileProfilespre-filter excluded profiles withactivation.file != nullbut not condition profiles whose expression callsmissing(path). Inside the sandbox,context.exists()always returnsfalse, somissing(path) = !false = truefires unconditionally — the same footgun as<file><missing>that the file pre-filter was introduced to prevent.Fix: add
hasFileConditionExpression()helper (checks forexists(/missing(in<condition>) and extend the filter to also exclude such profiles.Tests
Added
missing-conditionprofile toresolved-model-with-profiles.xml:BUILD_PROJECT(path genuinely absent — normal behaviour)CONSUMER_DEPENDENCY(pre-filtered byhasFileConditionExpression)