Skip to content

fix(#1982): honour the D2B/B2D opt-out for dual-PCS profiles - #2292

Merged
xsscx merged 1 commit into
masterfrom
fix/issue-1982-dual-pcs-selection
Aug 24, 2026
Merged

fix(#1982): honour the D2B/B2D opt-out for dual-PCS profiles#2292
xsscx merged 1 commit into
masterfrom
fix/issue-1982-dual-pcs-selection

Conversation

@colourbill-ctrl

@colourbill-ctrl colourbill-ctrl commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Harvests the CIccXform::Create half of ci-qa-issue-1982 (ea41af37, @xsscx) and adds
a two-direction regression CTest. Part of #1982 — items 1 and 2; item 3 is already
implemented, and one behaviour question is left open for a maintainer (both below).
Deliberately not a closing keyword.

CIccXform::Create selected a spectral DToBx/BToDx transform whenever the profile
carried a spectralPCS, ignoring the caller's bUseD2BTags opt-out. Every other
space-selection site already tests !pcs as well — CIccCmm::AddXform at
IccCmm.cpp:9093/:9112 and CIccNamedColorCmm::AddXform at :11718/:11796/:11813
all read (bUseD2BxB2DxTags || !m_Header.pcs). For a profile carrying both a colorimetric
and a spectral PCS the sites disagreed: AddXform recorded the 3-sample XYZ connection
while Create returned a transform emitting 36 spectral samples, and CIccCmm::Begin()
rejected the chain.

Both Create lines were introduced together in 92ffea98 (#1018, hybrid spectral
profiles). The AddXform half already carried the !pcs term; the two Create sites did
not. With this change all seven sites agree.

Measured

Clang 21.1.3 Release, master f78e915a, Testing/SpecRef/SixChanInputRef.xml
(AToB3 6→3, DToB3 6→36, PCS=XYZ, SpectralPCS=rs0024):

iccApplyToLink link.icc 0 2 1 T 0 1 1 0 SixChanInputRef.icc 11
  before: exit 255, "status 2: Invalid space link"
  after:  exit 0,   PCS XYZData, "MPE Element Chain: 1 elements, 6->3 channels"

Same for intent 13. Both decode to icXformLutColor with bUseD2BxB2DxTags cleared.

Instrumented master, whole 222-test suite, one probe per decision site:

IN_FLIP tagIntent=1 lut=0            <- SixChanInputRef @ 11
ADDXFORM_MISMATCH src=6/6 dst=36/3   <- xform emits 36, CMM recorded 3

Why both sites in one commit

The two mis-selections cancel out. A chain whose source and destination are dual-PCS
resolves today because both ends silently take the spectral route. With only the input
site corrected:

SixChanInputRef 11 -> SixChanCameraRef 11
  master:          exit 0
  input site only: exit 255, "status 13: Unsupported PCS Link used"

#1018 is not regressed

CMYK_Hybrid_Profile's spectral sub-profile has an empty <PCS>, so !pcs holds and its
DToBx path stays open. iccdev.hybrid-pipeline passes: 43s Release, 494s ASan+UBSan.

The test

iccdev.issue-1982-dual-pcs-selection-regression, both directions.

Part 1 (source) asserts exit status and link shape. Part 2 (destination) cannot: the
chain builds a link either way, just the wrong one, so an exit-status check would be
vacuous. It builds the same chain twice — both ends opting out (11,11) and both opting
in (1,1) — and requires the two links to differ. On master they are byte-identical once
Creation Date / Profile ID / size / banner are stripped. Both arms succeed in both
builds, so this asserts the route and not an error.

Red/green:

tree result
master f78e915a FAIL (part 1)
input site only FAIL (part 2)
both sites PASS

No pre-existing test exercised the opt-out path at all. The only other script using a
tens-digit-1 intent code is iccdev-applytolink-nan-sixchan-debug.sh, and it feeds nan
as the range so it fails before AddXform. A green suite is therefore not the evidence
here — the three-way red/green above is. The test exits 77 with SKIP_RETURN_CODE when
the tools are absent, so a build that cannot run it reports Skipped rather than a green
pass.

Pre-flight

Base d1a7d42a. Dispatched ci-pr-action with ci_scope=source before opening this PR
per the suggested workflow: run
32743042807,
success, 12 jobs green / 3 skipped by scope — GCC 15.2 Strict Release LTO, Windows
MSVC + ClangCL, MinGW UCRT64, macOS Release + Debug, Tool Smoke Tests ASAN+UBSAN. On the
ASAN leg the new test ran as 149/220 Test #150 ... Passed 0.54 sec (100% tests passed, 0 failed out of 220); script CTests are not registered on the Windows leg, which
nonetheless configured the new registration under MSVC (129 tests, 100%).

Local lanes:

lane result
Clang 21.1.3 Release, -Wall -Wextra -Wpedantic -Werror, strict warnings ENABLED 0 warnings, ctest 223/224
GCC 15.2.0 in ghcr.io/internationalcolorconsortium/iccdev-ci-regression:latest, same flags + LTO, strict warnings ENABLED 0 warnings, build and build-test-binaries both rc=0
Clang ASan+UBSan Debug ctest 222/223, new test passes in 0.34s
detect_leaks=1 over the six affected iccApplyToLink paths, including the newly reachable ProfileMissingTag refusal no leaks, no findings (LeakSanitizer confirmed live against a deliberately leaky control)
shellcheck 0.9.0 rc=0

The one ctest failure in each run is iccdev.spectral-tiff-preview, which needs the
imagecodecs Python module and fails identically on pristine master.

One behaviour change worth a maintainer's eye

Testing/Calc/CameraModel.xml is dual-PCS with A2B3 + B2D3 and no B2A3. As a
destination under the opt-out it now returns icCmmStatProfileMissingTag where master
silently used the spectral B2D3. A sweep of all 221 tracked XML found only three
dual-PCS profiles carrying B2D tags, and CameraModel is used only for XML→ICC, dump and
writer-failure tests — never as a CMM destination — so nothing in the suite covers it
either way. Whether an opt-out should fall back to the spectral tag when no colorimetric
tag exists in that direction is a spec call, raised on #1982 rather than decided here.

Not harvested: the branch's third change

ci-qa-issue-1982 also adds a sample-count guard in CIccCmm::AddXform (issue item 3).
Left out, measured against the branch applied verbatim:

  1. CIccCmm::Begin() already does it. Its two GetNumSrcSamples()/GetNumDstSamples()
    comparisons against the CMM's counts carry the comment "Otherwise we'll have a heap
    overflow during Apply."
    That is exactly why the unfixed dual-PCS case fails closed with
    icCmmStatBadSpaceLink rather than overflowing.

  2. It fires twice in the 222-test suite and both tests still print PASS.

    test master with the guard
    iccdev.issue-1336-applytolink-pcc-regression fails on the second profile (fuzz-abst-a044ff69.icc, status 2) fails on the first (fuzz-mntr-ef352586.icc, status 4)
    iccdev.issue-1985-encoding-lumamtx-uaf status 2 status 4

    CI c0d0fc2: Indirect Leak in CIccProfile::ReadBasic() at IccProfile.cpp:1363 #1336 exists to prove a -PCC profile is released on the AddXform error path. Moving
    the failure to the first profile means that path is never reached, so the test goes
    vacuous while still reporting [PASS] — it only greps for a LeakSanitizer report. CIccDefaultEncProfileConverter::ConvertFromParams frees a borrowed colorEncodingParams element — heap use-after-free write, then double free (IccEncoding.cpp:259) #1985's
    own header documents the expected outcome as "the ordinary 'invalid space link'
    rejection"
    , which the guard changes.

  3. It is not a channel-count check in practice. icGetSpaceSamples() returns 0 for any
    signature it does not map, so the guard is really "reject any profile whose PCS signature
    is unrecognised or zero". Both firings were .../0 cases — nDstSpace=00000000, and
    nSrcSpace=F6BD2F3E from the fuzzed abstract profile — not genuine disagreements.

Reproduce: apply the guard and run ctest -R 'issue-1336|issue-1985'.

Checklist

  • Built locally with the documented build flow in docs/build.md
  • Ran relevant CTest/profile tests from docs/ctest.md
  • Added or updated regression coverage for behavior changes
  • Checked sanitizer coverage for memory-safety or parser changes
  • Updated documentation for user-visible behavior changes
  • Did not change maintainer-owned workflow, CTest, CPack, sanitizer, release, or security infrastructure unless requested by an iccDEV maintainer
  • New source files include the ICC copyright and BSD 3-Clause license header
  • Code style matches nearby code: 2-space indent, K&R braces, m_ members

@github-actions github-actions Bot added Testing CTest, regression, or test coverage Source C or C++ source code changes Scripts Shell, PowerShell, or repository automation scripts Configuration Repository, CMake, YAML, JSON, or tool configuration Build Build system, CMake, compiler, or packaging Unix Linux, macOS, Bash, or POSIX shell scope pending CI checks still running labels Aug 24, 2026

@xsscx xsscx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026-08-24 15:24:40 UTC

@xsscx xsscx self-assigned this Aug 24, 2026
@xsscx xsscx added this to the v2.3.2.4 milestone Aug 24, 2026
@xsscx xsscx linked an issue Aug 24, 2026 that may be closed by this pull request
@xsscx
xsscx requested a lite review from Copilot August 24, 2026 15:27
@xsscx xsscx added the Copilot Copilot use indicated by Maintainer label Aug 24, 2026
@xsscx

xsscx commented Aug 24, 2026

Copy link
Copy Markdown
Member

PR Note

2026-08-24 15:28:42 UTC

  • Running the Copilot Cloud Review Agent to check Reporting & Coverage

CIccXform::Create selected a spectral DToBx/BToDx transform whenever the
profile carried a spectralPCS, ignoring the caller's bUseD2BTags opt-out.
Every other space-selection site already tests !pcs as well:
CIccCmm::AddXform at IccCmm.cpp:9093/:9112 and CIccNamedColorCmm::AddXform
at :11718/:11796/:11813 all read (bUseD2BxB2DxTags || !m_Header.pcs).  So
for a profile carrying both a colorimetric and a spectral PCS the sites
disagreed - AddXform recorded the 3-sample XYZ connection while Create
returned a transform emitting 36 spectral samples, and CIccCmm::Begin()
rejected the chain outright.

Both Create lines were introduced together in 92ffea9 (#1018, hybrid
spectral profiles); the AddXform half already carried the !pcs term and
the Create half did not.  With this change all seven sites agree.

Measured on master f78e915, Clang 21.1.3 Release, with SixChanInputRef
(AToB3 6->3, DToB3 6->36):

  iccApplyToLink link.icc 0 2 1 T 0 1 1 0 SixChanInputRef.icc 11
    before: exit 255, "status 2: Invalid space link"
    after:  exit 0, XYZ PCS, "MPE Element Chain: 1 elements, 6->3 channels"

Same for intent 13.  Both intents decode to icXformLutColor with
bUseD2BxB2DxTags cleared, i.e. an explicit opt-out.

The output half has to land in the same commit.  The two mis-selections
currently cancel out: a chain whose source and destination are both
dual-PCS resolves today because both ends silently take the spectral
route.  With only the input site corrected, SixChanInputRef into
SixChanCameraRef under intent 11 regresses from exit 0 to
"status 13: Unsupported PCS Link used".

#1018 is not regressed.  CMYK_Hybrid_Profile's spectral sub-profile has
an empty PCS, so !pcs holds and its DToBx path stays open;
iccdev.hybrid-pipeline passes.

New CTest iccdev.issue-1982-dual-pcs-selection-regression covers both
directions.  Part 1 asserts exit status and link shape for the source
profile.  Part 2 has to be relative: the destination-side chain builds a
link either way, just the wrong one, so it generates the chain with both
ends opting out (11,11) and both opting in (1,1) and requires the two
links to differ.  On master the two are byte-identical once the volatile
header fields are stripped.  It exits 77 with SKIP_RETURN_CODE when the
tools are absent, so a build that cannot run it reports Skipped rather
than a green pass.

No pre-existing test exercised the opt-out path at all - the only other
script using a tens-digit-1 intent code is the NaN PoC, which fails at
range parsing before AddXform - so a green suite is not the evidence
here; the three-way red/green is:

  master f78e915          -> FAIL (part 1)
  input-site fix only      -> FAIL (part 2)
  both sites fixed         -> PASS

Pre-flight on f37dbb3:
  Clang 21.1.3 Release, -Wall -Wextra -Wpedantic -Werror, strict warnings
    ENABLED: 0 warnings, ctest 223/224
  GCC 15.2.0 in ghcr.io/internationalcolorconsortium/iccdev-ci-regression
    :latest, same flags + LTO, strict warnings ENABLED: 0 warnings,
    build and build-test-binaries both rc=0
  Clang ASan+UBSan Debug: ctest 222/223, new test passes in 0.34s
  detect_leaks=1 over the six affected iccApplyToLink paths, including
    the newly reachable ProfileMissingTag refusal: no leaks, no findings
The one failure in each ctest run is iccdev.spectral-tiff-preview, which
needs the imagecodecs Python module and fails identically on pristine
master.
@xsscx
xsscx force-pushed the fix/issue-1982-dual-pcs-selection branch from 179bc6e to 8aeb407 Compare August 24, 2026 15:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes dual-PCS transform selection to honor D2B/B2D opt-outs and adds regression coverage.

Changes:

  • Updates source and destination transform selection.
  • Registers the issue-1982 CTest regression.
  • Adds two-direction route validation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
IccProfLib/IccCmm.cpp Honors D2B/B2D opt-outs for dual-PCS profiles.
Build/Cmake/Testing/CMakeLists.txt Registers the regression test.
.github/scripts/iccdev-issue-1982-dual-pcs-selection-regression.sh Validates colorimetric and spectral route selection.
Suppressed comments (1)

.github/scripts/iccdev-issue-1982-dual-pcs-selection-regression.sh:135

  • The CTest harness sets ASAN_OPTIONS/UBSAN_OPTIONS with halt_on_error=0, so a sanitizer finding during this iccFromXml invocation can be printed while the command still returns zero. Unlike the later iccApplyToLink calls, this log is never passed to sanitizer_check (and the same gap exists for the dump logs), so the new ASAN-labeled regression can pass with a sanitizer finding in its setup/assertion tools. Check each generated-tool log before relying on its assertions.
"$from_xml" "$src_xml" "$src_icc" >"$outdir/fromxml-input.log" 2>&1 ||
  fail "iccFromXml failed on $src_xml; see $outdir/fromxml-input.log"
"$dump_profile" -v 100 "$src_icc" ALL >"$outdir/input.log" 2>&1

@xsscx
xsscx enabled auto-merge (squash) August 24, 2026 15:40
@xsscx

xsscx commented Aug 24, 2026

Copy link
Copy Markdown
Member

Pre Merge Report

2026-08-24 15:40:48 UTC

TODO as Punch List following Merge

Suppressed comments (1)
.github/scripts/iccdev-issue-1982-dual-pcs-selection-regression.sh:135

The CTest harness sets ASAN_OPTIONS/UBSAN_OPTIONS with halt_on_error=0, so a sanitizer finding during this iccFromXml invocation can be printed while the command still returns zero. Unlike the later iccApplyToLink calls, this log is never passed to sanitizer_check (and the same gap exists for the dump logs), so the new ASAN-labeled regression can pass with a sanitizer finding in its setup/assertion tools. Check each generated-tool log before relying on its assertions.
"$from_xml" "$src_xml" "$src_icc" >"$outdir/fromxml-input.log" 2>&1 ||
fail "iccFromXml failed on $src_xml; see $outdir/fromxml-input.log"
"$dump_profile" -v 100 "$src_icc" ALL >"$outdir/input.log" 2>&1

@xsscx
xsscx merged commit 09bba40 into master Aug 24, 2026
23 checks passed
@xsscx
xsscx deleted the fix/issue-1982-dual-pcs-selection branch August 24, 2026 15:41
@xsscx xsscx linked an issue Aug 24, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Build Build system, CMake, compiler, or packaging Configuration Repository, CMake, YAML, JSON, or tool configuration Copilot Copilot use indicated by Maintainer pending CI checks still running Scripts Shell, PowerShell, or repository automation scripts Source C or C++ source code changes Testing CTest, regression, or test coverage Unix Linux, macOS, Bash, or POSIX shell scope

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Research: CMM: honor D2B/B2D opt-out for dual-PCS profiles

3 participants