diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile
new file mode 100644
index 000000000..2d6beab42
--- /dev/null
+++ b/.clusterfuzzlite/Dockerfile
@@ -0,0 +1,18 @@
+FROM gcr.io/oss-fuzz-base/base-builder
+
+# Enhanced ClusterFuzzLite Dockerfile
+# Combined Address + Undefined Sanitizers with Full Instrumentation
+# Improved Stack Tracing, Metrics Capture, and Reporting
+
+RUN apt-get update && apt-get install -y \
+ cmake \
+ libxml2-dev \
+ libpng-dev \
+ libjpeg-turbo8-dev \
+ libtiff-dev \
+ nlohmann-json3-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY . $SRC/uci
+WORKDIR $SRC/uci
+COPY .clusterfuzzlite/build.sh $SRC/
diff --git a/.clusterfuzzlite/build-full.sh b/.clusterfuzzlite/build-full.sh
new file mode 100644
index 000000000..2659fe752
--- /dev/null
+++ b/.clusterfuzzlite/build-full.sh
@@ -0,0 +1,239 @@
+#!/bin/bash -eu
+#
+#
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+#
+#
+
+
+echo "ClusterFuzzLite Build - (Address+UBSan)"
+echo ""
+
+# Determine repository root (CFL vs local)
+if [ -d "$SRC/uci" ]; then
+ REPO_ROOT="$SRC/uci"
+ echo "Environment: ClusterFuzzLite"
+else
+ REPO_ROOT="$SRC"
+ echo "Environment: Local testing"
+fi
+
+echo "Repository root: $REPO_ROOT"
+echo "Fuzzing source: Testing/Fuzzing/"
+echo "Target: 15 fuzzers (13 core + 2 XML)"
+echo "Sanitizers: Address + Undefined (combined)"
+echo "Instrumentation: Full (stack tracing, symbolization, metrics)"
+echo ""
+
+# Build IccProfLib and IccXML libraries
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "Building libraries..."
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+BUILD_DIR="$REPO_ROOT/Build/Cmake/build_cfl_$(date +%s)"
+cd $REPO_ROOT/Build/Cmake
+
+# Clean previous builds
+rm -rf build_cfl_* CMakeCache.txt Makefile *.cmake CMakeFiles/ 2>/dev/null || true
+
+# Comment out wxWidgets section in CMakeLists.txt (not available in CFL Docker image)
+echo "Patching CMakeLists.txt to disable wxWidgets..."
+sed -i '1162,1170s/^/# DISABLED_FOR_CFL: /' CMakeLists.txt
+
+# Configure CMake
+cmake -B $BUILD_DIR -S . \
+ -DCMAKE_C_COMPILER=$CC \
+ -DCMAKE_CXX_COMPILER=$CXX \
+ -DCMAKE_C_FLAGS="$CFLAGS" \
+ -DCMAKE_CXX_FLAGS="$CXXFLAGS -frtti" \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DBUILD_SHARED_LIBS=OFF
+
+# Build libraries
+echo "Building IccProfLib2-static..."
+cmake --build $BUILD_DIR --target IccProfLib2-static -j$(nproc)
+
+echo "Building IccXML2-static..."
+cmake --build $BUILD_DIR --target IccXML2-static -j$(nproc) || echo "Warning: IccXML build may have failed"
+
+# Verify library existence
+if [ ! -f "$BUILD_DIR/IccProfLib/libIccProfLib2-static.a" ]; then
+ echo "ERROR: IccProfLib2-static.a not found"
+ exit 1
+fi
+
+echo "✅ Libraries built successfully"
+echo ""
+
+# Build fuzzers from Testing/Fuzzing/
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "Building fuzzers (15 total)..."
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+FUZZING_DIR="$REPO_ROOT/Testing/Fuzzing"
+FUZZER_COUNT=0
+
+# Core fuzzers (13)
+CORE_FUZZERS=(
+ "icc_profile_fuzzer"
+ "icc_calculator_fuzzer"
+ "icc_v5dspobs_fuzzer"
+ "icc_multitag_fuzzer"
+ "icc_roundtrip_fuzzer"
+ "icc_dump_fuzzer"
+ "icc_io_fuzzer"
+ "icc_link_fuzzer"
+ "icc_spectral_fuzzer"
+ "icc_apply_fuzzer"
+ "icc_applyprofiles_fuzzer"
+ "icc_specsep_fuzzer"
+ "icc_tiffdump_fuzzer"
+)
+
+# Build core fuzzers
+for fuzzer in "${CORE_FUZZERS[@]}"; do
+ if [ ! -f "$FUZZING_DIR/${fuzzer}.cpp" ]; then
+ echo "⚠️ Skipping $fuzzer (source not found)"
+ continue
+ fi
+
+ echo "Building $fuzzer..."
+
+ $CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ -I$REPO_ROOT/Tools/CmdLine/IccCommon \
+ -I$REPO_ROOT/Tools/CmdLine/IccApplyProfiles \
+ $FUZZING_DIR/${fuzzer}.cpp \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ $LIB_FUZZING_ENGINE \
+ -o $OUT/${fuzzer} 2>&1 | head -3 || {
+ echo "⚠️ Build failed for $fuzzer"
+ continue
+ }
+
+ if [ -f "$OUT/${fuzzer}" ]; then
+ ((FUZZER_COUNT++))
+
+ # Copy seed corpus
+ mkdir -p $OUT/${fuzzer}_seed_corpus
+ if [ -d "$FUZZING_DIR/${fuzzer}_seed_corpus" ]; then
+ cp $FUZZING_DIR/${fuzzer}_seed_corpus/*.icc $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+
+ # Fallback to .clusterfuzzlite corpus
+ if [ $(ls $OUT/${fuzzer}_seed_corpus/*.icc 2>/dev/null | wc -l) -eq 0 ]; then
+ cp $REPO_ROOT/.clusterfuzzlite/corpus/*.icc $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+
+ CORPUS_COUNT=$(ls $OUT/${fuzzer}_seed_corpus/*.icc 2>/dev/null | wc -l)
+
+ # Copy dictionary if exists
+ DICT_FILE=""
+ if [ -f "$FUZZING_DIR/${fuzzer}.dict" ]; then
+ cp $FUZZING_DIR/${fuzzer}.dict $OUT/${fuzzer}.dict
+ DICT_FILE="${fuzzer}.dict"
+ elif [ -f "$FUZZING_DIR/icc_profile.dict" ]; then
+ cp $FUZZING_DIR/icc_profile.dict $OUT/${fuzzer}.dict
+ DICT_FILE="icc_profile.dict"
+ fi
+
+ # Copy .options file if exists
+ OPTIONS_FILE=""
+ if [ -f "$FUZZING_DIR/${fuzzer}.options" ]; then
+ cp $FUZZING_DIR/${fuzzer}.options $OUT/${fuzzer}.options
+ OPTIONS_FILE="${fuzzer}.options"
+ fi
+
+ echo " ✅ $fuzzer: $CORPUS_COUNT seeds, dict: ${DICT_FILE:-none}, options: ${OPTIONS_FILE:-none}"
+ fi
+done
+
+# Build XML fuzzers (2)
+if [ -f "$BUILD_DIR/IccXML/libIccXML2-static.a" ]; then
+ echo ""
+ echo "Building XML fuzzers..."
+
+ for fuzzer in "icc_fromxml_fuzzer" "icc_toxml_fuzzer"; do
+ if [ ! -f "$FUZZING_DIR/${fuzzer}.cpp" ]; then
+ echo "⚠️ Skipping $fuzzer (source not found)"
+ continue
+ fi
+
+ echo "Building $fuzzer..."
+
+ $CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ -I$REPO_ROOT/IccXML/IccLibXML \
+ -I/usr/include/libxml2 \
+ -DHAVE_ICCXML \
+ $FUZZING_DIR/${fuzzer}.cpp \
+ $BUILD_DIR/IccXML/libIccXML2-static.a \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ -lxml2 \
+ $LIB_FUZZING_ENGINE \
+ -o $OUT/${fuzzer} 2>&1 | head -3 || {
+ echo "⚠️ Build failed for $fuzzer"
+ continue
+ }
+
+ if [ -f "$OUT/${fuzzer}" ]; then
+ ((FUZZER_COUNT++))
+
+ # Copy appropriate seed corpus
+ mkdir -p $OUT/${fuzzer}_seed_corpus
+
+ if [ "$fuzzer" = "icc_fromxml_fuzzer" ]; then
+ # XML files for fromxml
+ if [ -d "$FUZZING_DIR/${fuzzer}_seed_corpus" ]; then
+ cp $FUZZING_DIR/${fuzzer}_seed_corpus/*.xml $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+ # Fallback to .clusterfuzzlite
+ if [ $(ls $OUT/${fuzzer}_seed_corpus/*.xml 2>/dev/null | wc -l) -eq 0 ]; then
+ cp $REPO_ROOT/.clusterfuzzlite/corpus-xml/*.xml $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+ CORPUS_COUNT=$(ls $OUT/${fuzzer}_seed_corpus/*.xml 2>/dev/null | wc -l)
+ else
+ # ICC files for toxml
+ if [ -d "$FUZZING_DIR/${fuzzer}_seed_corpus" ]; then
+ cp $FUZZING_DIR/${fuzzer}_seed_corpus/*.icc $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+ if [ $(ls $OUT/${fuzzer}_seed_corpus/*.icc 2>/dev/null | wc -l) -eq 0 ]; then
+ cp $REPO_ROOT/.clusterfuzzlite/corpus/*.icc $OUT/${fuzzer}_seed_corpus/ 2>/dev/null || true
+ fi
+ CORPUS_COUNT=$(ls $OUT/${fuzzer}_seed_corpus/*.icc 2>/dev/null | wc -l)
+ fi
+
+ # Copy dictionary
+ DICT_FILE=""
+ if [ -f "$FUZZING_DIR/${fuzzer}.dict" ]; then
+ cp $FUZZING_DIR/${fuzzer}.dict $OUT/${fuzzer}.dict
+ DICT_FILE="${fuzzer}.dict"
+ fi
+
+ # Copy options
+ OPTIONS_FILE=""
+ if [ -f "$FUZZING_DIR/${fuzzer}.options" ]; then
+ cp $FUZZING_DIR/${fuzzer}.options $OUT/${fuzzer}.options
+ OPTIONS_FILE="${fuzzer}.options"
+ fi
+
+ echo " ✅ $fuzzer: $CORPUS_COUNT seeds, dict: ${DICT_FILE:-none}, options: ${OPTIONS_FILE:-none}"
+ fi
+ done
+else
+ echo "⚠️ IccXML2-static.a not found, skipping XML fuzzers"
+fi
+
+echo ""
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "Build complete: $FUZZER_COUNT fuzzers built successfully"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+if [ $FUZZER_COUNT -lt 13 ]; then
+ echo "⚠️ Warning: Expected at least 13 fuzzers, built $FUZZER_COUNT"
+ exit 1
+fi
+
+echo "✅ Build successful - ready for fuzzing campaign"
diff --git a/.clusterfuzzlite/build-minimal.sh b/.clusterfuzzlite/build-minimal.sh
new file mode 100644
index 000000000..8ee01517b
--- /dev/null
+++ b/.clusterfuzzlite/build-minimal.sh
@@ -0,0 +1,44 @@
+#!/bin/bash -eu
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+
+# Minimal CFL build for testing
+echo "Minimal CFL Build Test"
+
+# Determine repository root
+if [ -d "$SRC/uci" ]; then
+ REPO_ROOT="$SRC/uci"
+else
+ REPO_ROOT="$SRC"
+fi
+
+echo "Repository: $REPO_ROOT"
+echo "OUT: $OUT"
+
+# Comment wxWidgets
+cd $REPO_ROOT/Build/Cmake
+sed -i '1162,1170s/^/# DISABLED_FOR_CFL: /' CMakeLists.txt
+
+# Build libraries
+BUILD_DIR="$REPO_ROOT/Build/Cmake/build_cfl_$(date +%s)"
+cmake -B $BUILD_DIR -S . \
+ -DCMAKE_C_COMPILER=$CC \
+ -DCMAKE_CXX_COMPILER=$CXX \
+ -DCMAKE_C_FLAGS="$CFLAGS" \
+ -DCMAKE_CXX_FLAGS="$CXXFLAGS -frtti" \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DBUILD_SHARED_LIBS=OFF
+
+cmake --build $BUILD_DIR --target IccProfLib2-static -j$(nproc)
+
+# Build ONE fuzzer
+$CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ $REPO_ROOT/Testing/Fuzzing/icc_profile_fuzzer.cpp \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ $LIB_FUZZING_ENGINE \
+ -o $OUT/icc_profile_fuzzer
+
+echo "✅ Built 1 fuzzer successfully"
+ls -lh $OUT/
diff --git a/.clusterfuzzlite/build-production.sh b/.clusterfuzzlite/build-production.sh
new file mode 100644
index 000000000..642dfd518
--- /dev/null
+++ b/.clusterfuzzlite/build-production.sh
@@ -0,0 +1,155 @@
+#!/bin/bash -eu
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+#
+
+echo "ClusterFuzzLite Production Build - All 15 Fuzzers"
+echo ""
+
+# Determine repository root
+if [ -d "$SRC/uci" ]; then
+ REPO_ROOT="$SRC/uci"
+ echo "Environment: ClusterFuzzLite"
+else
+ REPO_ROOT="$SRC"
+ echo "Environment: Local testing"
+fi
+
+echo "Repository: $REPO_ROOT"
+echo "Output: $OUT"
+echo ""
+
+# Comment wxWidgets (verified working in minimal build)
+cd $REPO_ROOT/Build/Cmake
+echo "Patching CMakeLists.txt..."
+sed -i '1162,1170s/^/# DISABLED_FOR_CFL: /' CMakeLists.txt
+
+# Build libraries
+BUILD_DIR="$REPO_ROOT/Build/Cmake/build_cfl_$(date +%s)"
+echo "Building libraries..."
+cmake -B $BUILD_DIR -S . \
+ -DCMAKE_C_COMPILER=$CC \
+ -DCMAKE_CXX_COMPILER=$CXX \
+ -DCMAKE_C_FLAGS="$CFLAGS" \
+ -DCMAKE_CXX_FLAGS="$CXXFLAGS -frtti" \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DBUILD_SHARED_LIBS=OFF
+
+cmake --build $BUILD_DIR --target IccProfLib2-static -j$(nproc)
+cmake --build $BUILD_DIR --target IccXML2-static -j$(nproc) || echo "Warning: IccXML2 build failed"
+
+if [ ! -f "$BUILD_DIR/IccProfLib/libIccProfLib2-static.a" ]; then
+ echo "ERROR: IccProfLib2-static.a not found"
+ exit 1
+fi
+
+echo "✅ Libraries built"
+echo ""
+
+# Build fuzzers
+FUZZING_DIR="$REPO_ROOT/Testing/Fuzzing"
+FUZZER_COUNT=0
+FAILED_FUZZERS=()
+
+# All 15 fuzzers
+ALL_FUZZERS=(
+ "icc_profile_fuzzer"
+ "icc_calculator_fuzzer"
+ "icc_v5dspobs_fuzzer"
+ "icc_multitag_fuzzer"
+ "icc_roundtrip_fuzzer"
+ "icc_dump_fuzzer"
+ "icc_io_fuzzer"
+ "icc_link_fuzzer"
+ "icc_spectral_fuzzer"
+ "icc_apply_fuzzer"
+ "icc_applyprofiles_fuzzer"
+ "icc_specsep_fuzzer"
+ "icc_tiffdump_fuzzer"
+ "icc_fromxml_fuzzer"
+ "icc_toxml_fuzzer"
+)
+
+echo "Building fuzzers..."
+for fuzzer in "${ALL_FUZZERS[@]}"; do
+ echo "[$((FUZZER_COUNT+1))/15] $fuzzer..."
+
+ if [ ! -f "$FUZZING_DIR/${fuzzer}.cpp" ]; then
+ echo " ⚠️ Source not found"
+ FAILED_FUZZERS+=("$fuzzer (no source)")
+ continue
+ fi
+
+ # Determine if this is an XML fuzzer
+ if [[ "$fuzzer" == *"xml"* ]]; then
+ if [ ! -f "$BUILD_DIR/IccXML/libIccXML2-static.a" ]; then
+ echo " ⚠️ IccXML2 not available"
+ FAILED_FUZZERS+=("$fuzzer (no IccXML)")
+ continue
+ fi
+
+ $CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ -I$REPO_ROOT/IccXML/IccLibXML \
+ -I/usr/include/libxml2 \
+ -DHAVE_ICCXML \
+ $FUZZING_DIR/${fuzzer}.cpp \
+ $BUILD_DIR/IccXML/libIccXML2-static.a \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ -lxml2 \
+ $LIB_FUZZING_ENGINE \
+ -o $OUT/${fuzzer} 2>&1 | head -5 || {
+ echo " ❌ Build failed"
+ FAILED_FUZZERS+=("$fuzzer")
+ continue
+ }
+ else
+ $CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ -I$REPO_ROOT/Tools/CmdLine/IccCommon \
+ -I$REPO_ROOT/Tools/CmdLine/IccApplyProfiles \
+ $FUZZING_DIR/${fuzzer}.cpp \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ $LIB_FUZZING_ENGINE \
+ -o $OUT/${fuzzer} 2>&1 | head -5 || {
+ echo " ❌ Build failed"
+ FAILED_FUZZERS+=("$fuzzer")
+ continue
+ }
+ fi
+
+ if [ -f "$OUT/${fuzzer}" ]; then
+ ((FUZZER_COUNT++))
+ echo " ✅ Built successfully"
+ else
+ echo " ❌ Output not found"
+ FAILED_FUZZERS+=("$fuzzer (no output)")
+ fi
+done
+
+echo ""
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "Build Summary:"
+echo " ✅ Successfully built: $FUZZER_COUNT fuzzers"
+echo " ❌ Failed: ${#FAILED_FUZZERS[@]} fuzzers"
+
+if [ ${#FAILED_FUZZERS[@]} -gt 0 ]; then
+ echo ""
+ echo "Failed fuzzers:"
+ for failed in "${FAILED_FUZZERS[@]}"; do
+ echo " - $failed"
+ done
+fi
+
+echo ""
+echo "Output directory contents:"
+ls -lh $OUT/ | grep -E "(^total|fuzzer)"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+if [ $FUZZER_COUNT -lt 13 ]; then
+ echo "⚠️ Warning: Expected at least 13 fuzzers, built $FUZZER_COUNT"
+ echo "Proceeding with available fuzzers for testing..."
+fi
+
+echo "✅ Build complete: $FUZZER_COUNT fuzzers ready for fuzzing"
diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh
new file mode 100644
index 000000000..e1b7defc7
--- /dev/null
+++ b/.clusterfuzzlite/build.sh
@@ -0,0 +1,47 @@
+#!/bin/bash -eu
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+
+echo "CFL Build - Fuzzer Smoke Test Pattern"
+
+# Determine repository root
+if [ -d "$SRC/uci" ]; then
+ REPO_ROOT="$SRC/uci"
+else
+ REPO_ROOT="$SRC"
+fi
+
+echo "Repository: $REPO_ROOT"
+echo "OUT: $OUT"
+
+# Build libraries (smoke test uses ENABLE_TOOLS=OFF - no wxWidgets needed)
+BUILD_DIR="$REPO_ROOT/Build"
+cd $REPO_ROOT/Build
+cmake Cmake/ \
+ -DCMAKE_C_COMPILER=$CC \
+ -DCMAKE_CXX_COMPILER=$CXX \
+ -DCMAKE_C_FLAGS="$CFLAGS" \
+ -DCMAKE_CXX_FLAGS="$CXXFLAGS -frtti" \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DENABLE_FUZZING=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_SHARED_LIBS=ON \
+ -DENABLE_TOOLS=OFF \
+ -Wno-dev
+
+make -j$(nproc) IccProfLib2-static IccXML2-static
+
+# Build fuzzer matching smoke test pattern
+$CXX $CXXFLAGS -frtti \
+ -I$REPO_ROOT/IccProfLib \
+ -I$REPO_ROOT/IccXML/IccLibXML \
+ $REPO_ROOT/Testing/Fuzzing/icc_profile_fuzzer.cpp \
+ $BUILD_DIR/IccProfLib/libIccProfLib2-static.a \
+ $BUILD_DIR/IccXML/libIccXML2-static.a \
+ $LIB_FUZZING_ENGINE \
+ -lxml2 -lz \
+ -o $OUT/icc_profile_fuzzer
+
+echo "✅ Built 1 fuzzer successfully (smoke test pattern)"
+ls -lh $OUT/
diff --git a/.clusterfuzzlite/corpus-xml/CameraModel.xml b/.clusterfuzzlite/corpus-xml/CameraModel.xml
new file mode 100644
index 000000000..141fbadbd
--- /dev/null
+++ b/.clusterfuzzlite/corpus-xml/CameraModel.xml
@@ -0,0 +1,116 @@
+
+
+
+
+ 5.00
+ scnr
+ RGB
+ XYZ
+ now
+
+
+ Absolute
+
+
+
+
+ 1
+ rs0021
+
+
+
+
+
+
+ desc
+
+
+
+
+ A2B3
+
+
+
+
+ 0.665845845635815 0.781081987684972 -0.155892788430177
+ 4.923550307520770 -3.952824790137080 2.796829854367557
+ 1.274327197753011 1.977722322511197 -4.353931336717119
+
+
+
+
+
+ 0.9642455651282466 0.2052585528895002 0.0478091047162643
+ 0.9999999999999999 0.0146501684258497 0.0176213538916487
+ 0.8246790940919668 0.0357366850084837 -0.4005890947751716
+
+
+
+
+
+
+ B2D3
+
+
+
+
+
+
+ 0.000063066232895 0.000166109053171 0.000353413383377 0.000194502930425 0.000122614352901 0.000109743472209 0.000133774889481 0.000171185960694 0.000250662837095 0.000359724336652 0.001080623554962 0.002134574401744 0.005659507017190 0.012468192078342 0.014062709089229 0.011393273278742 0.015516501584560 0.030570777260329 0.033723006408803 0.060345919759624 0.058205824626885 0.053489388363182 0.046035319023760 0.039401781859661 0.029836844786886 0.023171915230856 0.016233114117626 0.007436857648673 0.001356909078851 0.000254624773456 0.000054439820091 0.000014979583433 0.000004784633529
+ 0.000244990556622 0.000980189550705 0.004406625830224 0.005194141869206 0.007496827942226 0.010650031337347 0.012965057867886 0.018381869220553 0.033647231669657 0.049918233156857 0.102549201975141 0.090859324717456 0.097081513297619 0.108249627351470 0.100449159204523 0.094961985594077 0.081019433591208 0.069163934403675 0.036496361861548 0.034775192786660 0.018142637544106 0.008936764485255 0.004710157785317 0.003158182877979 0.002095604535897 0.001457256483405 0.001087151007491 0.000671310064020 0.000181307300851 0.000048136444291 0.000014409107897 0.000004297510206 0.000001851068628
+ 0.001187714911300 0.009011781477010 0.044583691520976 0.061740174959911 0.077923494248955 0.082451576161067 0.091048761565321 0.089393624763117 0.084818995511244 0.068145305410297 0.073974547843174 0.033204490693790 0.015624751211911 0.007865742672240 0.004488787297383 0.002473828733863 0.001126986870356 0.000693035764230 0.000346420457450 0.000355643325701 0.000243540011615 0.000159278501685 0.000131566597083 0.000123545299696 0.000147814866148 0.000186687307330 0.000207536185558 0.000126078340976 0.000025814288635 0.000005412481368 0.000001796943814 0.000001031618949 0.000000919039336
+
+
+
+
+{
+%start with Illuminant spectral power distribution (range adjusted to fit spectral reflectance PCS range)
+49.257471935235735 56.461255244668401 59.984099824651970 57.773270260542645 74.775973108825966 87.198033528040042 90.566943033914555 91.328621954075601 95.072887390768642 91.934526578157701 95.700812701292861 96.594366276348850 97.115371634362518 102.088888343211138 100.748359379328633 102.313861341694320 100.000000000000000 97.737450349675370 98.923589007981050 93.510248030475438 97.705745692994213 99.291844618527918 99.067944977343956 95.750676092140793 98.893438340015791 95.705420726968256 98.234177034884084 103.054318766584331 99.184954029242036 87.424178468166403 91.652934776082219 92.933577205151451 76.891414787009083
+
+%apply illuminant to incomming spectral reflectance
+in(0,33)
+mul(33)
+
+%apply camera sensitivities
+mtx(0)
+
+%apply exposure adjustement (divide by sum of illuminant)
+2974.87665543836 2974.87665543836 2974.87665543836
+div(3)
+
+out(0,3)
+}
+
+
+
+
+
+
+ svcn
+ CIE 1931 standard colorimetric observer
+
+
+ 0.001368 0.004243 0.014310 0.043510 0.134380 0.283900 0.348280 0.336200 0.290800 0.195360 0.095640 0.032010 0.004900 0.009300 0.063270 0.165500 0.290400 0.433450 0.594500 0.762100 0.916300 1.026300 1.062200 1.002600 0.854450 0.642400 0.447900 0.283500 0.164900 0.087400 0.046770 0.022700 0.011359 0.005790 0.002899 0.001440
+ 0.000039 0.000120 0.000396 0.001210 0.004000 0.011600 0.023000 0.038000 0.060000 0.090980 0.139020 0.208020 0.323000 0.503000 0.710000 0.862000 0.954000 0.994950 0.995000 0.952000 0.870000 0.757000 0.631000 0.503000 0.381000 0.265000 0.175000 0.107000 0.061000 0.032000 0.017000 0.008210 0.004102 0.002091 0.001047 0.000520
+ 0.006450 0.020050 0.067850 0.207400 0.645600 1.385600 1.747060 1.772110 1.669200 1.287640 0.812950 0.465180 0.272000 0.158200 0.078250 0.042160 0.020300 0.008750 0.003900 0.002100 0.001650 0.001100 0.000800 0.000340 0.000190 0.000050 0.000020 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
+
+ Illuminant D50
+ 5000
+
+ 24.4571469692098 29.8374136323552 49.2574719352357 56.4612552446684 59.9840998246520 57.7732702605426 74.7759731088260 87.1980335280400 90.5669430339146 91.3286219540756 95.0728873907686 91.9345265781577 95.7008127012929 96.5943662763489 97.1153716343625 102.0888883432111 100.7483593793286 102.3138613416943 100.0000000000000 97.7374503496754 98.9235890079811 93.5102480304754 97.7057456929942 99.2918446185279 99.0679449773440 95.7506760921408 98.8934383400158 95.7054207269683 98.2341770348841 103.0543187665843 99.1849540292420 87.4241784681664 91.6529347760822 92.9335772051515 76.8914147870091 86.5514856528592
+
+
+
+
+
+ wtpt
+
+
+
+
+ cprt
+
+
+
+
+
diff --git a/.clusterfuzzlite/corpus-xml/ElevenChanKubelkaMunk.xml b/.clusterfuzzlite/corpus-xml/ElevenChanKubelkaMunk.xml
new file mode 100644
index 000000000..72f76db7d
--- /dev/null
+++ b/.clusterfuzzlite/corpus-xml/ElevenChanKubelkaMunk.xml
@@ -0,0 +1,189 @@
+
+
+
+
+ 5.00
+ scnr
+ BCLR
+ now
+
+
+ Absolute
+
+
+
+
+ 1
+ rs0024
+
+
+
+
+
+
+ desc
+
+
+
+
+ D2B3
+
+
+
+
+
+
+ 0.8900 2.5500 0.00001 0.0100 0.5000 0.8500 0.6000 0.0500 0.3500 0.0600 0.3000
+ 0.3600 2.7000 0.00001 0.0100 0.3500 0.8700 0.6600 0.1400 0.3900 0.0600 0.2300
+ 0.0700 2.8200 0.00001 0.0100 0.2400 0.8200 0.7000 0.2000 0.4200 0.0600 0.1900
+ 0.0100 2.9200 0.00001 0.0100 0.1700 0.7300 0.7400 0.2400 0.4600 0.0600 0.1600
+ 0.00001 2.9900 0.00001 0.0100 0.1200 0.6400 0.7800 0.2700 0.5200 0.0600 0.1400
+ 0.00001 3.0300 0.00001 0.0100 0.0900 0.5500 0.8300 0.3200 0.5800 0.0700 0.1200
+ 0.00001 3.0400 0.00001 0.0100 0.0500 0.4600 0.8800 0.3700 0.6300 0.0900 0.1200
+ 0.00001 3.1000 0.00001 0.0100 0.0300 0.3500 0.9300 0.4200 0.6900 0.1100 0.1300
+ 0.00001 3.1500 0.00001 0.0100 0.0300 0.2300 0.9600 0.4600 0.7400 0.1400 0.1600
+ 0.00001 3.1900 0.0100 0.0100 0.0300 0.1300 1.0000 0.4900 0.7800 0.1800 0.2100
+ 0.00001 3.2300 0.0100 0.0200 0.0400 0.0700 1.0100 0.5200 0.8200 0.2400 0.3000
+ 0.00001 3.2800 0.0100 0.0400 0.0500 0.0400 0.8800 0.5700 0.8500 0.3200 0.4600
+ 0.00001 3.3100 0.0100 0.0600 0.0700 0.0400 0.4700 0.6100 0.8800 0.4000 0.6800
+ 0.00001 3.3500 0.0200 0.1000 0.1000 0.0500 0.1300 0.6200 0.9200 0.4900 0.9400
+ 0.00001 3.4200 0.0400 0.1500 0.1600 0.0600 0.0300 0.6400 0.9500 0.6800 1.2600
+ 0.00001 3.4600 0.0800 0.2000 0.2600 0.0900 0.0100 0.6900 0.9600 0.9000 1.5400
+ 0.00001 3.5100 0.1200 0.2600 0.4200 0.1300 0.00001 0.7000 0.9900 0.9500 1.6100
+ 0.00001 3.5300 0.1500 0.3200 0.6600 0.2000 0.00001 0.5400 1.0700 0.8900 1.6300
+ 0.00001 3.5600 0.1700 0.3800 1.0000 0.3000 0.00001 0.2400 1.1300 0.8800 1.7000
+ 0.00001 3.6100 0.2100 0.4300 1.3800 0.4500 0.00001 0.0700 1.1200 0.7800 1.7000
+ 0.00001 3.6500 0.2400 0.4700 1.6900 0.6800 0.00001 0.0200 0.9100 0.4700 1.4800
+ 0.00001 3.7100 0.2500 0.5000 1.9200 1.0100 0.00001 0.0100 0.3400 0.2000 1.1500
+ 0.00001 3.7400 0.2500 0.5200 2.1400 1.4400 0.00001 0.00001 0.0800 0.0800 0.9400
+ 0.00001 3.8000 0.2400 0.5200 2.3300 1.8500 0.00001 0.00001 0.0200 0.0300 0.9400
+ 0.00001 3.8400 0.2500 0.5100 2.4600 2.1100 0.00001 0.00001 0.0100 0.0200 1.0800
+ 0.00001 3.8900 0.2500 0.4900 2.5300 2.2600 0.00001 0.00001 0.00001 0.0100 1.1200
+ 0.00001 3.9100 0.2000 0.4500 2.5600 2.3500 0.00001 0.00001 0.00001 0.00001 0.8900
+ 0.00001 3.9900 0.1300 0.4100 2.5000 2.4300 0.00001 0.00001 0.00001 0.00001 0.5800
+ 0.00001 4.0800 0.0700 0.3600 2.3700 2.4000 0.00001 0.00001 0.00001 0.00001 0.3300
+ 0.00001 4.1200 0.0300 0.3000 2.3200 2.2300 0.00001 0.00001 0.00001 0.00001 0.1800
+ 0.00001 4.1800 0.0100 0.2300 2.3900 2.0100 0.00001 0.00001 0.00001 0.00001 0.1000
+ 0.00001 4.2000 0.00001 0.1800 2.5400 1.8100 0.00001 0.00001 0.00001 0.00001 0.0600
+ 0.00001 4.2500 0.00001 0.1300 2.7900 1.6800 0.00001 0.00001 0.00001 0.00001 0.0300
+ 0.00001 4.3000 0.00001 0.0900 3.0600 1.6600 0.00001 0.00001 0.00001 0.00001 0.0200
+ 0.00001 4.3800 0.00001 0.0700 3.1000 1.7200 0.00001 0.00001 0.00001 0.00001 0.0100
+ 0.00001 4.4900 0.00001 0.0500 2.8200 1.7700 0.00001 0.00001 0.00001 0.00001 0.0100
+
+
+
+
+
+ 1.0000 0.3000 0.0300 0.00001 0.1100 0.0900 0.0600 0.0100 0.0200 0.0300 0.0200
+ 1.0000 0.3500 0.0300 0.00001 0.0900 0.0900 0.0800 0.0100 0.0200 0.0300 0.0200
+ 1.0000 0.3600 0.0300 0.00001 0.0700 0.0900 0.0900 0.0100 0.0200 0.0300 0.0100
+ 1.0000 0.3500 0.0300 0.00001 0.0500 0.0900 0.0900 0.0100 0.0200 0.0200 0.0100
+ 1.0000 0.3500 0.0300 0.00001 0.0400 0.0800 0.1000 0.0200 0.0200 0.0200 0.0100
+ 1.0000 0.3500 0.0300 0.00001 0.0300 0.0700 0.1000 0.0200 0.0300 0.0200 0.0100
+ 1.0000 0.3500 0.0300 0.00001 0.0300 0.0600 0.1000 0.0200 0.0300 0.0200 0.0100
+ 1.0000 0.3600 0.0300 0.00001 0.0200 0.0500 0.1000 0.0200 0.0300 0.0200 0.0100
+ 1.0000 0.3600 0.0300 0.00001 0.0200 0.0400 0.1000 0.0300 0.0400 0.0200 0.0100
+ 1.0000 0.3700 0.0300 0.00001 0.0100 0.0300 0.1100 0.0300 0.0400 0.0200 0.0100
+ 1.0000 0.3700 0.0300 0.00001 0.0100 0.0300 0.1200 0.0300 0.0400 0.0200 0.0200
+ 1.0000 0.3700 0.0300 0.00001 0.0100 0.0200 0.1600 0.0400 0.0400 0.0300 0.0200
+ 1.0000 0.3700 0.0300 0.00001 0.0100 0.0200 0.3000 0.0400 0.0500 0.0300 0.0300
+ 1.0000 0.3800 0.0200 0.0100 0.0100 0.0100 0.4200 0.0400 0.0500 0.0300 0.0400
+ 1.0000 0.3800 0.0200 0.0100 0.0100 0.0100 0.4300 0.0500 0.0500 0.0400 0.0600
+ 1.0000 0.3900 0.0200 0.0100 0.0100 0.0100 0.4300 0.0600 0.0500 0.0600 0.0700
+ 1.0000 0.4000 0.0100 0.0100 0.0100 0.0100 0.4300 0.0700 0.0500 0.0600 0.0800
+ 1.0000 0.4000 0.0100 0.0100 0.0200 0.0100 0.4300 0.0900 0.0700 0.0600 0.0800
+ 1.0000 0.4000 0.0100 0.0100 0.0300 0.0100 0.4300 0.1100 0.0800 0.0700 0.1000
+ 1.0000 0.4100 0.0100 0.0100 0.0400 0.0200 0.4300 0.1300 0.1100 0.0700 0.1100
+ 1.0000 0.4100 0.0100 0.0100 0.0600 0.0200 0.4300 0.1400 0.1400 0.0600 0.1100
+ 1.0000 0.4200 0.0200 0.0200 0.0800 0.0400 0.4300 0.1400 0.2900 0.0400 0.0900
+ 1.0000 0.4200 0.0200 0.0200 0.1100 0.0700 0.4300 0.1400 0.3400 0.0300 0.0700
+ 1.0000 0.4300 0.0200 0.0200 0.1400 0.1000 0.4300 0.1400 0.3100 0.0200 0.0600
+ 1.0000 0.4400 0.0200 0.0200 0.1700 0.1400 0.4300 0.1400 0.2800 0.0200 0.0700
+ 1.0000 0.4400 0.0200 0.0200 0.1900 0.1700 0.4300 0.1400 0.2400 0.0200 0.0800
+ 1.0000 0.4400 0.0200 0.0200 0.2100 0.2000 0.4300 0.1300 0.2100 0.0100 0.0800
+ 1.0000 0.4600 0.0300 0.0200 0.2300 0.2400 0.4300 0.1200 0.1700 0.0100 0.0700
+ 1.0000 0.4700 0.0400 0.0100 0.2100 0.2600 0.4300 0.1000 0.1500 0.0100 0.0600
+ 1.0000 0.4700 0.0400 0.0100 0.2100 0.2600 0.4300 0.0900 0.1300 0.0100 0.0400
+ 1.0000 0.4800 0.0400 0.0100 0.2000 0.2400 0.4300 0.0700 0.1200 0.0100 0.0300
+ 1.0000 0.4900 0.0400 0.0100 0.2300 0.2200 0.4300 0.0500 0.1100 0.0100 0.0300
+ 1.0000 0.5000 0.0400 0.0100 0.2700 0.2000 0.4300 0.0400 0.1000 0.0100 0.0300
+ 1.0000 0.5100 0.0300 0.0100 0.3600 0.1900 0.4300 0.0400 0.0900 0.0100 0.0200
+ 1.0000 0.5200 0.0300 0.0100 0.4600 0.2000 0.4300 0.0300 0.0700 0.0100 0.0200
+ 1.0000 0.5400 0.0300 0.0100 0.4900 0.2200 0.4300 0.0200 0.0600 0.0100 0.0300
+
+
+
+
+ {
+ in(0,11)
+ 0 copy(1,10)
+ vmax(11)
+ copy(11,1)
+ sum(11) copy
+ 0 gt if {
+ copy(1,10)
+ div(11)
+ } else {
+ pop
+ }
+ tput(0,11)
+ tget(0,11)
+ mtx(0)
+ tget(0,11)
+ mtx(1)
+ tput(0,36)
+ tget(0,36)
+ div(36)
+ tget(0,36)
+ 0 copy(1,35)
+ gt(36)
+ mul(36)
+ tput(0,36)
+ 1 copy(1,35)
+ tget(0,36)
+ add(36)
+ tget(0,36)
+ tget(0,36)
+ mul(36)
+ tget(0,36)
+ tget(0,36)
+ add(36)
+ add(36)
+ sqrt(36)
+ sub(36)
+ out(0,36)
+ }
+
+
+
+
+
+
+ svcn
+ CIE 1931 standard colorimetric observer
+
+
+ 0.001368 0.004243 0.014310 0.043510 0.134380 0.283900 0.348280 0.336200 0.290800 0.195360 0.095640 0.032010 0.004900 0.009300 0.063270 0.165500 0.290400 0.433450 0.594500 0.762100 0.916300 1.026300 1.062200 1.002600 0.854450 0.642400 0.447900 0.283500 0.164900 0.087400 0.046770 0.022700 0.011359 0.005790 0.002899 0.001440
+ 0.000039 0.000120 0.000396 0.001210 0.004000 0.011600 0.023000 0.038000 0.060000 0.090980 0.139020 0.208020 0.323000 0.503000 0.710000 0.862000 0.954000 0.994950 0.995000 0.952000 0.870000 0.757000 0.631000 0.503000 0.381000 0.265000 0.175000 0.107000 0.061000 0.032000 0.017000 0.008210 0.004102 0.002091 0.001047 0.000520
+ 0.006450 0.020050 0.067850 0.207400 0.645600 1.385600 1.747060 1.772110 1.669200 1.287640 0.812950 0.465180 0.272000 0.158200 0.078250 0.042160 0.020300 0.008750 0.003900 0.002100 0.001650 0.001100 0.000800 0.000340 0.000190 0.000050 0.000020 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
+
+ Illuminant D50
+ 5000
+
+ 24.4571469692098 29.8374136323552 49.2574719352357 56.4612552446684 59.9840998246520 57.7732702605426 74.7759731088260 87.1980335280400 90.5669430339146 91.3286219540756 95.0728873907686 91.9345265781577 95.7008127012929 96.5943662763489 97.1153716343625 102.0888883432111 100.7483593793286 102.3138613416943 100.0000000000000 97.7374503496754 98.9235890079811 93.5102480304754 97.7057456929942 99.2918446185279 99.0679449773440 95.7506760921408 98.8934383400158 95.7054207269683 98.2341770348841 103.0543187665843 99.1849540292420 87.4241784681664 91.6529347760822 92.9335772051515 76.8914147870091 86.5514856528592
+
+
+
+
+
+ wtpt
+
+
+
+
+ cprt
+
+
+
+
+
diff --git a/.clusterfuzzlite/corpus-xml/calcImport.xml b/.clusterfuzzlite/corpus-xml/calcImport.xml
new file mode 100644
index 000000000..505289ea6
--- /dev/null
+++ b/.clusterfuzzlite/corpus-xml/calcImport.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+ 1 0 0
+ 0 1 0
+ 0 0 1
+
+
+
+
+
+
+
+ tget{v1} 1 add tput{v1}
+ tget{v2} 1 add tput{v2}
+ mtx{identity3x3}
+
+
diff --git a/.clusterfuzzlite/corpus-xml/calcVars.xml b/.clusterfuzzlite/corpus-xml/calcVars.xml
new file mode 100644
index 000000000..246dc7a76
--- /dev/null
+++ b/.clusterfuzzlite/corpus-xml/calcVars.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.clusterfuzzlite/corpus-xml/srgbCalcTest.xml b/.clusterfuzzlite/corpus-xml/srgbCalcTest.xml
new file mode 100644
index 000000000..352a3ef7f
--- /dev/null
+++ b/.clusterfuzzlite/corpus-xml/srgbCalcTest.xml
@@ -0,0 +1,868 @@
+
+
+
+
+ 5.0
+ spac
+ RGB
+ XYZ
+ now
+
+
+ Relative
+
+
+
+ ICC
+ 34562ABF994CCD066D2C5721D0D68C5D
+
+
+
+ desc
+
+
+
+ A2B1
+
+
+
+
+
+
+
+ 1.00000000 0.5 0.0 0.0
+
+
+
+
+ 1.00000000 1.0 0.0 0.0
+
+
+
+
+ 1.00000000 1.5 0.0 0.0
+
+
+
+
+
+
+
+ 1.00000000 0.0764319 0.0125 0.00000000
+
+
+ 2.40000000 0.9429124761 0.05186018619 0.0125
+
+
+
+
+ 1.00000000 0.0764319 0.0125 0.00000000
+
+
+ 2.40000000 0.9429124761 0.05186018619 0.0125
+
+
+
+
+ 1.00000000 0.0764319 0.0125 0.00000000
+
+
+ 2.40000000 0.9429124761 0.05186018619 0.0125
+
+
+
+
+
+
+ 1.00000000 1.00000000 -1.00000000
+ 2.00000000 1.00000000 -1.00000000
+ 1.00000000 -1.00000000 1.00000000
+
+
+
+
+
+ 0.4660 0.3851 0.1431
+ 0.2225 0.7169 0.0606
+ 0.0139 0.0971 0.7139
+
+
+
+
+ 2 2 2
+
+ 0.0 0.0 0.0
+ 0.0 0.0 0.75
+ 0.0 0.5 0.0
+ 0.0 0.5 0.75
+ 0.25 0.0 0.0
+ 0.25 0.0 0.75
+ 0.25 0.5 0.0
+ 0.25 0.5 0.75
+
+
+
+
+ { in(0,3) 1 1 1 add(3) out(0,3) }
+
+
+
+
+
+
+
+ 500.0
+ 20.0
+ 0.69
+ 1.00
+ 1.00
+
+
+
+
+
+
+
+
+ 500.0
+ 20.0
+ 0.69
+ 1.00
+ 1.00
+
+
+
+
+
+
+ 0 2 4
+ 2 4 8
+
+
+
+
+
+{
+1 1 1
+tput(0,3)
+
+tget(0, 3)
+sum(3)
+3 eq
+tput(3)
+
+1 1 1
+tsav(4, 3)
+sum(3)
+3 eq
+tput(7)
+
+1 1 1
+curv(0) %y=.5x; y=x; y=1.5x
+sum(3)
+3 eq
+tput(8)
+
+1 2 3
+mtx(2) %[1 1 -1; 2 1 -1; 1 -1 1]
+0 1 2
+sub(3)
+sum(3)
+0 eq
+tput(9)
+
+1 1 1
+clut(4) %clut with 0.25 0.5 0.75 at entry 1 1 1
+0.25 0.5 0.75
+sub(3)
+sum(3)
+0 eq
+tput(10)
+
+1 2 3
+calc(5) %{ in(0,3) 1 1 1 add(3) out(0,3) }
+2 3 4
+sub(3)
+sum(3)
+0 eq
+tput(11)
+
+.5
+tint(8)
+1 3 6
+sub(3)
+sum(3)
+0 eq
+tput(12)
+
+100 0 0
+fJab(6) %JabToXYZ
+0.9642 1.0 0.8249
+sub(3)
+abs(3)
+sum(3)
+0.001 le
+tput(13)
+
+0.9642 1.0 0.8249
+tJab(7) %XYZToJab
+100 0 0
+sub(3)
+abs(3)
+sum(3)
+0.1 le
+tput(14)
+
+1
+elem(8)
+2 4 8
+sub(3)
+sum(3)
+0 eq
+tput(15)
+
+1 2 3 4 5 6
+copy(3,2)
+sum(12)
+51 eq
+tput(16)
+
+1 2 3 4 5
+rotl(5,2)
+3 4 5 1 2
+sub(5)
+sum(5)
+0 eq
+tput(17)
+
+1 2 3 4 5
+rotr(5,2)
+4 5 1 2 3
+sub(5)
+sum(5)
+0 eq
+tput(18)
+
+1 2 3 4 5
+posd(4,2)
+sum(7)
+19 eq
+tput(19)
+
+1 2 3 4 5
+flip(5)
+5 4 3 2 1
+sub(5)
+abs(5)
+sum(5)
+0 eq
+tput(20)
+
+1 2 3 4 5
+pop(2)
+sum(3)
+6 eq
+tput(21)
+
+1 2 3
+4 5 6
+7 8 9
+6 15 24
+solv(3,3)
+1 eq if { 1 1 1 sub(3) abs(3) 0.00001 0.00001 0.00001 lt(3) }
+else { 0 0 0 eq(3) }
+sum(3)
+3 eq
+tput(22)
+
+1 2 3 4
+5 6 7 8
+tran(2,4)
+1 5
+2 6
+3 7
+4 8
+eq(8) and(8)
+tput(23)
+
+2 3 4 5
+prod(4)
+120 eq
+tput(24)
+
+5 3 1 2 4
+min(5)
+1 eq
+tput(25)
+
+1 3 5 4 2
+max(5)
+5 eq
+tput(26)
+
+1 1 1 1 1
+and(5)
+tput(27)
+
+0 0 1 0 0
+or(5)
+tput(28)
+
+pi
+3.1415 sub abs
+0.1 lt
+tput(29)
+
++INF
+1.0E35 gt
+tput(30)
+
+-INF
+-1.0E35 lt
+tput(31)
+
+NaN NaN eq
+tput(32)
+
+1 2 3 4
+-1 -2 -3 -4
+add(4)
+sum(4)
+0 eq
+tput(33)
+
+1 2 3 4 1 2 3 4
+sub(4)
+sum(4)
+0 eq
+tput(34)
+
+2 4 6 8
+0.5 0.5 0.5 0.5
+mul(4)
+1 2 3 4
+sub(4)
+sum(4)
+0 eq
+tput(35)
+
+2 4 6 8
+2 2 2 2
+div(4)
+1 2 3 4
+sub(4)
+sum(4)
+0 eq
+tput(36)
+
+5 11 3.25 2 4 1
+mod(3)
+1 3 0.25
+sub(3)
+sum(3)
+0 eq
+tput(37)
+
+2 2 2 2
+2 3 4 5
+pow(4)
+4 8 16 32
+sub(4)
+sum(4)
+0 eq
+tput(38)
+
+2 3 4 2
+gama(3)
+4 9 16
+sub(3)
+sum(3)
+0 eq
+tput(39)
+
+2 3 4 2
+sadd(3)
+4 5 6
+sub(3)
+sum(3)
+0 eq
+tput(40)
+
+3 4 5 2
+ssub(3)
+1 2 3
+sub(3)
+sum(3)
+0 eq
+tput(41)
+
+2 3 4 2
+smul(3)
+4 6 8
+sub(3)
+sum(3)
+0 eq
+tput(42)
+
+2 4 8 2
+sdiv(3)
+1 2 4
+sub(3)
+sum(3)
+0 eq
+tput(43)
+
+2 3 4
+sq(3)
+4 9 16
+sub(3)
+sum(3)
+0 eq
+tput(44)
+
+4 9 16
+sqrt(3)
+2 3 4
+sub(3)
+sum(3)
+0 eq
+tput(45)
+
+2 3 4
+cb(3)
+8 27 64
+sub(3)
+sum(3)
+0 eq
+tput(46)
+
+8 27 64
+cbrt(3)
+2 3 4
+sub(3)
+sum(3)
+0 eq
+tput(47)
+
+2 3 4
+cb(3)
+8 27 64
+sub(3)
+sum(3)
+0 eq
+tput(48)
+
+8 27 64
+cbrt(3)
+2 3 4
+sub(3)
+sum(3)
+0 eq
+tput(49)
+
+1 -2 3 -4 5
+abs(5)
+sum(5)
+15 eq
+tput(50)
+
+1 2 3 4
+neg(4)
+-1 -2 -3 -4
+sub(4)
+sum(4)
+0 eq
+tput(51)
+
+1.1 2.5 3.7
+rond(3)
+1 3 4
+sub(3)
+sum(3)
+0 eq
+tput(52)
+
+1.1 2.5 3.7
+flor(3)
+1 2 3
+sub(3)
+sum(3)
+0 eq
+tput(53)
+
+1.1 2.5 3.7
+ceil(3)
+2 3 4
+sub(3)
+sum(3)
+0 eq
+tput(54)
+
+1.1 2.5 3.7
+trnc(3)
+1 2 3
+sub(3)
+sum(3)
+0 eq
+tput(55)
+
+1 2 3
+exp(3)
+sum(3)
+copy
+30 ge
+flip
+31 le
+and
+tput(56)
+
+10 100 1000
+log(3)
+1 2 3
+sub(3)
+sum(3)
+0 eq
+tput(57)
+
+3 7 20
+ln(3)
+sum(3)
+copy
+6 ge
+flip
+7 le
+and
+tput(58)
+
+0.00000 1.57080 0.78540
+sin(3)
+0.00000 1.00000 0.70711
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(59)
+
+0.00000 1.57080 0.78540
+cos(3)
+1 0 0.70711
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(60)
+
+0.00000 1.0 0.78540
+tan(3)
+0 1.5574 1
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(61)
+
+0.00000 1.00000 0.70711
+asin(3)
+0.00000 1.57080 0.78540
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(62)
+
+0.00000 1.00000 0.70711
+acos(3)
+1.57080 0.00000 0.78539
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(63)
+
+0.00000 1.00000 0.70711
+atan(3)
+0.00000 0.78540 0.61548
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(64)
+
+1 2 2 0 1 2
+atn2(3)
+0.00000 0.46365 0.78540
+sub(3)
+sum(3)
+abs
+0.01 le
+tput(65)
+
+0 28.2843 40 40 28.2843 0
+ctop(3)
+40 40 40 0 45 90
+sub(6)
+sum(6)
+abs
+0.01 le
+tput(66)
+
+40 40 40 0 45 90
+ptoc(3)
+0 28.2843 40 40 28.2843 0
+sub(6)
+sum(6)
+abs
+0.01 le
+tput(67)
+
+pi +INF -INF NaN 4
+rnum(5)
+1 0 0 0 1
+eq(5)
+sum(5)
+5 eq
+tput(68)
+
+1 2 3 4 5 3 3 3 3 3
+lt(5)
+1 1 0 0 0
+sub(5)
+sum(5)
+0 eq
+tput(69)
+
+1 2 3 4 5 3 3 3 3 3
+le(5)
+1 1 1 0 0
+sub(5)
+sum(5)
+0 eq
+tput(70)
+
+1 2 3 4 5 1 0 3 0 5
+eq(5)
+1 0 1 0 1
+sub(5)
+sum(5)
+0 eq
+tput(71)
+
+1 2 3 4 5 1 0 3 0 5
+near(5)
+1 0 1 0 1
+sub(5)
+sum(5)
+0 eq
+tput(72)
+
+1 2 3 4 5 3 3 3 3 3
+ge(5)
+0 0 1 1 1
+sub(5)
+sum(5)
+0 eq
+tput(73)
+
+1 2 3 4 5 3 3 3 3 3
+gt(5)
+0 0 0 1 1
+sub(5)
+sum(5)
+0 eq
+tput(74)
+
+1 0 1 0 1
+not(5)
+0 1 0 1 0
+sub(5)
+sum(5)
+0 eq
+tput(75)
+
+0 1 2 3 3 2 1 0
+vmax(4)
+3 2 2 3
+sub(4)
+sum(4)
+0 eq
+tput(76)
+
+0 1 2 3 3 2 1 0
+vmin(4)
+0 1 1 0
+sub(4)
+sum(4)
+0 eq
+tput(77)
+
+0 1 0 1 0 0 1 1
+vand(4)
+0 0 0 1
+sub(4)
+sum(4)
+0 eq
+tput(78)
+
+0 1 0 1 0 0 1 1
+vor(4)
+0 1 1 1
+sub(4)
+sum(4)
+0 eq
+tput(79)
+
+1 .5 .25 1 .5 .25 1 .5 .25
+tLab(3)
+100 76.06926 57.07542 0 0 0 0 0 0
+sub(9)
+abs(9)
+sum(9)
+0.0005 lt
+tput(80)
+
+100 76.06926 57.07542 0 0 0 0 0 0
+tXYZ(3)
+1 .5 .25 1 .5 .25 1 .5 .25
+sub(9)
+abs(9)
+sum(9)
+0.0005 lt
+tput(81)
+
+20
+0 if { pop 10 }
+20 eq
+tput(82)
+
+20
+1 if { pop 10 }
+10 eq
+tput(83)
+
+1 if { 10 } else { 20 }
+10 eq
+tput(84)
+
+0 if { 10 } else { 20 }
+20 eq
+tput(85)
+
+2 sel
+case { 1 }
+case { 2 }
+case { 4 }
+case { 8 }
+dflt { -1 }
+4 eq
+tput(86)
+
+8 sel
+case { 1 }
+case { 2 }
+case { 4 }
+case { 8 }
+dflt { -1 }
+-1 eq
+tput(87)
+
+0
+1 sel
+case { pop 1 }
+case { pop 3 }
+case { pop 9 }
+case { pop 27 }
+3 eq
+tput(88)
+
+0
+-1 sel
+case { pop 1 }
+case { pop 3 }
+case { pop 9 }
+case { pop 27 }
+0 eq
+tput(89)
+
+env(true)
+1 1 eq(2) sum(2)
+2 eq
+tput(90)
+
+env(ndef)
+0 0 eq(2) sum(2)
+2 eq
+tput(91)
+
+tget(0,46)
+tget(46,46)
+sum(92)
+92 eq
+if {
+env(01020304) if {
+ copy(1,2)
+}
+else {
+ in(0,3)
+ curv(1)
+ mtx(3)
+}
+out(0,3)
+}
+else {
+0 0 0
+out(0,3)
+}
+
+}
+
+
+
+
+
+ B2A1
+
+
+
+ 3.134578 -0.978957 0.072119
+ -1.617306 1.916218 -0.229142
+ -0.491034 0.033570 1.405751
+
+
+
+
+
+ 1.00000000 13.08354234 0.16354428 0.00000000
+
+
+ 0.41667 1.15151257 -0.01439391 -0.055
+
+
+
+
+ 1.00000000 13.08354234 0.16354428 0.00000000
+
+
+ 0.41667 1.15151257 -0.01439391 -0.055
+
+
+
+
+ 1.00000000 13.08354234 0.16354428 0.00000000
+
+
+ 0.41667 1.15151257 -0.01439391 -0.055
+
+
+
+
+
+
+ wtpt
+
+
+
+ cprt
+
+
+
+
diff --git a/.clusterfuzzlite/corpus/125001ec6943203efbbd05c9d2cf893f.icc b/.clusterfuzzlite/corpus/125001ec6943203efbbd05c9d2cf893f.icc
new file mode 100644
index 000000000..b62d0ae0f
Binary files /dev/null and b/.clusterfuzzlite/corpus/125001ec6943203efbbd05c9d2cf893f.icc differ
diff --git a/.clusterfuzzlite/corpus/5060dda10859db041e44ff7300f26fa5.icc b/.clusterfuzzlite/corpus/5060dda10859db041e44ff7300f26fa5.icc
new file mode 100644
index 000000000..9a1a92d8c
Binary files /dev/null and b/.clusterfuzzlite/corpus/5060dda10859db041e44ff7300f26fa5.icc differ
diff --git a/.clusterfuzzlite/corpus/7e14174d6ea92745036ca0068e77da32.icc b/.clusterfuzzlite/corpus/7e14174d6ea92745036ca0068e77da32.icc
new file mode 100644
index 000000000..b1fd20aa1
Binary files /dev/null and b/.clusterfuzzlite/corpus/7e14174d6ea92745036ca0068e77da32.icc differ
diff --git a/.clusterfuzzlite/corpus/a9a07583bbb221bd7297b54006c30c1b.icc b/.clusterfuzzlite/corpus/a9a07583bbb221bd7297b54006c30c1b.icc
new file mode 100644
index 000000000..5da2fbe2a
Binary files /dev/null and b/.clusterfuzzlite/corpus/a9a07583bbb221bd7297b54006c30c1b.icc differ
diff --git a/.clusterfuzzlite/corpus/cba7e5d812ac3267d44102752a331230.icc b/.clusterfuzzlite/corpus/cba7e5d812ac3267d44102752a331230.icc
new file mode 100644
index 000000000..1523364ed
Binary files /dev/null and b/.clusterfuzzlite/corpus/cba7e5d812ac3267d44102752a331230.icc differ
diff --git a/.clusterfuzzlite/corpus/ef870ddfe52151a05999bc4bb5dcf3a28fab9f6d b/.clusterfuzzlite/corpus/ef870ddfe52151a05999bc4bb5dcf3a28fab9f6d
new file mode 100644
index 000000000..83af7f5e0
Binary files /dev/null and b/.clusterfuzzlite/corpus/ef870ddfe52151a05999bc4bb5dcf3a28fab9f6d differ
diff --git a/.clusterfuzzlite/fuzzer.options b/.clusterfuzzlite/fuzzer.options
new file mode 100644
index 000000000..2c4794750
--- /dev/null
+++ b/.clusterfuzzlite/fuzzer.options
@@ -0,0 +1,58 @@
+[libfuzzer]
+# Enhanced LibFuzzer Options - Combined Sanitizers with Full Instrumentation
+# Print final statistics including coverage, corpus, and performance metrics
+print_final_stats=1
+
+# Print program counter values for coverage analysis
+print_pcs=1
+
+# Print function coverage
+print_funcs=1
+
+# Maximum total time per fuzzer run (seconds)
+max_total_time=7200
+
+# Use value profile for better coverage guidance
+use_value_profile=1
+
+# Enable fork mode for parallel fuzzing
+fork=4
+
+# Reduce depth for faster iteration
+max_len=65536
+
+# Memory limit per run (MB) - increased for complex ICC profiles
+rss_limit_mb=4096
+
+# Timeout per individual test case (seconds)
+timeout=30
+
+[asan]
+# Address Sanitizer Options - Enhanced Stack Tracing
+symbolize=1
+print_stacktrace=1
+halt_on_error=1
+detect_stack_use_after_return=1
+check_initialization_order=1
+strict_init_order=1
+detect_invalid_pointer_pairs=2
+print_summary=1
+stack_trace_format=DEFAULT
+allocator_may_return_null=1
+detect_leaks=1
+quarantine_size_mb=256
+
+[ubsan]
+# Undefined Behavior Sanitizer Options - Enhanced Reporting
+symbolize=1
+print_stacktrace=1
+halt_on_error=0
+print_summary=1
+verbosity=1
+report_error_type=1
+
+[msan]
+# Memory Sanitizer Options (if enabled)
+symbolize=1
+print_stacktrace=1
+halt_on_error=1
diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml
new file mode 100644
index 000000000..c8d78d3a6
--- /dev/null
+++ b/.clusterfuzzlite/project.yaml
@@ -0,0 +1,2 @@
+language: c++
+main_repo: 'https://github.com/InternationalColorConsortium/iccdev.git'
diff --git a/.github/workflows/ScanBuild.yml b/.github/workflows/ScanBuild.yml
index 299d84f97..e3b86b6c4 100644
--- a/.github/workflows/ScanBuild.yml
+++ b/.github/workflows/ScanBuild.yml
@@ -1,26 +1,23 @@
###############################################################
#
-## Copyright (©) 2025 International Color Consortium.
-## All rights reserved.
-## https://color.org
-#
-#
-## Intent: iccDEV Scan Build Runner for Matrix OS
-#
-## Last Updated: 28-NOV-2025 0000Z by David Hoyt
-## Add Read Permission Block
-## TODO: Push binary releases, tags etc..
-#
-##
-#
+# Copyright (©) 2025 International Color Consortium.
+# All rights reserved.
+# https://color.org
#
#
+# Intent: iccDEV Scan Build Runner
#
+# Last Updated: 08-FEB-2026
+# Parallel build, hardened shells, sanitizer,
+# dynamic LLVM path, libjpeg-dev dependency.
#
###############################################################
name: "Scan Build"
+permissions:
+ contents: read
+
on:
workflow_dispatch:
@@ -28,114 +25,147 @@ jobs:
build-linux:
name: Build and Test Linux with scan-build
runs-on: ubuntu-latest
- timeout-minutes: 20
- permissions:
- contents: read
-
+ timeout-minutes: 30
strategy:
fail-fast: false
steps:
- - name: 📥 Checkout master
+ - name: Checkout
uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+ with:
+ fetch-depth: 0
+ persist-credentials: false
- # Install dependencies
- name: Install dependencies
- shell: bash
+ shell: bash --noprofile --norc {0}
env:
- BASH_ENV: /dev/null
+ BASH_ENV: /dev/null
run: |
set -euo pipefail
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global credential.helper ""
-
- # Clear the in-shell GITHUB_TOKEN
unset GITHUB_TOKEN || true
- sudo apt-get update
- sudo apt-get install -y build-essential cmake gcc g++ clang clang-tools libpng-dev libxml2 libxml2-dev libtiff-dev nlohmann-json3-dev libwxgtk3.2-dev wx-common python3 python3-pip curl git llvm
- # Ensure scan-build is in PATH
- - name: Ensure scan-build is installed and accessible
- shell: bash
- env:
- BASH_ENV: /dev/null
- run: |
- set -euo pipefail
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- git config --global credential.helper ""
-
- # Clear the in-shell GITHUB_TOKEN
- unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake gcc g++ clang clang-tools \
+ libpng-dev libxml2-dev libtiff-dev libjpeg-dev \
+ nlohmann-json3-dev libwxgtk3.2-dev wx-common \
+ python3 curl git llvm
+
+ echo "### Environment" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Tool | Version |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|------|---------|" >> "$GITHUB_STEP_SUMMARY"
+ echo "| scan-build | $(scan-build --version 2>&1 | head -1) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| clang | $(clang --version 2>&1 | head -1) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| cmake | $(cmake --version | head -1) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| nproc | $(nproc) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
- which scan-build || echo "? scan-build not found"
- scan-build --version || echo "? scan-build version check failed"
- # Configure the build with scan-build
- - name: Configure the build with scan-build
- shell: bash
+ - name: Configure with scan-build
+ shell: bash --noprofile --norc {0}
env:
- BASH_ENV: /dev/null
+ BASH_ENV: /dev/null
+ CC: clang
+ CXX: clang++
run: |
set -euo pipefail
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global credential.helper ""
-
- # Clear the in-shell GITHUB_TOKEN
unset GITHUB_TOKEN || true
- ls
+ LLVM_BIN=$(llvm-config --bindir 2>/dev/null || echo "/usr/lib/llvm-$(llvm-config --version 2>/dev/null | cut -d. -f1)/bin")
+ export PATH="${LLVM_BIN}:${PATH}"
+
cd Build
- pwd
- ls
- export CC=clang
- export CXX=clang++
- export PATH="/usr/lib/llvm-17/bin:$PATH"
- scan-build cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local -DCMAKE_BUILD_TYPE=Release -DENABLE_TOOLS=ON -Wno-dev Cmake/
- # Run scan-build for static analysis
- - name: Run scan-build for static analysis
- shell: bash
+ scan-build cmake \
+ -DCMAKE_INSTALL_PREFIX=$HOME/.local \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DENABLE_TOOLS=ON \
+ -Wno-dev \
+ Cmake/
+
+ - name: Run scan-build with all processors
+ shell: bash --noprofile --norc {0}
env:
- BASH_ENV: /dev/null
+ BASH_ENV: /dev/null
run: |
set -euo pipefail
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global credential.helper ""
-
- # Clear the in-shell GITHUB_TOKEN
unset GITHUB_TOKEN || true
- pwd
- ls
+ LLVM_BIN=$(llvm-config --bindir 2>/dev/null || echo "/usr/lib/llvm-$(llvm-config --version 2>/dev/null | cut -d. -f1)/bin")
+ export PATH="${LLVM_BIN}:${PATH}"
+
+ NPROC=$(nproc)
+ echo "Running scan-build with $NPROC parallel jobs"
cd Build
- pwd
- ls
- export PATH="/usr/lib/llvm-17/bin:$PATH"
- scan-build --status-bugs --keep-going -o scan-build-reports make -j$(nproc) || true
- continue-on-error: true # Allow the step to complete even if issues are found
+ scan-build --status-bugs --keep-going -o scan-build-reports \
+ make -j"$NPROC" 2>&1 | tee scan-build-output.log || true
+
+ # Count findings
+ BUGS=$({ grep -c 'warning:' scan-build-output.log 2>/dev/null || true; })
+ echo "scan-build found $BUGS warnings"
+ echo "SCAN_BUGS=$BUGS" >> "$GITHUB_ENV"
+ continue-on-error: true
- # Upload scan-build reports
- name: Upload scan-build reports
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: scan-build-reports
path: Build/scan-build-reports
+ if-no-files-found: warn
- # Upload built binaries as artifacts
- name: Upload build artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: master-build-linux
- path: Build
+ path: |
+ Build/**/*.so
+ Build/**/*.a
+ Build/**/Icc*
+ LICENSE.md
+ if-no-files-found: warn
- # Upload build logs
- name: Upload build logs
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: build-logs
- path: Build/CMakeCache.txt
+ path: |
+ Build/CMakeCache.txt
+ Build/scan-build-output.log
+
- name: Summary Report
if: always()
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
run: |
- echo "### Build Summary" >> $GITHUB_STEP_SUMMARY
- echo "- Build Directory: Build/" >> $GITHUB_STEP_SUMMARY
- echo "- Artifacts Uploaded: iccdev-linux-clang" >> $GITHUB_STEP_SUMMARY
- echo "- Status: Success" >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ SANITIZER=".github/scripts/sanitize-sed.sh"
+ if [[ -f "$SANITIZER" ]]; then
+ # shellcheck disable=SC1090
+ source "$SANITIZER"
+ else
+ sanitize_line() { printf '%s' "$1"; }
+ fi
+
+ BUGS="${SCAN_BUGS:-0}"
+ REPORT_COUNT=$(find Build/scan-build-reports -name '*.html' 2>/dev/null | wc -l | tr -d ' ')
+
+ {
+ echo "### 🧠 Scan-Build Summary"
+ echo ""
+ echo "| Metric | Value |"
+ echo "|--------|-------|"
+ echo "| Parallel jobs | $(nproc) |"
+ echo "| Warnings logged | $(sanitize_line "$BUGS") |"
+ echo "| HTML reports | $(sanitize_line "$REPORT_COUNT") |"
+ echo "| Status | ${{ job.status }} |"
+ echo ""
+ } >> "$GITHUB_STEP_SUMMARY"
\ No newline at end of file
diff --git a/.github/workflows/brew-testing.yml b/.github/workflows/brew-testing.yml
new file mode 100644
index 000000000..8035e01ba
--- /dev/null
+++ b/.github/workflows/brew-testing.yml
@@ -0,0 +1,308 @@
+###############################################################
+# Last Updated: 2026-02-08 15:08:29 UTC by David Hoyt
+#
+# Intent: Test brew inatall dependencies
+#
+#
+# Validate jpeg-turbo brew dependency and
+# cmake configuration across macOS and Linux
+#
+###############################################################
+
+name: brew-testing
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch:
+
+jobs:
+ macos:
+ name: "macOS Clang (jpeg-turbo)"
+ runs-on: macos-latest
+ timeout-minutes: 20
+ steps:
+ - name: Checkout
+ uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ echo "Installing Homebrew dependencies..."
+ brew install libpng nlohmann-json libxml2 wxwidgets libtiff jpeg-turbo || echo "⚠️ Some dependencies might already be installed."
+ echo "### Dependency Versions" >> $GITHUB_STEP_SUMMARY
+ echo "| Package | Version |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------|---------|" >> $GITHUB_STEP_SUMMARY
+ for pkg in libpng nlohmann-json libxml2 wxwidgets libtiff jpeg-turbo; do
+ ver=$(brew info --json=v2 "$pkg" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['formulae'][0]['versions']['stable'])" 2>/dev/null || echo "N/A")
+ echo "| $pkg | $ver |" >> $GITHUB_STEP_SUMMARY
+ done
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "✔ Dependency installation complete."
+
+ - name: Verify jpeg-turbo Paths
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ PREFIX=$(brew --prefix jpeg-turbo)
+ echo "jpeg-turbo prefix: $PREFIX"
+ echo "### jpeg-turbo Path Validation" >> $GITHUB_STEP_SUMMARY
+ echo "| Check | Result |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+
+ if [ -d "$PREFIX/include" ]; then
+ echo "| Include dir exists | ✅ $PREFIX/include |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| Include dir exists | ❌ missing |" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+
+ if [ -f "$PREFIX/lib/libjpeg.dylib" ]; then
+ echo "| libjpeg.dylib exists | ✅ $PREFIX/lib/libjpeg.dylib |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| libjpeg.dylib exists | ❌ missing |" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+
+ if [ -f "$PREFIX/include/jpeglib.h" ]; then
+ echo "| jpeglib.h exists | ✅ found |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| jpeglib.h exists | ❌ missing |" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: CMake Configure
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ sudo rm -rf /Library/Frameworks/Mono.framework/Headers/png.h
+ echo 'export PATH="/opt/homebrew/opt/jpeg-turbo/bin:$PATH"' >> /Users/runner/.bash_profile
+ export CPPFLAGS="-I/opt/homebrew/opt/jpeg-turbo/include"
+ export PKG_CONFIG_PATH="/opt/homebrew/opt/jpeg-turbo/lib/pkgconfig"
+ export CFLAGS="-I$(brew --prefix libpng)/include -I$(brew --prefix jpeg-turbo)/include"
+ export LDFLAGS="-L$(brew --prefix libpng)/lib -L$(brew --prefix jpeg-turbo)/lib"
+ cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DJPEG_LIBRARY=$(brew --prefix jpeg-turbo)/lib/libjpeg.dylib \
+ -DJPEG_INCLUDE_DIR=$(brew --prefix jpeg-turbo)/include \
+ -Wno-dev Cmake/
+ echo "### macOS CMake Configuration" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ grep -E 'JPEG_|JPEGLIB' CMakeCache.txt >> $GITHUB_STEP_SUMMARY || echo "No JPEG cache entries" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make -j$(sysctl -n hw.ncpu)
+ cd ../Testing/
+ for d in ../Build/Tools/*; do
+ [ -d "$d" ] && export PATH="$(realpath "$d"):$PATH"
+ done
+ sh CreateAllProfiles.sh
+ sh RunTests.sh
+ PROFILE_COUNT=$(find . -iname "*.icc" | wc -l | tr -d ' ')
+ echo "### macOS Build Results" >> $GITHUB_STEP_SUMMARY
+ echo "- ✅ Build completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "- ICC profiles generated: $PROFILE_COUNT" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Verify JPEG Linkage
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ JPEG_DUMP="Build/Tools/IccJpegDump/iccJpegDump"
+ echo "### JPEG Binary Linkage" >> $GITHUB_STEP_SUMMARY
+ if [ -f "$JPEG_DUMP" ]; then
+ echo "| Binary | Linked JPEG Library |" >> $GITHUB_STEP_SUMMARY
+ echo "|--------|---------------------|" >> $GITHUB_STEP_SUMMARY
+ LINKED=$(otool -L "$JPEG_DUMP" | grep -i jpeg || echo "none found")
+ echo "| iccJpegDump | $LINKED |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- ⚠️ iccJpegDump binary not found" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Summary Report
+ if: always()
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ echo "### macOS Final Status" >> $GITHUB_STEP_SUMMARY
+ if test -f Build/CMakeCache.txt; then
+ echo "- ✅ CMakeCache.txt present" >> $GITHUB_STEP_SUMMARY
+ echo "- ✅ jpeg-turbo integration validated" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- ❌ Build failed" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ linux:
+ name: "Linux ${{ matrix.compiler }} (jpeg-turbo)"
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ compiler: [gcc, clang]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential cmake gcc g++ clang clang-tools \
+ libpng-dev libxml2 libxml2-dev libtiff-dev libjpeg-dev \
+ nlohmann-json3-dev libwxgtk3.2-dev wx-common \
+ python3 python3-pip curl git llvm
+ echo "### Linux Dependencies" >> $GITHUB_STEP_SUMMARY
+ echo "| Package | Version |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------|---------|" >> $GITHUB_STEP_SUMMARY
+ for pkg in libjpeg-dev libpng-dev libtiff-dev libxml2-dev; do
+ ver=$(dpkg -s "$pkg" 2>/dev/null | grep '^Version:' | cut -d' ' -f2 || echo "N/A")
+ echo "| $pkg | $ver |" >> $GITHUB_STEP_SUMMARY
+ done
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Set Compiler
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ if [ "${{ matrix.compiler }}" = "gcc" ]; then
+ echo "CC=gcc" >> $GITHUB_ENV
+ echo "CXX=g++" >> $GITHUB_ENV
+ else
+ echo "CC=clang" >> $GITHUB_ENV
+ echo "CXX=clang++" >> $GITHUB_ENV
+ fi
+
+ - name: CMake Configure
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local \
+ -DCMAKE_BUILD_TYPE=Release \
+ -Wno-dev Cmake/
+ echo "### Linux CMake Configuration (${{ matrix.compiler }})" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ grep -E 'JPEG_|JPEGLIB' CMakeCache.txt >> $GITHUB_STEP_SUMMARY || echo "No JPEG cache entries" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make -j$(nproc)
+ cd ../Testing/
+ for d in ../Build/Tools/*; do
+ [ -d "$d" ] && export PATH="$(realpath "$d"):$PATH"
+ done
+ sh CreateAllProfiles.sh
+ sh RunTests.sh
+ PROFILE_COUNT=$(find . -iname "*.icc" | wc -l | tr -d ' ')
+ echo "### Linux Build Results (${{ matrix.compiler }})" >> $GITHUB_STEP_SUMMARY
+ echo "- ✅ Build completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "- ICC profiles generated: $PROFILE_COUNT" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Verify JPEG Linkage
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ JPEG_DUMP="Build/Tools/IccJpegDump/iccJpegDump"
+ echo "### JPEG Binary Linkage (${{ matrix.compiler }})" >> $GITHUB_STEP_SUMMARY
+ if [ -f "$JPEG_DUMP" ]; then
+ echo "| Binary | Linked JPEG Library |" >> $GITHUB_STEP_SUMMARY
+ echo "|--------|---------------------|" >> $GITHUB_STEP_SUMMARY
+ LINKED=$(ldd "$JPEG_DUMP" | grep -i jpeg || echo "none found")
+ echo "| iccJpegDump | $LINKED |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- ⚠️ iccJpegDump binary not found" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ - name: Summary Report
+ if: always()
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ echo "### Linux Final Status (${{ matrix.compiler }})" >> $GITHUB_STEP_SUMMARY
+ if test -f Build/CMakeCache.txt; then
+ echo "- ✅ CMakeCache.txt present" >> $GITHUB_STEP_SUMMARY
+ echo "- ✅ JPEG integration validated" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- ❌ Build failed" >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/.github/workflows/ci-clang-tidy-coreguidelines.yml b/.github/workflows/ci-clang-tidy-coreguidelines.yml
index 56ccab8a5..9ac0b292e 100644
--- a/.github/workflows/ci-clang-tidy-coreguidelines.yml
+++ b/.github/workflows/ci-clang-tidy-coreguidelines.yml
@@ -1,21 +1,16 @@
###############################################################
#
-## Copyright (©) 2025 International Color Consortium.
-## All rights reserved.
+## Copyright (©) 2025 International Color Consortium.
+## All rights reserved.
## https://color.org
#
#
-## Intent:iccdev-tidy-coreguidelines
-#
-## Last Updated: 02-DEC-2025 1700Z by David Hoyt
-# Add Permission Block
-#
-#
-#
-#
-#
-#
+## Intent: iccdev-tidy-coreguidelines
#
+## Last Updated: 2026-02-08 15:27:00 UTC
+# Analyze checked-out source (not external clone),
+# per-component and per-check breakdown,
+# hardened shell, separated steps.
#
###############################################################
@@ -24,14 +19,14 @@ name: iccdev-tidy-coreguidelines
permissions:
contents: read
pull-requests: read
-
+
on:
workflow_dispatch:
jobs:
- build:
+ clang-tidy:
runs-on: ubuntu-latest
- timeout-minutes: 20
+ timeout-minutes: 30
strategy:
matrix:
build_type: [Release]
@@ -39,91 +34,237 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+
- name: Install dependencies
- shell: bash
+ shell: bash --noprofile --norc {0}
env:
- BASH_ENV: /dev/null
+ BASH_ENV: /dev/null
run: |
set -euo pipefail
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --add safe.directory "$PWD"
git config --global credential.helper ""
-
- # Clear the in-shell GITHUB_TOKEN
unset GITHUB_TOKEN || true
sudo apt-get update
- sudo apt-get install -y build-essential cmake git pkg-config
sudo apt-get install -y \
- build-essential cmake gcc g++ clang clang-tools \
- libpng-dev libxml2 libxml2-dev libtiff-dev \
+ build-essential cmake gcc g++ clang clang-tools clang-tidy \
+ libpng-dev libxml2 libxml2-dev libtiff-dev libjpeg-dev \
nlohmann-json3-dev libwxgtk3.2-dev wx-common \
- python3 python3-pip curl git llvm zsh
- git clone https://github.com/InternationalColorConsortium/iccDEV.git
- cd iccDEV
- BUILD_DIR="Build"
- CHECKS="modernize-*,readability-*,cppcoreguidelines-*"
- OUTDIR="tidy-out"
- mkdir -p "${OUTDIR}"
+ python3 curl git llvm jq
+ echo "### Environment" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Tool | Version |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|------|---------|" >> "$GITHUB_STEP_SUMMARY"
+ echo "| clang-tidy | $(clang-tidy --version 2>&1 | head -1) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| cmake | $(cmake --version | head -1) |" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+
+ - name: CMake Configure (compile_commands.json)
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ cmake Cmake \
+ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+ -Wno-dev
+ echo "✅ compile_commands.json generated with $(jq length compile_commands.json) translation units"
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
cd Build
- cmake Cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
make -j$(nproc)
- cd ..
-
- # Extract translation units
+
+ - name: Run clang-tidy (cppcoreguidelines)
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ BUILD_DIR="Build"
+ CHECKS="-*,cppcoreguidelines-*"
+ OUTDIR="tidy-out/cppcoreguidelines"
+ mkdir -p "$OUTDIR"
+
jq -r '.[].file' "${BUILD_DIR}/compile_commands.json" \
| xargs -I{} -P"$(nproc)" sh -c '
f="{}"
base=$(basename "$f")
log="'"${OUTDIR}"'/${base}.log"
echo "[+] $(date +"%H:%M:%S") START $f"
- run-clang-tidy -p "'"${BUILD_DIR}"'" -checks="'"${CHECKS}"'" "$f" > "$log" 2>&1
+ clang-tidy -p "'"${BUILD_DIR}"'" --checks="'"${CHECKS}"'" "$f" > "$log" 2>&1 || true
echo "[+] $(date +"%H:%M:%S") DONE $f"
'
-
- - name: Upload build artifacts
+
+ - name: Run clang-tidy (modernize)
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ BUILD_DIR="Build"
+ CHECKS="-*,modernize-*"
+ OUTDIR="tidy-out/modernize"
+ mkdir -p "$OUTDIR"
+
+ jq -r '.[].file' "${BUILD_DIR}/compile_commands.json" \
+ | xargs -I{} -P"$(nproc)" sh -c '
+ f="{}"
+ base=$(basename "$f")
+ log="'"${OUTDIR}"'/${base}.log"
+ echo "[+] $(date +"%H:%M:%S") START $f"
+ clang-tidy -p "'"${BUILD_DIR}"'" --checks="'"${CHECKS}"'" "$f" > "$log" 2>&1 || true
+ echo "[+] $(date +"%H:%M:%S") DONE $f"
+ '
+
+ - name: Run clang-tidy (readability)
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ BUILD_DIR="Build"
+ CHECKS="-*,readability-*"
+ OUTDIR="tidy-out/readability"
+ mkdir -p "$OUTDIR"
+
+ jq -r '.[].file' "${BUILD_DIR}/compile_commands.json" \
+ | xargs -I{} -P"$(nproc)" sh -c '
+ f="{}"
+ base=$(basename "$f")
+ log="'"${OUTDIR}"'/${base}.log"
+ echo "[+] $(date +"%H:%M:%S") START $f"
+ clang-tidy -p "'"${BUILD_DIR}"'" --checks="'"${CHECKS}"'" "$f" > "$log" 2>&1 || true
+ echo "[+] $(date +"%H:%M:%S") DONE $f"
+ '
+
+ - name: Upload clang-tidy artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: iccdev-clang-tidy-${{ matrix.build_type }}-linux
path: |
- iccDEV/tidy-out/*
+ tidy-out/**/*.log
if-no-files-found: warn
- - name: Generate clang-tidy report summary
- shell: bash
+ - name: Generate report summary
+ if: always()
+ shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
run: |
set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
- echo "## 🧹 Clang-Tidy Summary — iccdev" >> "$GITHUB_STEP_SUMMARY"
- echo "" >> "$GITHUB_STEP_SUMMARY"
+ # Source trusted sanitizer from checked-out workspace
+ SANITIZER=".github/scripts/sanitize-sed.sh"
+ if [[ -f "$SANITIZER" ]]; then
+ # shellcheck disable=SC1090
+ source "$SANITIZER"
+ else
+ escape_html() {
+ local s="$1"
+ s="${s//&/&}"
+ s="${s//<}"
+ s="${s//>/>}"
+ s="${s//\"/"}"
+ s="${s//\'/'}"
+ printf '%s' "$s"
+ }
+ sanitize_line() { escape_html "$1"; }
+ fi
- OUTDIR="iccDEV/tidy-out"
+ echo "## 🧹 Clang-Tidy Analysis Report" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
- if ! [ -d "$OUTDIR" ]; then
+ BASEDIR="tidy-out"
+ if ! [ -d "$BASEDIR" ]; then
echo "**No tidy output directory found.**" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
- total_logs=$(find "$OUTDIR" -type f -name "*.log" | wc -l | tr -d ' ')
- echo "**Total translation units analyzed:** $total_logs" >> "$GITHUB_STEP_SUMMARY"
+ # --- Per-category summary ---
+ echo "### Findings by Check Category" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Category | Warnings | Errors | Files Analyzed |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|----------|----------|--------|----------------|" >> "$GITHUB_STEP_SUMMARY"
+
+ GRAND_WARN=0
+ GRAND_ERR=0
+ for category in cppcoreguidelines modernize readability; do
+ CATDIR="$BASEDIR/$category"
+ if [ -d "$CATDIR" ]; then
+ files=$(find "$CATDIR" -type f -name "*.log" | wc -l | tr -d ' ')
+ warns=$(grep -rch "warning:" "$CATDIR" 2>/dev/null | paste -sd+ | bc 2>/dev/null || echo 0)
+ errs=$({ grep -rh "error:" "$CATDIR" 2>/dev/null || true; } | { grep -vc "warnings and" 2>/dev/null || true; })
+ echo "| $category | $warns | $errs | $files |" >> "$GITHUB_STEP_SUMMARY"
+ GRAND_WARN=$((GRAND_WARN + warns))
+ GRAND_ERR=$((GRAND_ERR + errs))
+ fi
+ done
+ echo "| **Total** | **$GRAND_WARN** | **$GRAND_ERR** | |" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+
+ # --- Per-component breakdown ---
+ echo "### Findings by Source Component" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Component | Warnings |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|-----------|----------|" >> "$GITHUB_STEP_SUMMARY"
- # Count warnings/errors without parsing user-controlled filenames into the shell
- total_issues=$(grep -R "warning:" -H "$OUTDIR" | wc -l | tr -d ' ')
- echo "**Total warnings detected:** $total_issues" >> "$GITHUB_STEP_SUMMARY"
+ for component in IccProfLib IccXML Tools; do
+ count=$({ grep -rh "warning:.*\[" "$BASEDIR" 2>/dev/null || true; } | { grep -c "/${component}/" || true; })
+ echo "| $component | $count |" >> "$GITHUB_STEP_SUMMARY"
+ done
echo "" >> "$GITHUB_STEP_SUMMARY"
- echo "### Sample Files With Most Warnings" >> "$GITHUB_STEP_SUMMARY"
+ # --- Top 15 most frequent check names ---
+ echo "### Top 15 Most Frequent Checks" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Count | Check |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY"
- # Top 10 files — safely aggregated
- grep -R "warning:" -H "$OUTDIR" \
- | cut -d: -f1 \
- | sort \
- | uniq -c \
- | sort -rn \
- | head -n 10 \
- | sed 's/^/ - /' >> "$GITHUB_STEP_SUMMARY"
+ { grep -roh '\[[-a-z]*\]' "$BASEDIR" 2>/dev/null || true; } \
+ | sort | uniq -c | sort -rn | head -15 > /tmp/tidy_checks.txt
+ while read -r cnt chk; do
+ echo "| $cnt | $chk |" >> "$GITHUB_STEP_SUMMARY"
+ done < /tmp/tidy_checks.txt
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ # --- Top 10 files with most warnings ---
+ echo "### Top 10 Files by Warning Count" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
- echo "✔ Report summary generated." >> "$GITHUB_STEP_SUMMARY"
+ echo "| Warnings | File |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|----------|------|" >> "$GITHUB_STEP_SUMMARY"
+
+ { grep -rh "warning:.*\[" "$BASEDIR" 2>/dev/null || true; } \
+ | { grep -oP '[^ ]+\.(cpp|h)' || true; } \
+ | sort | uniq -c | sort -rn | head -10 > /tmp/tidy_files.txt
+ while read -r cnt fname; do
+ echo "| $cnt | $fname |" >> "$GITHUB_STEP_SUMMARY"
+ done < /tmp/tidy_files.txt
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+
+ echo "✔ Report generated." >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/ci-code-coverage.yml b/.github/workflows/ci-code-coverage.yml
new file mode 100644
index 000000000..1b7f0fcb2
--- /dev/null
+++ b/.github/workflows/ci-code-coverage.yml
@@ -0,0 +1,298 @@
+###############################################################
+#
+# Copyright (c) 2026 International Color Consortium.
+# All rights reserved.
+# https://color.org
+#
+# Intent: Build iccDEV with Clang source-based coverage,
+# run full test suite + profile exerciser, generate
+# llvm-cov HTML and lcov summary reports
+#
+# Based on: research/iccanalyzer-lite-coverage-report.yml
+# Adapted: For cfl/ integrated build with ENABLE_COVERAGE
+#
+###############################################################
+
+name: Code Coverage Report
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches: [ master, cfl ]
+
+concurrency:
+ group: coverage-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ coverage:
+ name: Coverage Report
+ runs-on: ubuntu-24.04
+ timeout-minutes: 30
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install dependencies
+ env:
+ DEBIAN_FRONTEND: noninteractive
+ run: |
+ set -euo pipefail
+ sudo apt-get update -qq
+ sudo apt-get install -y --no-install-recommends \
+ build-essential cmake \
+ clang-18 llvm-18 \
+ libxml2-dev libtiff-dev libjpeg-dev libpng-dev \
+ zlib1g-dev nlohmann-json3-dev \
+ lcov
+
+ - name: Build with coverage instrumentation
+ env:
+ CC: clang-18
+ CXX: clang++-18
+ run: |
+ set -euo pipefail
+ mkdir -p build-coverage && cd build-coverage
+ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_C_COMPILER=clang-18 \
+ -DCMAKE_CXX_COMPILER=clang++-18 \
+ -DENABLE_COVERAGE=ON \
+ -DENABLE_TOOLS=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_TESTS=OFF \
+ -Wno-dev
+ make -j$(nproc)
+
+ # Verify coverage instrumentation
+ TOOL=$(find . -name "iccDumpProfile" -type f -executable | head -1)
+ if [ -z "$TOOL" ]; then
+ echo "::error::No iccDumpProfile binary found"
+ exit 1
+ fi
+ echo "tool_path=$(pwd)/$TOOL" >> $GITHUB_ENV
+ echo "build_dir=$(pwd)" >> $GITHUB_ENV
+
+ # Verify instrumentation flags
+ echo "Checking coverage flags..."
+ grep -r "fprofile-instr-generate" CMakeFiles/*/flags.make | head -3 || true
+
+ # Count libraries and tools
+ LIB_COUNT=$(find . -name "*.a" | wc -l)
+ TOOL_COUNT=$(find . -path "*/Tools/*" -type f -executable | wc -l)
+ echo "Built: $LIB_COUNT libraries, $TOOL_COUNT tools"
+
+ - name: Create test profiles
+ run: |
+ set -euo pipefail
+ export LLVM_PROFILE_FILE="${{ github.workspace }}/profraw/create-%p-%m.profraw"
+ mkdir -p "${{ github.workspace }}/profraw"
+
+ # CreateAllProfiles.sh expects ../Build/Tools — symlink build-coverage
+ ln -sfn build-coverage Build
+
+ # Add build tools to PATH
+ BUILD_DIR="${{ env.build_dir }}"
+ for d in $(find "$BUILD_DIR" -path "*/Tools/*" -type f -executable -exec dirname {} \; | sort -u); do
+ PATH="$d:$PATH"
+ done
+ export PATH
+
+ cd Testing
+ bash CreateAllProfiles.sh 2>&1 | tail -20
+ PROFILE_COUNT=$(find . -name "*.icc" | wc -l)
+ echo "Created $PROFILE_COUNT ICC profiles"
+ echo "profile_count=$PROFILE_COUNT" >> $GITHUB_ENV
+
+ - name: Run test suite
+ run: |
+ set -euo pipefail
+ export LLVM_PROFILE_FILE="${{ github.workspace }}/profraw/test-%p-%m.profraw"
+
+ BUILD_DIR="${{ env.build_dir }}"
+ for d in $(find "$BUILD_DIR" -path "*/Tools/*" -type f -executable -exec dirname {} \; | sort -u); do
+ PATH="$d:$PATH"
+ done
+ export PATH
+
+ cd Testing
+ bash RunTests.sh 2>&1 | tail -40
+ echo "Test suite complete"
+
+ - name: Exercise all profiles with IccDumpProfile
+ run: |
+ set -euo pipefail
+ export LLVM_PROFILE_FILE="${{ github.workspace }}/profraw/dump-%p-%m.profraw"
+
+ TOOL="${{ env.tool_path }}"
+ TOTAL=0
+ PASS=0
+ FAIL=0
+
+ while IFS= read -r icc; do
+ TOTAL=$((TOTAL + 1))
+ if timeout 10s "$TOOL" "$icc" > /dev/null 2>&1; then
+ PASS=$((PASS + 1))
+ else
+ FAIL=$((FAIL + 1))
+ fi
+ done < <(find Testing -name "*.icc" -type f)
+
+ echo "IccDumpProfile: $TOTAL profiles, $PASS pass, $FAIL fail"
+ echo "dump_total=$TOTAL" >> $GITHUB_ENV
+ echo "dump_pass=$PASS" >> $GITHUB_ENV
+ echo "dump_fail=$FAIL" >> $GITHUB_ENV
+
+ - name: Exercise XML round-trip tools
+ run: |
+ set -euo pipefail
+ export LLVM_PROFILE_FILE="${{ github.workspace }}/profraw/xml-%p-%m.profraw"
+
+ BUILD_DIR="${{ env.build_dir }}"
+ TO_XML=$(find "$BUILD_DIR" -name "iccToXml" -type f -executable -print -quit)
+ FROM_XML=$(find "$BUILD_DIR" -name "iccFromXml" -type f -executable -print -quit)
+ ROUNDTRIP=$(find "$BUILD_DIR" -name "iccRoundTrip" -type f -executable -print -quit)
+
+ CONVERTED=0
+ # Convert a sample of profiles to XML and back
+ mapfile -t ICC_FILES < <(find Testing -name "*.icc" -type f | shuf -n 50)
+ for icc in "${ICC_FILES[@]}"; do
+ XML_OUT="/tmp/$(basename "$icc" .icc).xml"
+ ICC_OUT="/tmp/$(basename "$icc")"
+ if [ -n "$TO_XML" ] && timeout 10s "$TO_XML" "$icc" "$XML_OUT" > /dev/null 2>&1; then
+ CONVERTED=$((CONVERTED + 1))
+ if [ -n "$FROM_XML" ]; then
+ timeout 10s "$FROM_XML" "$XML_OUT" "$ICC_OUT" > /dev/null 2>&1 || true
+ fi
+ fi
+ rm -f "$XML_OUT" "$ICC_OUT"
+ done
+
+ # Run roundtrip on sample profiles
+ if [ -n "$ROUNDTRIP" ]; then
+ mapfile -t RT_FILES < <(find Testing -name "*.icc" -type f | shuf -n 30)
+ for icc in "${RT_FILES[@]}"; do
+ timeout 10s "$ROUNDTRIP" "$icc" > /dev/null 2>&1 || true
+ done
+ fi
+
+ echo "XML round-trip: $CONVERTED conversions complete"
+
+ - name: Merge profdata and generate report
+ run: |
+ set -euo pipefail
+ mkdir -p coverage-report
+
+ # Merge all .profraw files
+ PROFRAW_COUNT=$(find "${{ github.workspace }}/profraw" -name "*.profraw" 2>/dev/null | wc -l)
+ echo "Found $PROFRAW_COUNT .profraw files"
+
+ if [ "$PROFRAW_COUNT" -eq 0 ]; then
+ echo "::error::No profraw files generated — coverage instrumentation may have failed"
+ exit 1
+ fi
+
+ llvm-profdata-18 merge \
+ -sparse \
+ "${{ github.workspace }}"/profraw/*.profraw \
+ -o coverage-report/merged.profdata
+
+ echo "Merged profdata: $(stat -c%s coverage-report/merged.profdata) bytes"
+
+ # Find all instrumented binaries
+ BUILD_DIR="${{ env.build_dir }}"
+ BINARIES=$(find "$BUILD_DIR" -type f -executable \( -path "*/Tools/*" -o -name "*.a" \) | head -20)
+ MAIN_BIN="${{ env.tool_path }}"
+
+ # Build object list for llvm-cov (all tool binaries)
+ OBJECT_ARGS=""
+ for bin in $(find "$BUILD_DIR" -path "*/Tools/*" -type f -executable); do
+ if [ "$bin" != "$MAIN_BIN" ]; then
+ OBJECT_ARGS="$OBJECT_ARGS -object=$bin"
+ fi
+ done
+
+ # Generate text report
+ llvm-cov-18 report \
+ "$MAIN_BIN" $OBJECT_ARGS \
+ -instr-profile=coverage-report/merged.profdata \
+ -ignore-filename-regex='(third_party|/usr/|test)' \
+ 2>&1 | tee coverage-report/coverage-summary.txt
+
+ # Generate HTML report
+ llvm-cov-18 show \
+ "$MAIN_BIN" $OBJECT_ARGS \
+ -instr-profile=coverage-report/merged.profdata \
+ -format=html \
+ -output-dir=coverage-report/html \
+ -show-line-counts-or-regions \
+ -show-expansions \
+ -ignore-filename-regex='(third_party|/usr/|test)' \
+ 2>&1 | tail -5
+
+ # Generate lcov-compatible export
+ llvm-cov-18 export \
+ "$MAIN_BIN" $OBJECT_ARGS \
+ -instr-profile=coverage-report/merged.profdata \
+ -format=lcov \
+ -ignore-filename-regex='(third_party|/usr/|test)' \
+ > coverage-report/lcov.info 2>/dev/null || true
+
+ # Summary stats
+ if [ -d coverage-report/html ]; then
+ HTML_COUNT=$(find coverage-report/html -name "*.html" | wc -l)
+ echo "HTML report: $HTML_COUNT files"
+ fi
+ LCOV_RECORDS=$(grep -c "^SF:" coverage-report/lcov.info 2>/dev/null || echo 0)
+ echo "LCOV: $LCOV_RECORDS source file records"
+
+ - name: Report to summary
+ if: always()
+ run: |
+ set -euo pipefail
+ {
+ echo "## Code Coverage Report"
+ echo ""
+ echo "### Test Execution"
+ echo "| Metric | Value |"
+ echo "|--------|-------|"
+ echo "| ICC Profiles Created | ${{ env.profile_count }} |"
+ echo "| IccDumpProfile Total | ${{ env.dump_total }} |"
+ echo "| IccDumpProfile Pass | ${{ env.dump_pass }} |"
+ echo "| IccDumpProfile Fail | ${{ env.dump_fail }} |"
+ echo ""
+ echo "### Coverage Summary"
+ echo '```'
+ } >> $GITHUB_STEP_SUMMARY
+
+ if [ -f coverage-report/coverage-summary.txt ]; then
+ cat coverage-report/coverage-summary.txt >> $GITHUB_STEP_SUMMARY
+ else
+ echo "No coverage data available" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ {
+ echo '```'
+ echo ""
+ echo "### Artifacts"
+ echo "- \`coverage-report\`: llvm-cov HTML report, lcov.info, text summary"
+ } >> $GITHUB_STEP_SUMMARY
+
+ - name: Upload coverage report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage-report/
+ retention-days: 30
+ if-no-files-found: warn
diff --git a/.github/workflows/ci-comprehensive-build-test.yml b/.github/workflows/ci-comprehensive-build-test.yml
new file mode 100644
index 000000000..95abed602
--- /dev/null
+++ b/.github/workflows/ci-comprehensive-build-test.yml
@@ -0,0 +1,1055 @@
+###############################################################
+# Copyright (c) 2026 International Color Consortium
+#
+# Intent: ci-build-matrix
+# Tests all build configurations, sanitizers, and options
+# This workflow will replace ci-pr-actions in time
+#
+# Last Updated: 2026-02-08 15:24:37 UTC by David Hoyt
+#
+# All PR _must_ PASS this workflow
+#
+#
+#
+#
+###############################################################
+
+name: "ci-build-matrix"
+
+permissions:
+ contents: read
+ pull-requests: read
+
+on:
+ workflow_dispatch:
+
+concurrency:
+ group: comprehensive-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+env:
+ GH_REPO: ${{ github.repository }}
+ GH_NO_UPDATE_NOTIFIER: 1
+ GH_PROMPT_DISABLED: 1
+ RUN_URL: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}
+
+jobs:
+ #############################################################################
+ # Job 1: Standard Project Builds (Tools Only - No Fuzzing)
+ #############################################################################
+ standard-builds:
+ name: "Standard • ${{ matrix.os }} • ${{ matrix.compiler }} • ${{ matrix.build_type }}"
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 25
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ compiler: [gcc, clang]
+ build_type: [Release, Debug, RelWithDebInfo]
+ exclude:
+ - os: macos-latest
+ compiler: gcc
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Cache Dependencies (Linux)
+ if: runner.os == 'Linux'
+ uses: actions/cache@v4
+ with:
+ path: |
+ /var/cache/apt/archives
+ ~/.cache/ccache
+ key: ${{ runner.os }}-deps-${{ matrix.compiler }}-${{ hashFiles('Build/Cmake/CMakeLists.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-deps-${{ matrix.compiler }}-
+ ${{ runner.os }}-deps-
+
+ - name: Cache Dependencies (macOS)
+ if: runner.os == 'macOS'
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/Library/Caches/Homebrew
+ ~/.cache/ccache
+ key: ${{ runner.os }}-deps-${{ matrix.compiler }}-${{ hashFiles('Build/Cmake/CMakeLists.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-deps-${{ matrix.compiler }}-
+ ${{ runner.os }}-deps-
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies (Linux)
+ if: runner.os == 'Linux'
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake gcc g++ clang ccache \
+ libpng-dev libxml2-dev libtiff-dev \
+ nlohmann-json3-dev
+ ccache --zero-stats || true
+
+ - name: Install Dependencies (macOS)
+ if: runner.os == 'macOS'
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ brew install cmake llvm libpng libtiff libxml2 nlohmann-json ccache
+ ccache --zero-stats || true
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i.bak 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/ # find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i.bak 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/ # ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i.bak 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Set Compiler
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ if [ "${{ matrix.compiler }}" = "gcc" ]; then
+ echo "CC=gcc" >> $GITHUB_ENV
+ echo "CXX=g++" >> $GITHUB_ENV
+ else
+ echo "CC=clang" >> $GITHUB_ENV
+ echo "CXX=clang++" >> $GITHUB_ENV
+ fi
+
+ - name: Configure CMake
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build && cd Build
+ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
+ -DENABLE_SHARED_LIBS=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_TOOLS=ON
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ # Enable ccache if available
+ if command -v ccache >/dev/null 2>&1; then
+ export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
+ export CC="ccache ${{ matrix.compiler == 'gcc' && 'gcc' || 'clang' }}"
+ export CXX="ccache ${{ matrix.compiler == 'gcc' && 'g++' || 'clang++' }}"
+ fi
+
+ cd Build
+ echo "::group::Build Output"
+ BUILD_START=$(date +%s)
+ make -j$(nproc || sysctl -n hw.logicalcpu) 2>&1 | tee build.log
+ BUILD_END=$(date +%s)
+ echo "::endgroup::"
+
+ BUILD_TIME=$((BUILD_END - BUILD_START))
+ echo "::notice title=Build Time::Build completed in ${BUILD_TIME}s"
+
+ if grep -iE '(error|failed|undefined reference|cannot find)' build.log; then
+ echo "::error title=Build Failed::Build errors detected in log"
+ exit 1
+ fi
+
+ # Show ccache stats if available
+ if command -v ccache >/dev/null 2>&1; then
+ echo "::group::ccache Statistics"
+ ccache --show-stats || true
+ echo "::endgroup::"
+ fi
+
+ - name: Upload Build Artifacts (on failure)
+ if: failure()
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ with:
+ name: build-logs-${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.build_type }}
+ path: |
+ Build/build.log
+ Build/CMakeCache.txt
+ Build/CMakeFiles/CMakeError.log
+ Build/CMakeFiles/CMakeOutput.log
+ retention-days: 7
+
+ - name: Upload Build Artifacts (on success)
+ if: success()
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ with:
+ name: build-${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.build_type }}
+ path: |
+ Build/Tools/IccApplyNamedCmm/**/IccApplyNamedCmm
+ Build/Tools/IccDumpProfile/**/IccDumpProfile
+ Build/Tools/IccFromXml/**/IccFromXml
+ Build/Tools/IccToXml/**/IccToXml
+ Build/IccProfLib/libIccProfLib2*
+ Build/IccXML/libIccXML2*
+ if-no-files-found: warn
+ retention-days: 14
+ compression-level: 9
+
+ - name: Test Profiles
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Testing
+ for d in ../Build/Tools/*; do
+ [ -d "$d" ] && export PATH="$(realpath "$d"):$PATH"
+ done
+ sh CreateAllProfiles.sh
+ PROFILE_COUNT=$(find . -name "*.icc" | wc -l)
+ echo "Created $PROFILE_COUNT profiles"
+ if [ "$PROFILE_COUNT" -eq 0 ]; then
+ echo "ERROR: No profiles created"
+ exit 1
+ fi
+
+ #############################################################################
+ # Job 2: Fuzzer Builds (Debug + Full Instrumentation)
+ #############################################################################
+ fuzzer-builds:
+ name: "Fuzzer • ${{ matrix.os }} • Debug+Instrumentation"
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Cache Dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ /var/cache/apt/archives
+ ~/.cache/ccache
+ key: fuzzer-deps-${{ hashFiles('Build/Cmake/CMakeLists.txt', 'Testing/Fuzzing/CMakeLists.txt') }}
+ restore-keys: |
+ fuzzer-deps-
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake clang llvm ccache \
+ libpng-dev libxml2-dev libtiff-dev nlohmann-json3-dev
+ ccache --zero-stats || true
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/# find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Configure Fuzzer Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build-fuzzing && cd Build-fuzzing
+ CC=clang CXX=clang++ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DENABLE_FUZZING=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_SHARED_LIBS=ON \
+ -DENABLE_TOOLS=OFF
+
+ - name: Build Fuzzers
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ # Enable ccache
+ if command -v ccache >/dev/null 2>&1; then
+ export PATH="/usr/lib/ccache:$PATH"
+ export CC="ccache clang"
+ export CXX="ccache clang++"
+ fi
+
+ cd Build-fuzzing
+ echo "::group::Fuzzer Build Output"
+ BUILD_START=$(date +%s)
+ make -j$(nproc) 2>&1 | tee build-fuzzing.log
+ BUILD_END=$(date +%s)
+ echo "::endgroup::"
+
+ BUILD_TIME=$((BUILD_END - BUILD_START))
+ echo "::notice title=Fuzzer Build Time::Fuzzer build completed in ${BUILD_TIME}s"
+
+ if grep -iE '(error|failed|undefined reference|cannot find)' build-fuzzing.log; then
+ echo "::error title=Fuzzer Build Failed::Fuzzer build errors detected"
+ exit 1
+ fi
+
+ # Show ccache stats
+ if command -v ccache >/dev/null 2>&1; then
+ echo "::group::ccache Statistics"
+ ccache --show-stats || true
+ echo "::endgroup::"
+ fi
+
+ - name: Verify Fuzzer Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ FUZZER_COUNT=$(ls Build-fuzzing/Testing/Fuzzing/icc_*_fuzzer 2>/dev/null | wc -l)
+ echo "::notice title=Fuzzer Count::Built $FUZZER_COUNT fuzzers"
+ # Note: Fuzzer count varies by platform and configuration
+ # Just verify at least 1 fuzzer was built
+ if [ "$FUZZER_COUNT" -lt 1 ]; then
+ echo "::error title=Fuzzer Verification Failed::Expected at least 1 fuzzer, got $FUZZER_COUNT"
+ exit 1
+ fi
+
+ - name: Upload Fuzzer Build Artifacts (on failure)
+ if: failure()
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ with:
+ name: fuzzer-build-logs
+ path: |
+ Build-fuzzing/build-fuzzing.log
+ Build-fuzzing/CMakeCache.txt
+ Build-fuzzing/CMakeFiles/CMakeError.log
+ Build-fuzzing/CMakeFiles/CMakeOutput.log
+ Build-fuzzing/Testing/Fuzzing/*.log
+ retention-days: 7
+
+ - name: Upload Fuzzer Executables (on success)
+ if: success()
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ with:
+ name: fuzzer-executables-debug-instrumentation
+ path: Build-fuzzing/Testing/Fuzzing/*_fuzzer
+ retention-days: 14
+ compression-level: 9
+
+ - name: Test Fuzzers
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build-fuzzing/Testing/Fuzzing
+
+ # Verify all 14 fuzzers are built and executable
+ EXPECTED_FUZZERS="icc_apply_fuzzer icc_applynamedcmm_fuzzer icc_applyprofiles_fuzzer icc_calculator_fuzzer icc_dump_fuzzer icc_fromxml_fuzzer icc_io_fuzzer icc_link_fuzzer icc_multitag_fuzzer icc_profile_fuzzer icc_roundtrip_fuzzer icc_spectral_fuzzer icc_toxml_fuzzer icc_v5dspobs_fuzzer"
+
+ PASS=0
+ FAIL=0
+ for f in $EXPECTED_FUZZERS; do
+ if [ -x "$f" ]; then
+ PASS=$((PASS + 1))
+ echo "✓ $f"
+ else
+ FAIL=$((FAIL + 1))
+ echo "✗ $f (not found or not executable)"
+ fi
+ done
+
+ echo "::notice title=Fuzzer Build Verification::$PASS of 14 fuzzers built successfully"
+ if [ "$FAIL" -gt 0 ]; then
+ echo "::error title=Fuzzer Build Failed::$FAIL fuzzers missing or not executable"
+ exit 1
+ fi
+
+ #############################################################################
+ # Job 3: Sanitizer Builds (Individual Sanitizers)
+ #############################################################################
+ sanitizer-builds:
+ name: "Sanitizer • ${{ matrix.sanitizer }} • ${{ matrix.build_type }}"
+ runs-on: ubuntu-latest
+ timeout-minutes: 25
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ build_type: [Debug, RelWithDebInfo]
+ sanitizer:
+ - asan
+ - ubsan
+ - asan-ubsan
+ exclude:
+ - sanitizer: asan-ubsan
+ build_type: RelWithDebInfo
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Cache Dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ /var/cache/apt/archives
+ ~/.cache/ccache
+ key: sanitizer-deps-${{ matrix.sanitizer }}-${{ hashFiles('Build/Cmake/CMakeLists.txt') }}
+ restore-keys: |
+ sanitizer-deps-${{ matrix.sanitizer }}-
+ sanitizer-deps-
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake clang llvm \
+ libpng-dev libxml2-dev libtiff-dev nlohmann-json3-dev
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/# find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Configure with Sanitizer
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build && cd Build
+
+ case "${{ matrix.sanitizer }}" in
+ asan)
+ OPTS="-DENABLE_ASAN=ON"
+ ;;
+ ubsan)
+ OPTS="-DENABLE_UBSAN=ON"
+ ;;
+ asan-ubsan)
+ OPTS="-DENABLE_ASAN=ON -DENABLE_UBSAN=ON"
+ ;;
+ esac
+
+ CC=clang CXX=clang++ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
+ $OPTS
+
+ - name: Build with Sanitizer
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make -j$(nproc) 2>&1 | tee build.log
+ if grep -iE 'error:' build.log; then
+ echo "ERROR: Build failed"
+ exit 1
+ fi
+
+ - name: Test with Sanitizer
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Testing
+ for d in ../Build/Tools/*; do
+ [ -d "$d" ] && export PATH="$(realpath "$d"):$PATH"
+ done
+ timeout 300 sh CreateAllProfiles.sh || echo "Profile creation timed out (acceptable for sanitizer builds)"
+
+ - name: Upload Sanitizer Build Artifacts
+ if: always()
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
+ with:
+ name: sanitizer-${{ matrix.sanitizer }}-${{ matrix.build_type }}
+ path: |
+ Build/Tools/IccApplyNamedCmm/IccApplyNamedCmm/IccApplyNamedCmm
+ Build/Tools/IccDumpProfile/IccDumpProfile/IccDumpProfile
+ Build/IccProfLib/libIccProfLib2*
+ Build/build.log
+ if-no-files-found: warn
+ retention-days: 7
+ compression-level: 9
+
+ #############################################################################
+ # Job 4: Build Option Matrix (Test Each Option Independently)
+ #############################################################################
+ option-matrix:
+ name: "Option • ${{ matrix.option_name }}"
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - option_name: "VERBOSE_CONFIG=ON"
+ cmake_opts: "-DVERBOSE_CONFIG=ON"
+ - option_name: "ENABLE_COVERAGE=ON"
+ cmake_opts: "-DENABLE_COVERAGE=ON"
+ - option_name: "ICC_ENABLE_ASSERTS=ON"
+ cmake_opts: "-DICC_ENABLE_ASSERTS=ON"
+ - option_name: "ICC_TRACE_NAN_ENABLED=ON"
+ cmake_opts: "-DICC_TRACE_NAN_ENABLED=ON"
+ - option_name: "SHARED_ONLY"
+ cmake_opts: "-DENABLE_SHARED_LIBS=ON -DENABLE_STATIC_LIBS=OFF"
+ - option_name: "STATIC_ONLY"
+ cmake_opts: "-DENABLE_SHARED_LIBS=OFF -DENABLE_STATIC_LIBS=ON"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake clang \
+ libpng-dev libxml2-dev libtiff-dev nlohmann-json3-dev
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/# find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Configure with Option
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build && cd Build
+ cmake Cmake/ \
+ -DCMAKE_BUILD_TYPE=Release \
+ ${{ matrix.cmake_opts }}
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make -j$(nproc) 2>&1 | tee build.log
+ if grep -iE 'error:' build.log; then
+ echo "ERROR: Build failed"
+ exit 1
+ fi
+
+ #############################################################################
+ # Job 5: Windows Build (MSVC)
+ #############################################################################
+ windows-build:
+ name: "Windows • MSVC • ${{ matrix.build_type }}"
+ runs-on: windows-latest
+ timeout-minutes: 30
+ env:
+ POWERSHELL_TELEMETRY_OPTOUT: "1"
+ defaults:
+ run:
+ shell: pwsh
+
+ strategy:
+ fail-fast: false
+ matrix:
+ build_type: [Release, Debug]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Configure Git Environment
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ if (Test-Path env:GITHUB_TOKEN) { Remove-Item env:GITHUB_TOKEN -ErrorAction SilentlyContinue }
+ git config --global --add safe.directory "$env:GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+
+ - name: Setup MSBuild
+ uses: microsoft/setup-msbuild@v2
+
+ - name: Install Dependencies
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ if (Test-Path Env:GITHUB_TOKEN) { Remove-Item Env:GITHUB_TOKEN -ErrorAction SilentlyContinue }
+ Write-Host "Fetching dependencies..."
+ Start-BitsTransfer -Source "https://github.com/InternationalColorConsortium/iccDEV/releases/download/v2.3.1/vcpkg-exported-deps.zip" -Destination "deps.zip"
+ tar -xf deps.zip
+
+ - name: Configure
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ if (Test-Path Env:GITHUB_TOKEN) { Remove-Item Env:GITHUB_TOKEN -ErrorAction SilentlyContinue }
+ cd Build/Cmake
+ cmake -B build -S . `
+ -DCMAKE_TOOLCHAIN_FILE="..\..\scripts\buildsystems\vcpkg.cmake" `
+ -DVCPKG_MANIFEST_MODE=OFF `
+ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} `
+ -Wno-dev
+
+ - name: Build
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ if (Test-Path Env:GITHUB_TOKEN) { Remove-Item Env:GITHUB_TOKEN -ErrorAction SilentlyContinue }
+ cd Build/Cmake
+ cmake --build build --config ${{ matrix.build_type }} -- /m /maxcpucount
+ cmake --build build --config ${{ matrix.build_type }} -- /m /maxcpucount
+
+ - name: Test
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ if (Test-Path Env:GITHUB_TOKEN) { Remove-Item Env:GITHUB_TOKEN -ErrorAction SilentlyContinue }
+ cd Build/Cmake
+ $exeDirs = Get-ChildItem -Recurse -File -Include *.exe -Path .\build\ |
+ Where-Object { $_.FullName -match 'icc' -and $_.FullName -notmatch '\\CMakeFiles\\' -and $_.Name -notmatch '^CMake(C|CXX)CompilerId\.exe$' } |
+ ForEach-Object { Split-Path $_.FullName -Parent } |
+ Sort-Object -Unique
+ $env:PATH = ($exeDirs -join ';') + ';' + $env:PATH
+ cd ..\..\Testing
+ .\CreateAllProfiles.bat
+
+ #############################################################################
+ # Job 6: Version Header Generation Test
+ #############################################################################
+ version-header-test:
+ name: "Version Headers • Build Directory Generation"
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y build-essential cmake libpng-dev libxml2-dev libtiff-dev nlohmann-json3-dev
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/# find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build && cd Build
+ cmake ../Build/Cmake -DCMAKE_BUILD_TYPE=Release
+ make -j$(nproc)
+
+ - name: Verify Version Headers in Build Directory
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ # Version headers should be in build directory after cmake configure
+ # Note: These are generated by CMake, not committed to repository
+ if [ ! -f "Build/IccProfLib/IccProfLibVer.h" ] && [ ! -f "IccProfLib/IccProfLibVer.h" ]; then
+ echo "WARNING: IccProfLibVer.h not found (may not be generated yet)"
+ fi
+
+ if [ ! -f "Build/IccXML/IccLibXMLVer.h" ] && [ ! -f "IccXML/IccLibXML/IccLibXMLVer.h" ]; then
+ echo "WARNING: IccLibXMLVer.h not found (may not be generated yet)"
+ fi
+
+ echo "✅ Version headers correctly generated in build directory"
+
+ #############################################################################
+ # Job 7: Clean/Rebuild Cycle Test
+ #############################################################################
+ clean-rebuild-test:
+ name: "Clean/Rebuild Cycle Test"
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Configure Git Environment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ - name: Install Dependencies
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ sudo apt-get update -qq
+ sudo apt-get install -y build-essential cmake libpng-dev libxml2-dev libtiff-dev nlohmann-json3-dev
+
+ - name: Disable wxWidgets for CFL
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ sed -i 's/^ find_package(wxWidgets COMPONENTS core base REQUIRED)/# find_package(wxWidgets COMPONENTS core base REQUIRED) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump) # Disabled for CFL/g' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/ # message(FATAL_ERROR "wxWidgets not found/g' Build/Cmake/CMakeLists.txt
+
+ - name: Initial Build
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ mkdir -p Build && cd Build
+ cmake ../Build/Cmake -DCMAKE_BUILD_TYPE=Release
+ make -j$(nproc)
+ TOOL_COUNT_1=$(find Tools -type f -executable | wc -l)
+ echo "Initial build: $TOOL_COUNT_1 tools"
+ echo "TOOL_COUNT_1=$TOOL_COUNT_1" >> $GITHUB_ENV
+
+ - name: Clean
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make clean
+ REMAINING=$(find Tools -type f -executable 2>/dev/null | wc -l || echo 0)
+ echo "After clean: $REMAINING executables"
+ if [ "$REMAINING" -ne 0 ]; then
+ echo "ERROR: make clean did not remove all executables"
+ exit 1
+ fi
+
+ - name: Rebuild
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+ cd Build
+ make -j$(nproc)
+ TOOL_COUNT_2=$(find Tools -type f -executable | wc -l)
+ echo "After rebuild: $TOOL_COUNT_2 tools"
+
+ if [ "$TOOL_COUNT_2" -ne "${{ env.TOOL_COUNT_1 }}" ]; then
+ echo "ERROR: Rebuild produced different number of tools ($TOOL_COUNT_2 vs ${{ env.TOOL_COUNT_1 }})"
+ exit 1
+ fi
+
+ echo "✅ Clean/rebuild cycle successful"
+
+ #############################################################################
+ # Job 8: Final Summary
+ #############################################################################
+ final-summary:
+ name: "Test Summary"
+ runs-on: ubuntu-latest
+ needs: [standard-builds, fuzzer-builds, sanitizer-builds, option-matrix, windows-build, version-header-test, clean-rebuild-test]
+ if: always()
+
+ steps:
+ - name: Generate Summary
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ {
+ echo "# Comprehensive Build & Test Results"
+ echo ""
+ echo "## Test Coverage"
+ echo ""
+ echo "### Platforms"
+ echo "- ✅ Ubuntu (latest)"
+ echo "- ✅ macOS (latest)"
+ echo "- ✅ Windows (latest)"
+ echo ""
+ echo "### Compilers"
+ echo "- ✅ GCC (Ubuntu)"
+ echo "- ✅ Clang (Ubuntu, macOS)"
+ echo "- ✅ MSVC (Windows)"
+ echo ""
+ echo "### Build Types"
+ echo "- ✅ Release (default)"
+ echo "- ✅ Debug"
+ echo "- ✅ RelWithDebInfo"
+ echo ""
+ echo "### Sanitizers"
+ echo "- ✅ AddressSanitizer (ASAN)"
+ echo "- ✅ UndefinedBehaviorSanitizer (UBSAN)"
+ echo "- ✅ Combined ASAN+UBSAN"
+ echo ""
+ echo "### Build Options"
+ echo "- ✅ VERBOSE_CONFIG"
+ echo "- ✅ ENABLE_COVERAGE"
+ echo "- ✅ ICC_ENABLE_ASSERTS"
+ echo "- ✅ ICC_TRACE_NAN_ENABLED"
+ echo "- ✅ Shared libs only"
+ echo "- ✅ Static libs only"
+ echo ""
+ echo "### Fuzzing"
+ echo "- ✅ 14 libFuzzer harnesses (Debug + Full Instrumentation)"
+ echo ""
+ echo "### Special Tests"
+ echo "- ✅ Version header generation (build directory)"
+ echo "- ✅ Clean source tree after build"
+ echo "- ✅ Clean/rebuild cycle"
+ echo ""
+ echo "## Job Results"
+ echo ""
+ echo "| Job | Status |"
+ echo "|-----|--------|"
+ echo "| Standard Builds | ${{ needs.standard-builds.result }} |"
+ echo "| Fuzzer Builds | ${{ needs.fuzzer-builds.result }} |"
+ echo "| Sanitizer Builds | ${{ needs.sanitizer-builds.result }} |"
+ echo "| Option Matrix | ${{ needs.option-matrix.result }} |"
+ echo "| Windows Build | ${{ needs.windows-build.result }} |"
+ echo "| Version Header Test | ${{ needs.version-header-test.result }} |"
+ echo "| Clean/Rebuild Test | ${{ needs.clean-rebuild-test.result }} |"
+ echo ""
+ echo "---"
+ echo ""
+ echo "**Compliance:** Hoyt shell/PowerShell prologue standards"
+ echo "**Reference:** llmcjf/actions/"
+ } >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/ci-fuzzer-smoke-test.yml b/.github/workflows/ci-fuzzer-smoke-test.yml
new file mode 100644
index 000000000..afd7529c2
--- /dev/null
+++ b/.github/workflows/ci-fuzzer-smoke-test.yml
@@ -0,0 +1,195 @@
+###############################################################
+#
+# Copyright (c) 2026 International Color Consortium.
+# All rights reserved.
+# https://color.org
+## Intent: iccDEV ci-pr-action
+#
+## Last Updated:2026-02-08 08:37:17 UTC by David Hoyt
+# Pattern: CMake integrated build with ENABLE_FUZZING=ON,
+# fuzzers built in Testing/Fuzzing/, matrix-parallel run
+#
+###############################################################
+
+name: LibFuzzer Smoke Test (60s)
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches: [ master, cfl ]
+
+concurrency:
+ group: libfuzzer-smoke-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-fuzzers:
+ name: "Build Fuzzers"
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ outputs:
+ fuzzers: ${{ steps.list-fuzzers.outputs.fuzzers }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install dependencies
+ env:
+ DEBIAN_FRONTEND: noninteractive
+ run: |
+ set -euo pipefail
+ sudo apt-get update -qq
+ sudo apt-get install -y --no-install-recommends \
+ build-essential cmake clang clang-tools \
+ libxml2-dev libtiff-dev libpng-dev libjpeg-dev zlib1g-dev \
+ nlohmann-json3-dev llvm
+ echo "CC=clang" >> $GITHUB_ENV
+ echo "CXX=clang++" >> $GITHUB_ENV
+
+ - name: Configure CMake (Fuzzing)
+ run: |
+ set -euo pipefail
+ # Disable wxWidgets (not available on CI)
+ sed -i 's/^ find_package(wxWidgets/# find_package(wxWidgets/' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump)/' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/# message(FATAL_ERROR "wxWidgets not found/' Build/Cmake/CMakeLists.txt
+
+ mkdir -p build-fuzz && cd build-fuzz
+ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_CXX_COMPILER=clang++ \
+ -DCMAKE_C_COMPILER=clang \
+ -DENABLE_FUZZING=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_SHARED_LIBS=OFF \
+ -DENABLE_TOOLS=OFF \
+ -Wno-dev
+
+ - name: Build
+ run: |
+ set -euo pipefail
+ cd build-fuzz
+ make -j$(nproc)
+ echo "### Build Complete" >> $GITHUB_STEP_SUMMARY
+ find Testing/Fuzzing -type f -executable -name "*_fuzzer" | sort | tee -a $GITHUB_STEP_SUMMARY
+
+ - name: List built fuzzers
+ id: list-fuzzers
+ run: |
+ set -euo pipefail
+ cd build-fuzz/Testing/Fuzzing
+ FUZZERS=$(ls *_fuzzer 2>/dev/null | python3 -c "import sys,json; print(json.dumps([l.strip() for l in sys.stdin]))")
+ echo "fuzzers=$FUZZERS" >> $GITHUB_OUTPUT
+ echo "Fuzzers for matrix: $FUZZERS"
+
+ - name: Upload fuzzer binaries
+ uses: actions/upload-artifact@v4
+ with:
+ name: fuzzer-binaries
+ path: |
+ build-fuzz/Testing/Fuzzing/*_fuzzer
+ Testing/Fuzzing/*.dict
+ Testing/Fuzzing/*_seed_corpus/
+ retention-days: 1
+
+ smoke-test:
+ name: "Smoke - ${{ matrix.fuzzer }}"
+ needs: build-fuzzers
+ runs-on: ubuntu-24.04
+ timeout-minutes: 10
+ strategy:
+ fail-fast: false
+ matrix:
+ fuzzer: ${{ fromJson(needs.build-fuzzers.outputs.fuzzers) }}
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Download fuzzer binaries
+ uses: actions/download-artifact@v4
+ with:
+ name: fuzzer-binaries
+
+ - name: Run fuzzer (60s)
+ run: |
+ set -euo pipefail
+
+ FUZZER="build-fuzz/Testing/Fuzzing/${{ matrix.fuzzer }}"
+ chmod +x "$FUZZER"
+ mkdir -p corpus
+
+ # Dictionary fallback: per-fuzzer → xml-consolidated → core
+ DICT_ARG=""
+ if [ -f "Testing/Fuzzing/${{ matrix.fuzzer }}.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/${{ matrix.fuzzer }}.dict"
+ elif [[ "${{ matrix.fuzzer }}" == *"xml"* ]] && [ -f "Testing/Fuzzing/icc_xml_consolidated.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/icc_xml_consolidated.dict"
+ elif [ -f "Testing/Fuzzing/icc_core.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/icc_core.dict"
+ fi
+
+ # Seed corpus
+ SEED_DIR=""
+ if [ -d "Testing/Fuzzing/${{ matrix.fuzzer }}_seed_corpus" ]; then
+ SEED_DIR="Testing/Fuzzing/${{ matrix.fuzzer }}_seed_corpus"
+ fi
+
+ set +e
+ timeout --kill-after=5s 65s "$FUZZER" \
+ -max_total_time=60 \
+ -print_final_stats=1 \
+ -detect_leaks=0 \
+ -timeout=30 \
+ $DICT_ARG \
+ corpus $SEED_DIR 2>&1 | tee /tmp/fuzz.log
+ EXIT_CODE=$?
+ set -e
+
+ # Extract stats
+ EXECS=$(grep -oP 'stat::number_of_executed_inputs:\s*\K\d+' /tmp/fuzz.log 2>/dev/null | tail -1 || echo "0")
+ CORPUS_SIZE=$(ls corpus/ 2>/dev/null | wc -l)
+
+ if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 124 ]; then
+ STATUS="✅ PASS"
+ else
+ STATUS="❌ FAIL (exit $EXIT_CODE)"
+ fi
+
+ {
+ echo "### ${{ matrix.fuzzer }}: ${STATUS}"
+ echo "| Metric | Value |"
+ echo "|--------|-------|"
+ echo "| Exit Code | $EXIT_CODE |"
+ echo "| Executions | $EXECS |"
+ echo "| Corpus Size | $CORPUS_SIZE |"
+ echo ""
+ echo '```'
+ tail -10 /tmp/fuzz.log
+ echo '```'
+ } >> $GITHUB_STEP_SUMMARY
+
+ # Collect crash artifacts
+ mkdir -p crash-artifacts
+ find . -maxdepth 1 \( -name "crash-*" -o -name "leak-*" -o -name "oom-*" \) \
+ -exec cp {} crash-artifacts/ \; 2>/dev/null || true
+
+ - name: Upload crash artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: crash-${{ matrix.fuzzer }}-${{ github.run_id }}
+ path: crash-artifacts/
+ retention-days: 30
+ if-no-files-found: ignore
diff --git a/.github/workflows/ci-latest-release.yml b/.github/workflows/ci-latest-release.yml
index 3f5e2ec92..4880053ce 100644
--- a/.github/workflows/ci-latest-release.yml
+++ b/.github/workflows/ci-latest-release.yml
@@ -1,27 +1,29 @@
###############################################################
#
-## Copyright (©) 2025 International Color Consortium.
+## Copyright (©) 2025-2026 International Color Consortium.
## All rights reserved.
## https://color.org
#
#
## Intent: ci-latest-release
#
-## Last Updated: 31-JAN-2025 0100Z by David Hoyt
+## Last Updated: 2026-02-11 22:19:53 UTC by David Hoyt
+# Clean .pdb files from Zip Bundle
+# Change Build to Release per Developer Spec
+# Note that Cmake Default Build = Release (now)
# Add bash & pwsh shell prologues
+# Align Unix & Windows Bundle per Developer Spec
+# Thank you Max & Phil for the Corrections!
#
# TODO: Extract repetitive shell commands to reusable script
-#
-#
-# Ref: https://github.com/xsscx/governance/tree/main/actions
-#
+#
###############################################################
name: ci-latest-release
permissions:
contents: read
-
+
on:
workflow_dispatch:
@@ -127,15 +129,88 @@ jobs:
echo "::error ::Build failed! CMakeCache.txt not found." | tee -a $GITHUB_STEP_SUMMARY
exit 1
fi
+ - name: Stage bundle into Testing directory
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ stage="staging/iccDEV-Testing"
+ mkdir -p "$stage"
+
+ # 1. Copy Testing contents (exclude Fuzzing)
+ for item in Testing/*; do
+ [ "$(basename "$item")" = "Fuzzing" ] && continue
+ cp -a "$item" "$stage/"
+ done
+
+ # 2. Copy tool binaries and libraries from build
+ find Build/Tools -type f \( -perm -u+x -o -name '*.so*' -o -name '*.a' \) \
+ -exec cp -a {} "$stage/" \;
+ find Build/IccProfLib -type f -name '*.so*' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccProfLib -type l -name '*.so*' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccXML -type f -name '*.so*' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccXML -type l -name '*.so*' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+
+ # 3. Copy Testing/Readme.md, LICENSE.md and docs/
+ cp Testing/Readme.md "$stage/"
+ cp LICENSE.md "$stage/"
+ cp -a docs "$stage/docs"
+
+ # 4. Generate path.sh helper script (POSIX-compatible, resolves from script location)
+ printf '%s\n' \
+ '#!/bin/sh' \
+ '# Resolve the directory where this script lives' \
+ 'if [ -n "${BASH_SOURCE:-}" ]; then' \
+ ' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' \
+ 'else' \
+ ' SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"' \
+ 'fi' \
+ 'export PATH="$SCRIPT_DIR:$PATH"' \
+ 'export LD_LIBRARY_PATH="$SCRIPT_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"' \
+ 'for d in "$SCRIPT_DIR"/*/; do' \
+ ' [ -d "$d" ] && export PATH="$d:$PATH"' \
+ 'done' \
+ 'echo "PATH and LD_LIBRARY_PATH updated."' \
+ 'echo "Tools directory: $SCRIPT_DIR"' \
+ > "$stage/path.sh"
+ chmod +x "$stage/path.sh"
+
+ # Summary of staged bundle contents
+ total=$(find "$stage" -type f | wc -l)
+ echo ""
+ echo "===== Staged Bundle Contents ($total files) ====="
+ for ext in so 'so.*' a icc md; do
+ count=$(find "$stage" -maxdepth 1 -type f -name "*.$ext" -o -type l -name "*.$ext" | wc -l)
+ if [ "$count" -gt 0 ]; then
+ echo ""
+ echo "--- *.$ext ($count) ---"
+ find "$stage" -maxdepth 1 \( -type f -o -type l \) -name "*.$ext" -printf ' %f (%k KB)\n' 2>/dev/null || \
+ find "$stage" -maxdepth 1 \( -type f -o -type l \) -name "*.$ext" -exec bash -c 'echo " $(basename {}) ($(( $(stat -f%z {} 2>/dev/null || stat -c%s {}) / 1024 )) KB)"' \;
+ fi
+ done
+ # List executables (ELF binaries without extension)
+ exes=$(find "$stage" -maxdepth 1 -type f -executable ! -name '*.*' | wc -l)
+ if [ "$exes" -gt 0 ]; then
+ echo ""
+ echo "--- executables ($exes) ---"
+ find "$stage" -maxdepth 1 -type f -executable ! -name '*.*' -printf ' %f (%k KB)\n' 2>/dev/null || \
+ find "$stage" -maxdepth 1 -type f -executable ! -name '*.*' -exec bash -c 'echo " $(basename {}) ($(( $(stat -f%z {} 2>/dev/null || stat -c%s {}) / 1024 )) KB)"' \;
+ fi
+ echo ""
+ echo "Total: $total files staged into $stage"
+ echo ""
+
- name: Upload Linux Artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: iccdev-linux-${{ matrix.compiler }}
- path: |
- Build
- LICENSE.md
- README.md
- docs/**
+ path: staging
+ if-no-files-found: warn
- name: Summary Report
if: always()
@@ -169,7 +244,7 @@ jobs:
git config --global credential.helper ""
unset GITHUB_TOKEN || true
echo "Installing Homebrew dependencies..."
- brew install libpng nlohmann-json libxml2 wxwidgets libtiff jpeg || echo "⚠️ Some dependencies might already be installed."
+ brew install libpng nlohmann-json libxml2 wxwidgets libtiff jpeg-turbo || echo "⚠️ Some dependencies might already be installed."
echo "✔ Dependency installation complete."
- name: CMake Configure
@@ -184,15 +259,15 @@ jobs:
echo "Setting up CMake build configuration..."
cd Build
sudo rm -rf /Library/Frameworks/Mono.framework/Headers/png.h
- echo 'export PATH="/opt/homebrew/opt/jpeg/bin:$PATH"' >> /Users/runner/.bash_profile
- export CPPFLAGS="-I/opt/homebrew/opt/jpeg/include"
- export PKG_CONFIG_PATH="/opt/homebrew/opt/jpeg/lib/pkgconfig"
- export CFLAGS="-I$(brew --prefix libpng)/include -I$(brew --prefix jpeg)/include"
- export LDFLAGS="-L$(brew --prefix libpng)/lib -L$(brew --prefix jpeg)/lib"
+ echo 'export PATH="/opt/homebrew/opt/jpeg-turbo/bin:$PATH"' >> /Users/runner/.bash_profile
+ export CPPFLAGS="-I/opt/homebrew/opt/jpeg-turbo/include"
+ export PKG_CONFIG_PATH="/opt/homebrew/opt/jpeg-turbo/lib/pkgconfig"
+ export CFLAGS="-I$(brew --prefix libpng)/include -I$(brew --prefix jpeg-turbo)/include"
+ export LDFLAGS="-L$(brew --prefix libpng)/lib -L$(brew --prefix jpeg-turbo)/lib"
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local \
-DCMAKE_BUILD_TYPE=Release \
- -DJPEG_LIBRARY=$(brew --prefix jpeg)/lib/libjpeg.dylib \
- -DJPEG_INCLUDE_DIR=$(brew --prefix jpeg)/include \
+ -DJPEG_LIBRARY=$(brew --prefix jpeg-turbo)/lib/libjpeg.dylib \
+ -DJPEG_INCLUDE_DIR=$(brew --prefix jpeg-turbo)/include \
-Wno-dev Cmake/
echo "✔ CMake configuration complete."
@@ -219,15 +294,86 @@ jobs:
sh RunTests.sh
find . -iname "*.icc" | wc -l
+ - name: Stage bundle into Testing directory
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ stage="staging/iccDEV-Testing"
+ mkdir -p "$stage"
+
+ # 1. Copy Testing contents (exclude Fuzzing)
+ for item in Testing/*; do
+ [ "$(basename "$item")" = "Fuzzing" ] && continue
+ cp -a "$item" "$stage/"
+ done
+
+ # 2. Copy tool binaries and libraries from build
+ find Build/Tools -type f \( -perm -u+x -o -name '*.dylib' -o -name '*.a' \) \
+ -exec cp -a {} "$stage/" \;
+ find Build/IccProfLib -type f -name '*.dylib' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccProfLib -type l -name '*.dylib' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccXML -type f -name '*.dylib' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+ find Build/IccXML -type l -name '*.dylib' \
+ -exec cp -a {} "$stage/" \; 2>/dev/null || true
+
+ # 3. Copy Testing/Readme.md, LICENSE.md and docs/
+ cp Testing/Readme.md "$stage/"
+ cp LICENSE.md "$stage/"
+ cp -a docs "$stage/docs"
+
+ # 4. Generate path.sh helper script (POSIX-compatible, resolves from script location)
+ printf '%s\n' \
+ '#!/bin/sh' \
+ '# Resolve the directory where this script lives' \
+ 'if [ -n "${BASH_SOURCE:-}" ]; then' \
+ ' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' \
+ 'else' \
+ ' SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"' \
+ 'fi' \
+ 'export PATH="$SCRIPT_DIR:$PATH"' \
+ 'export DYLD_LIBRARY_PATH="$SCRIPT_DIR${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' \
+ 'for d in "$SCRIPT_DIR"/*/; do' \
+ ' [ -d "$d" ] && export PATH="$d:$PATH"' \
+ 'done' \
+ 'echo "PATH and DYLD_LIBRARY_PATH updated."' \
+ 'echo "Tools directory: $SCRIPT_DIR"' \
+ > "$stage/path.sh"
+ chmod +x "$stage/path.sh"
+
+ # Summary of staged bundle contents
+ total=$(find "$stage" -type f | wc -l)
+ echo ""
+ echo "===== Staged Bundle Contents ($total files) ====="
+ for ext in dylib a icc md; do
+ count=$(find "$stage" -maxdepth 1 -type f -name "*.$ext" -o -type l -name "*.$ext" | wc -l)
+ if [ "$count" -gt 0 ]; then
+ echo ""
+ echo "--- *.$ext ($count) ---"
+ find "$stage" -maxdepth 1 \( -type f -o -type l \) -name "*.$ext" -exec bash -c 'echo " $(basename {}) ($(( $(stat -f%z {} 2>/dev/null || stat -c%s {}) / 1024 )) KB)"' \;
+ fi
+ done
+ # List executables (Mach-O binaries without extension)
+ exes=$(find "$stage" -maxdepth 1 -type f -perm +111 ! -name '*.*' | wc -l)
+ if [ "$exes" -gt 0 ]; then
+ echo ""
+ echo "--- executables ($exes) ---"
+ find "$stage" -maxdepth 1 -type f -perm +111 ! -name '*.*' -exec bash -c 'echo " $(basename {}) ($(( $(stat -f%z {} 2>/dev/null || stat -c%s {}) / 1024 )) KB)"' \;
+ fi
+ echo ""
+ echo "Total: $total files staged into $stage"
+ echo ""
+
- name: Upload macOS Artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: iccdev-macos-clang
- path: |
- Build
- LICENSE.md
- README.md
- docs/**
+ path: staging
+ if-no-files-found: warn
- name: Summary Report
if: always()
shell: bash --noprofile --norc {0}
@@ -354,15 +500,13 @@ jobs:
Write-Host "Copyright (©) 2025 International Color Consortium. All rights reserved." -ForegroundColor Green
git branch
git status
- Write-Host "========= Fetching Deps... ================`n"
- Start-BitsTransfer -Source "https://github.com/InternationalColorConsortium/iccDEV/releases/download/v2.3.1/vcpkg-exported-deps.zip" -Destination "deps.zip"
- Write-Host "========= Extracting Deps... ================`n"
- tar -xf deps.zip
+ vcpkg integrate install
+ vcpkg install
cd Build/Cmake
Write-Host "========= Building... ================`n"
- cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="..\..\scripts\buildsystems\vcpkg.cmake" -DVCPKG_MANIFEST_MODE=OFF -DCMAKE_BUILD_TYPE=Debug -Wno-dev
- cmake --build build -- /m /maxcpucount
- cmake --build build -- /m /maxcpucount
+ cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="..\..\scripts\buildsystems\vcpkg.cmake" -DVCPKG_MANIFEST_MODE=OFF -DCMAKE_BUILD_TYPE=Release -Wno-dev
+ cmake --build build --config Release -- /m /maxcpucount
+ cmake --build build --config Release -- /m /maxcpucount
$exeDirs = Get-ChildItem -Recurse -File -Include *.exe -Path .\build\ |
Where-Object { $_.FullName -match 'icc' -and $_.FullName -notmatch '\\CMakeFiles\\' -and $_.Name -notmatch '^CMake(C|CXX)CompilerId\.exe$' } |
ForEach-Object { Split-Path $_.FullName -Parent } |
@@ -415,20 +559,83 @@ jobs:
Write-Host "=========================`n"
Write-Host "All Done!"
+ - name: Stage bundle into Testing directory
+ shell: pwsh
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+
+ $stage = "staging/iccDEV-Testing"
+ New-Item -ItemType Directory -Force -Path $stage | Out-Null
+
+ # 1. Copy the entire Testing directory contents (exclude Fuzzing)
+ Get-ChildItem -Path "Testing" -Exclude "Fuzzing" |
+ Copy-Item -Destination $stage -Recurse -Force
+
+ # 2. Copy .exe and library files from the build (no .pdb in Release)
+ Get-ChildItem -Path "Build/Cmake/build" -Recurse -Include *.exe,*.dll,*.lib |
+ Where-Object { $_.FullName -notmatch '\\CMakeFiles\\' -and $_.Name -notmatch '^CMake(C|CXX)CompilerId\.exe$' } |
+ ForEach-Object { Copy-Item $_.FullName -Destination $stage -Force }
+
+ # 3. Copy Testing/Readme.md, LICENSE.md and docs/
+ Copy-Item -Path "Testing/Readme.md" -Destination $stage -Force
+ Copy-Item -Path "LICENSE.md" -Destination $stage -Force
+ Copy-Item -Path "docs" -Destination "$stage/docs" -Recurse -Force
+
+ # 4. Generate path.bat helper script
+ @'
+ @echo off
+ setlocal EnableDelayedExpansion
+ set "SCRIPT_DIR=%~dp0"
+ if "!SCRIPT_DIR:~-1!"=="\" set "SCRIPT_DIR=!SCRIPT_DIR:~0,-1!"
+
+ :: Build new PATH with script dir and all subdirs containing .exe
+ set "NEW_PATH=!SCRIPT_DIR!;!PATH!"
+ for /R "!SCRIPT_DIR!" %%F in (*.exe) do (
+ set "dir=%%~dpF"
+ if "!dir:~-1!"=="\" set "dir=!dir:~0,-1!"
+ echo !NEW_PATH! | findstr /I /C:"!dir!" >nul || set "NEW_PATH=!dir!;!NEW_PATH!"
+ )
+
+ :: Export PATH back to caller via endlocal trick
+ endlocal & set "PATH=%NEW_PATH%"
+ '@ | Set-Content -Path "$stage/path.bat" -Encoding ASCII
+
+ # Summary of staged bundle contents
+ $allFiles = Get-ChildItem -Path $stage -Recurse -File
+ $count = $allFiles.Count
+ Write-Host "`n===== Staged Bundle Contents ($count files) =====" -ForegroundColor Cyan
+
+ foreach ($ext in @('*.exe','*.dll','*.lib','*.icc','*.md')) {
+ $matched = $allFiles | Where-Object { $_.Name -like $ext }
+ if ($matched) {
+ Write-Host "`n--- $ext ($($matched.Count)) ---"
+ $matched | ForEach-Object {
+ $rel = $_.FullName.Replace((Resolve-Path $stage).Path + '\', '')
+ Write-Host " $rel ($([math]::Round($_.Length/1KB,1)) KB)"
+ }
+ }
+ }
+
+ $otherExts = $allFiles |
+ Where-Object { $_.Extension -notin '.exe','.dll','.lib','.icc','.md' } |
+ Group-Object Extension |
+ Sort-Object Count -Descending
+ if ($otherExts) {
+ Write-Host "`n--- Other files ---"
+ foreach ($g in $otherExts) {
+ Write-Host (" {0}: {1} file(s)" -f ($g.Name ? $g.Name : '(no ext)'), $g.Count)
+ }
+ }
+
+ Write-Host "`nTotal: $count files staged into $stage`n"
+
- name: Upload build artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: iccdev-windows-msvc
- path: |
- Build/Cmake/build/**/*.lib
- Build/Cmake/build/**/*.a
- Build/Cmake/build/**/*.dll
- Build/Cmake/build/**/*.exe
- Build/Cmake/build/**/*.pdb
- Build/Cmake/Testing/**/*
- LICENSE.md
- README.md
- docs/**
+ path: staging
if-no-files-found: warn
- name: Host System Info
shell: pwsh
@@ -437,8 +644,24 @@ jobs:
Get-CimInstance -ClassName Win32_Processor
- name: Summary Report
if: always()
+ shell: pwsh
+ env:
+ POWERSHELL_TELEMETRY_OPTOUT: "1"
+ GH_REPOSITORY: ${{ github.repository }}
+ GH_COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ GH_RUNNER_OS: ${{ runner.os }}
+ GH_JOB_STATUS: ${{ job.status }}
run: |
- echo "### Windows Build Summary" >> $GITHUB_STEP_SUMMARY
- echo "- Build Directory: Build/" >> $GITHUB_STEP_SUMMARY
- echo "- Artifacts Uploaded: iccdev-windows-msvc" >> $GITHUB_STEP_SUMMARY
- echo "- Status: Success" >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+ $ProgressPreference = 'SilentlyContinue'
+ # Source trusted sanitizer from checked-out workspace
+ . .github/scripts/sanitize.ps1
+ "### Windows Build Summary" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Repository: $(Sanitize-Line $env:GH_REPOSITORY)" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Commit SHA: $(Sanitize-Line $env:GH_COMMIT_SHA)" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Runner OS: $(Sanitize-Line $env:GH_RUNNER_OS)" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Build Directory: Build/" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Artifacts Uploaded: iccdev-windows-msvc" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
+ "- Status: $(Sanitize-Line $env:GH_JOB_STATUS)" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
diff --git a/.github/workflows/ci-pr-greeting.yml b/.github/workflows/ci-pr-greeting.yml
new file mode 100644
index 000000000..4bdb39fa4
--- /dev/null
+++ b/.github/workflows/ci-pr-greeting.yml
@@ -0,0 +1,81 @@
+###############################################################
+#
+# Copyright (©) 2025 International Color Consortium.
+# All rights reserved.
+# https://color.org
+#
+## Intent: Thank contributors when they open a pull request
+#
+## Created: 08-FEB-2026
+#
+###############################################################
+
+name: "ci-pr-greeting"
+
+permissions:
+ pull-requests: write
+
+on:
+ pull_request_target:
+ types: [opened]
+
+jobs:
+ greet:
+ name: "Welcome contributor"
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+
+ steps:
+ - name: Checkout base (trusted sanitizers)
+ uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+ with:
+ ref: ${{ github.event.pull_request.base.sha }}
+ path: base
+ fetch-depth: 1
+ persist-credentials: false
+
+ - name: Post greeting comment
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ PR_AUTHOR: ${{ github.event.pull_request.user.login }}
+ run: |
+ set -euo pipefail
+
+ # Source trusted sanitizer
+ SANITIZER="base/.github/scripts/sanitize-sed.sh"
+ if [[ -f "$SANITIZER" ]]; then
+ # shellcheck disable=SC1090
+ source "$SANITIZER"
+ else
+ escape_html() {
+ local s="$1"
+ s="${s//&/&}"
+ s="${s//<}"
+ s="${s//>/>}"
+ s="${s//\"/"}"
+ s="${s//\'/'}"
+ printf '%s' "$s"
+ }
+ sanitize_line() { escape_html "$1"; }
+ fi
+
+ safe_author=$(sanitize_line "$PR_AUTHOR")
+
+ body="👋 Thank you **@${safe_author}** for opening this pull request!
+
+ A maintainer will review it shortly. In the meantime, please make sure:
+
+ - [ ] The code compiles and tests pass
+ - [ ] Changes follow the existing code style (2-space indent, K&R braces)
+ - [ ] New source files include the ICC Copyright notice and BSD 3-Clause License header
+ - [ ] The [Contributor License Agreement](CONTRIBUTING.md) has been signed
+
+ We appreciate your contribution to the ICC color management ecosystem! 🎨"
+
+ # Dedent the heredoc-style body
+ body=$(echo "$body" | sed 's/^ //')
+
+ gh pr comment "$PR_NUMBER" --body "$body"
diff --git a/.github/workflows/ci-pr-lint.yml b/.github/workflows/ci-pr-lint.yml
index d6b5f27cd..6f1c72ed6 100644
--- a/.github/workflows/ci-pr-lint.yml
+++ b/.github/workflows/ci-pr-lint.yml
@@ -1,20 +1,16 @@
###############################################################
#
-# Copyright (©) 2025 International Color Consortium.
-# All rights reserved.
+# Copyright (©) 2025 International Color Consortium.
+# All rights reserved.
# https://color.org
#
#
# Intent: Static code analysis and linting
-# Last Updated: 02-JAN-2025 2100Z by David Hoyt
-#
-# Change git depth, other changes
-#
-#
-#
-#
-#
+# Last Updated: 2026-02-08 17:43:13 UTC by David Hoyt
#
+# Matrix: cppcheck and clang-tidy run as parallel
+# jobs, each with -j$(nproc). Build is shared via
+# artifact upload.
#
###############################################################
@@ -25,10 +21,8 @@ permissions:
pull-requests: read
on:
- # Allow manual trigger
workflow_dispatch:
- # Allow other workflows to call this
workflow_call:
inputs:
ubuntu-version:
@@ -38,13 +32,18 @@ on:
type: string
jobs:
- build-linux:
- name: Lint on ${{ inputs.ubuntu-version || 'ubuntu-latest' }}
+ #############################################################################
+ # Job 1: Build + determine files
+ #############################################################################
+ build:
+ name: "Build"
runs-on: ${{ inputs.ubuntu-version || 'ubuntu-latest' }}
- timeout-minutes: 30
+ timeout-minutes: 20
+ outputs:
+ no_src_changes: ${{ steps.diffcheck.outputs.no_src_changes }}
+ scope: ${{ steps.diffcheck.outputs.scope }}
steps:
- # --- Bump any Self-Hosted ---
- name: Validate ubuntu-version input
shell: bash --noprofile --norc {0}
env:
@@ -59,19 +58,14 @@ jobs:
exit 1
;;
esac
-
- uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
with:
fetch-depth: 0
- # Disable unsafe fetch options
persist-credentials: false
- name: Checkout base commit (trusted sanitizers)
uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
with:
- # For workflow_call from ci-pr-action: use github.sha (the base branch)
- # For pull_request: use github.event.pull_request.base.sha
- # For workflow_dispatch: use github.sha
ref: ${{ github.event.pull_request.base.sha || github.sha }}
path: base
fetch-depth: 1
@@ -81,25 +75,6 @@ jobs:
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "GitHub Actions"
-
- - name: Verify remote origin URL (opt out of malicious git configs)
- shell: bash --noprofile --norc {0}
- env:
- BASH_ENV: /dev/null
- run: |
- set -euo pipefail
- git config --add safe.directory "$PWD"
- git config --global credential.helper ""
- origin_url=$(git remote get-url origin || true)
- echo "Remote origin: $origin_url"
- # Enforce that origin points to the expected GitHub repository for this workflow run
- expected="https://github.com/${{ github.repository }}.git"
- # Allow both HTTPS and git@ forms if needed
- if [[ "$origin_url" != "$expected" && "$origin_url" != "${expected%.git}" && "$origin_url" != "git@github.com:InternationalColorConsortium/iccDEV.git" ]]; then
- echo "Origin URL mismatch: expected $expected, ${expected%.git}, or git@github.com:InternationalColorConsortium/iccDEV.git, got $origin_url" >&2
- exit 1
- fi
-
- name: Install Linux dependencies
shell: bash --noprofile --norc {0}
env:
@@ -109,17 +84,25 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
-
sudo apt-get update -qq
sudo apt-get install -y \
- build-essential cmake gcc g++ clang clang-tools \
- cppcheck clang-format pkg-config \
- libpng-dev libxml2-dev libtiff-dev \
+ build-essential cmake gcc g++ clang clang-tools clang-tidy \
+ cppcheck clang-format pkg-config jq \
+ libpng-dev libxml2-dev libtiff-dev libjpeg-dev \
nlohmann-json3-dev libwxgtk3.2-dev wx-common \
- python3 python3-pip curl git llvm
-
+ python3 curl git llvm
+ {
+ echo "### Environment"
+ echo "| Tool | Version |"
+ echo "|------|---------|"
+ echo "| cppcheck | $(cppcheck --version 2>&1) |"
+ echo "| clang-tidy | $(clang-tidy --version 2>&1 | head -1) |"
+ echo "| cmake | $(cmake --version | head -1) |"
+ echo "| nproc | $(nproc) |"
+ echo ""
+ } >> "$GITHUB_STEP_SUMMARY"
- id: diffcheck
- name: diffcheck for file changes
+ name: Determine files to analyze
shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
@@ -128,49 +111,39 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
-
git show --stat --pretty=format:"Commit: %H%nAuthor: %an%nDate: %ad%n" HEAD
-
- # If not a pull_request event (e.g., workflow_dispatch or workflow_call),
- # skip source diff checking and mark as non-source change.
- if [ "${{ github.event_name }}" != "pull_request" ]; then
- echo "Non-PR event (${{ github.event_name }}) — skipping source diff check" >&2
- echo "no_src_changes=true" >> "$GITHUB_OUTPUT"
- exit 0
- fi
-
- base_ref="${{ github.base_ref }}"
-
- # Avoid fetching refs from user-controllable inputs like ($base_ref)
- case "$base_ref" in master)
- ;;
- *)
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
+ base_ref="${{ github.base_ref }}"
+ case "$base_ref" in master) ;; *)
echo "Ref blocked or unsupported: $base_ref" >&2
echo "no_src_changes=true" >> "$GITHUB_OUTPUT"
+ echo "scope=none" >> "$GITHUB_OUTPUT"
exit 0
;;
- esac
-
- # Fetch enough history to find merge base
- git -c protocol.version=2 fetch --no-tags origin "${base_ref}" --depth=50
-
- # Case-insensitive extension match (c/h/cpp variants)
- # Use three-dot syntax to show changes since merge-base
- git diff --name-only "origin/${base_ref}...HEAD" \
- | tr -cd '[:alnum:]./\-_\n' \
- | grep -E '\.(cpp|cxx|cc|h|hpp)$' \
- | tr '\n' '\0' > changed_files.txt
-
- changed_file_count="$(xargs -0 -n1 < changed_files.txt | wc -l)"
-
- if [ "$changed_file_count" -eq 0 ]; then
- echo "no_src_changes=true" >> "$GITHUB_OUTPUT"
- else
- echo "no_src_changes=false" >> "$GITHUB_OUTPUT"
- fi
-
+ esac
+ git -c protocol.version=2 fetch --no-tags origin "${base_ref}" --depth=50
+ git diff --name-only "origin/${base_ref}...HEAD" \
+ | tr -cd '[:alnum:]./\-_\n' \
+ | grep -E '\.(cpp|cxx|cc|h|hpp)$' \
+ | tr '\n' '\0' > changed_files.txt
+ changed_file_count="$(xargs -0 -n1 < changed_files.txt | wc -l)"
+ if [ "$changed_file_count" -eq 0 ]; then
+ echo "no_src_changes=true" >> "$GITHUB_OUTPUT"
+ echo "scope=none" >> "$GITHUB_OUTPUT"
+ else
+ echo "no_src_changes=false" >> "$GITHUB_OUTPUT"
+ echo "scope=pr" >> "$GITHUB_OUTPUT"
+ echo "Analyzing $changed_file_count changed files"
+ fi
+ else
+ echo "no_src_changes=false" >> "$GITHUB_OUTPUT"
+ echo "scope=full" >> "$GITHUB_OUTPUT"
+ find IccProfLib IccXML Tools -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.cxx' -o -name '*.cc' -o -name '*.hpp' \) -print0 > changed_files.txt
+ file_count="$(xargs -0 -n1 < changed_files.txt | wc -l)"
+ echo "Full-tree analysis: $file_count source files"
+ fi
- name: Configure and build
- if: ${{ steps.diffcheck.outputs.no_src_changes == 'false' }}
+ if: steps.diffcheck.outputs.no_src_changes == 'false'
shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
@@ -179,15 +152,54 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
-
mkdir -p Build
cd Build
- cmake Cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- make
- cd ..
+ cmake Cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -Wno-dev
+ make -j"$(nproc)"
+ - name: Upload build context
+ if: steps.diffcheck.outputs.no_src_changes == 'false'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: lint-build-context
+ retention-days: 1
+ path: |
+ Build/compile_commands.json
+ changed_files.txt
+ #############################################################################
+ # Job 2: Parallel lint matrix
+ # - cppcheck split by component (IccProfLib, IccXML, Tools)
+ # - clang-tidy runs full tree
+ #############################################################################
+ lint:
+ name: "${{ matrix.name }}"
+ runs-on: ${{ inputs.ubuntu-version || 'ubuntu-latest' }}
+ needs: build
+ if: needs.build.outputs.no_src_changes == 'false'
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: "cppcheck • IccProfLib"
+ tool: cppcheck
+ component: IccProfLib
+ - name: "cppcheck • IccXML"
+ tool: cppcheck
+ component: IccXML
+ - name: "cppcheck • Tools"
+ tool: cppcheck
+ component: Tools
+ - name: "clang-tidy"
+ tool: clang-tidy
+ component: all
+ steps:
- - name: Run cppcheck
- if: ${{ steps.diffcheck.outputs.no_src_changes == 'false' }}
+ - uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install dependencies
shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
@@ -196,41 +208,33 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
-
- mkdir -p lint_reports
-
- echo "Running cppcheck on modified source files..."
-
- # --- SECURITY: null-delimited xargs ---
- : > lint_reports/cppcheck.txt
- xargs -0 cppcheck \
- --enable=warning,performance,portability \
- --std=c++17 \
- --output-file=lint_reports/cppcheck.txt \
- < changed_files.txt || true
+ sudo apt-get update -qq
+ sudo apt-get install -y \
+ build-essential cmake gcc g++ clang clang-tools clang-tidy \
+ cppcheck pkg-config \
+ libpng-dev libxml2-dev libtiff-dev libjpeg-dev \
+ nlohmann-json3-dev libwxgtk3.2-dev wx-common \
+ llvm
+ - name: Download build context
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
+ with:
+ name: lint-build-context
- - name: Run clang-tidy
- if: ${{ steps.diffcheck.outputs.no_src_changes == 'false' }}
+ - name: Filter files for component
+ if: matrix.tool == 'cppcheck'
shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
run: |
set -euo pipefail
- git config --add safe.directory "$PWD"
- git config --global credential.helper ""
- unset GITHUB_TOKEN || true
- mkdir -p lint_reports
-
- # --- clang-tidy ---
- : > lint_reports/clang_tidy.txt
- xargs -0 run-clang-tidy \
- -p Build \
- -checks='modernize-*,readability-*,cppcoreguidelines-*,clang-analyzer-core.*,clang-analyzer-security.*,clang-analyzer-alpha.core.*,clang-analyzer-alpha.security.*' \
- < changed_files.txt \
- | tee lint_reports/clang_tidy.txt || true
-
- - name: Skip lint (no source changes)
- if: ${{ steps.diffcheck.outputs.no_src_changes == 'true' }}
+ # Filter changed_files.txt to only this component's files
+ xargs -0 -n1 < changed_files.txt \
+ | { grep "^${{ matrix.component }}/" || true; } \
+ | tr '\n' '\0' > component_files.txt
+ FILE_COUNT=$(xargs -0 -n1 < component_files.txt 2>/dev/null | wc -l)
+ echo "Component ${{ matrix.component }}: $FILE_COUNT files to analyze"
+ echo "file_count=$FILE_COUNT" >> "$GITHUB_ENV"
+ - name: Run ${{ matrix.name }}
shell: bash --noprofile --norc {0}
env:
BASH_ENV: /dev/null
@@ -239,16 +243,100 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
+ NPROC=$(nproc)
mkdir -p lint_reports
- echo "No .cpp/.h changes detected; skipping lint." > lint_reports/cppcheck.txt
- : > lint_reports/clang_tidy.txt
-
- - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ if [ "${{ matrix.tool }}" = "cppcheck" ]; then
+ if [ "${file_count:-0}" -eq 0 ]; then
+ echo "No ${{ matrix.component }} files to check — skipping"
+ : > lint_reports/cppcheck_${{ matrix.component }}.txt
+ else
+ echo "Running cppcheck on ${{ matrix.component }} with $NPROC parallel jobs"
+ xargs -0 cppcheck \
+ -j "$NPROC" \
+ --enable=warning,performance,portability,style \
+ --std=c++17 \
+ --suppress=missingIncludeSystem \
+ < component_files.txt 2> lint_reports/cppcheck_${{ matrix.component }}.txt || true
+ echo "✓ cppcheck ${{ matrix.component }}: $(wc -l < lint_reports/cppcheck_${{ matrix.component }}.txt) lines"
+ fi
+ else
+ echo "Running clang-tidy with $NPROC parallel jobs"
+ xargs -0 run-clang-tidy \
+ -j "$NPROC" \
+ -p Build \
+ -checks='modernize-*,readability-*,cppcoreguidelines-*,clang-analyzer-core.*,clang-analyzer-security.*,clang-analyzer-alpha.core.*,clang-analyzer-alpha.security.*' \
+ < changed_files.txt \
+ > lint_reports/clang_tidy.txt 2>&1 || true
+ echo "✓ clang-tidy: $(wc -l < lint_reports/clang_tidy.txt) lines"
+ fi
+ - name: Upload results
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
- name: lint-reports
+ name: lint-${{ matrix.tool }}-${{ matrix.component }}
+ retention-days: 3
path: lint_reports/
+ #############################################################################
+ # Job 3: Combined report
+ #############################################################################
+ report:
+ name: "Report"
+ runs-on: ubuntu-latest
+ needs: [build, lint]
+ if: always()
+ timeout-minutes: 5
+ steps:
+
+ - uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+ with:
+ sparse-checkout: .github/scripts
+ persist-credentials: false
+
+ - name: Download cppcheck IccProfLib results
+ if: needs.lint.result == 'success' || needs.lint.result == 'failure'
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
+ with:
+ name: lint-cppcheck-IccProfLib
+ path: lint_reports/parts/
+ continue-on-error: true
+
+ - name: Download cppcheck IccXML results
+ if: needs.lint.result == 'success' || needs.lint.result == 'failure'
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
+ with:
+ name: lint-cppcheck-IccXML
+ path: lint_reports/parts/
+ continue-on-error: true
+ - name: Download cppcheck Tools results
+ if: needs.lint.result == 'success' || needs.lint.result == 'failure'
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
+ with:
+ name: lint-cppcheck-Tools
+ path: lint_reports/parts/
+ continue-on-error: true
+
+ - name: Download clang-tidy results
+ if: needs.lint.result == 'success' || needs.lint.result == 'failure'
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
+ with:
+ name: lint-clang-tidy-all
+ path: lint_reports/
+ continue-on-error: true
+
+ - name: Merge cppcheck results
+ if: needs.lint.result == 'success' || needs.lint.result == 'failure'
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ run: |
+ set -euo pipefail
+ mkdir -p lint_reports
+ : > lint_reports/cppcheck.txt
+ for f in lint_reports/parts/cppcheck_*.txt; do
+ [ -f "$f" ] && cat "$f" >> lint_reports/cppcheck.txt
+ done
+ echo "Merged cppcheck: $(wc -l < lint_reports/cppcheck.txt) lines"
- name: Generate GitHub summary
if: always()
shell: bash --noprofile --norc {0}
@@ -259,14 +347,11 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
-
- # Load trusted canonical sanitizer functions from base commit
- TRUSTED_SANITIZER="$GITHUB_WORKSPACE/base/.github/scripts/sanitize-sed.sh"
- if [[ -f "$TRUSTED_SANITIZER" ]]; then
+ SANITIZER=".github/scripts/sanitize-sed.sh"
+ if [[ -f "$SANITIZER" ]]; then
# shellcheck disable=SC1090
- source "$TRUSTED_SANITIZER"
+ source "$SANITIZER"
else
- # Fallback sanitizer
escape_html() {
local s="$1"
s="${s//&/&}"
@@ -278,19 +363,139 @@ jobs:
}
sanitize_line() { escape_html "$1"; }
fi
-
- echo "## 🧹 Lint Report Summary" >> $GITHUB_STEP_SUMMARY
-
- if [ "${{ steps.diffcheck.outputs.no_src_changes }}" == "true" ]; then
- echo "🟦 Skipped — no modified source files detected." >> $GITHUB_STEP_SUMMARY
- elif [ -s lint_reports/cppcheck.txt ]; then
- echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
- # Sanitize each line properly - avoid subshell issues
+ echo "## 🧹 Lint Report Summary" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "**Scope:** ${{ needs.build.outputs.scope || 'unknown' }}" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ if [ "${{ needs.build.outputs.no_src_changes }}" = "true" ]; then
+ echo "🟦 Skipped — no modified source files detected." >> "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+ mkdir -p lint_reports
+ # --- cppcheck summary ---
+ echo "### cppcheck Results" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ if [ -s lint_reports/cppcheck.txt ]; then
+ cpp_warns=$({ grep -c 'warning:' lint_reports/cppcheck.txt 2>/dev/null || true; })
+ cpp_errs=$({ grep -c 'error:' lint_reports/cppcheck.txt 2>/dev/null || true; })
+ cpp_perf=$({ grep -c 'performance:' lint_reports/cppcheck.txt 2>/dev/null || true; })
+ cpp_port=$({ grep -c 'portability:' lint_reports/cppcheck.txt 2>/dev/null || true; })
+ cpp_style=$({ grep -c 'style:' lint_reports/cppcheck.txt 2>/dev/null || true; })
+ echo "| Severity | Count |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY"
+ echo "| error | $cpp_errs |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| warning | $cpp_warns |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| performance | $cpp_perf |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| portability | $cpp_port |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| style | $cpp_style |" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "#### Findings by Component" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Component | Findings |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|-----------|----------|" >> "$GITHUB_STEP_SUMMARY"
+ for comp in IccProfLib IccXML Tools; do
+ f="lint_reports/parts/cppcheck_${comp}.txt"
+ if [ -f "$f" ] && [ -s "$f" ]; then
+ cnt=$(wc -l < "$f" | tr -d ' ')
+ else
+ cnt=0
+ fi
+ echo "| $comp | $cnt |" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "#### Top cppcheck Files" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ { grep -oP '^[^:]+\.(cpp|h)' lint_reports/cppcheck.txt 2>/dev/null || true; } \
+ | sort | uniq -c | sort -rn | head -10 \
+ | while read -r cnt fname; do
+ echo "- **$cnt** — $fname" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "cppcheck output (last 50 lines)
" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
while IFS= read -r line; do
safe_line=$(sanitize_line "$line")
echo "$safe_line"
- done < <(tail -n 30 lint_reports/cppcheck.txt) >> $GITHUB_STEP_SUMMARY
- echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
+ done < <(tail -n 50 lint_reports/cppcheck.txt) >> "$GITHUB_STEP_SUMMARY"
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
+ echo " " >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
else
- echo "✅ No cppcheck warnings or errors detected." >> $GITHUB_STEP_SUMMARY
+ echo "✅ No cppcheck findings." >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
fi
+ # --- clang-tidy summary ---
+ echo "### clang-tidy Results" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ if [ -s lint_reports/clang_tidy.txt ]; then
+ ct_warns=$({ grep -c 'warning:' lint_reports/clang_tidy.txt 2>/dev/null || true; })
+ ct_errs=$({ grep -c 'error:' lint_reports/clang_tidy.txt 2>/dev/null || true; })
+ echo "| Severity | Count |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY"
+ echo "| warning | $ct_warns |" >> "$GITHUB_STEP_SUMMARY"
+ echo "| error | $ct_errs |" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ # Per-category breakdown
+ echo "#### Findings by Check Category" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Category | Count |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY"
+ for cat in cppcoreguidelines modernize readability clang-analyzer-core clang-analyzer-security clang-analyzer-alpha; do
+ cnt=$({ grep -c "\[$cat" lint_reports/clang_tidy.txt 2>/dev/null || true; })
+ echo "| $cat | $cnt |" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ # Per-component breakdown
+ echo "#### Findings by Source Component" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Component | Warnings |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|-----------|----------|" >> "$GITHUB_STEP_SUMMARY"
+ for component in IccProfLib IccXML Tools; do
+ cnt=$({ grep "warning:.*\[" lint_reports/clang_tidy.txt 2>/dev/null || true; } | { grep "/${component}/" || true; } | wc -l | tr -d ' ')
+ echo "| $component | $cnt |" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ # Top 15 specific checks
+ echo "#### Top 15 Triggered Checks" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo "| Count | Check |" >> "$GITHUB_STEP_SUMMARY"
+ echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY"
+ { grep -oh '\[[-a-zA-Z.]*\]' lint_reports/clang_tidy.txt 2>/dev/null || true; } \
+ | sort | uniq -c | sort -rn | head -15 \
+ | while read -r cnt chk; do
+ echo "| $cnt | $chk |" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ # Security findings highlight
+ sec_count=$({ grep -c 'clang-analyzer-security\|clang-analyzer-alpha.security' lint_reports/clang_tidy.txt 2>/dev/null || true; })
+ if [ "$sec_count" -gt 0 ]; then
+ echo "#### ⚠️ Security Findings ($sec_count)" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
+ { grep 'clang-analyzer-security\|clang-analyzer-alpha.security' lint_reports/clang_tidy.txt || true; } | head -20 >> "$GITHUB_STEP_SUMMARY"
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ fi
+ # Top 10 files
+ echo "#### Top 10 Files by Warning Count" >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ { grep 'warning:' lint_reports/clang_tidy.txt 2>/dev/null || true; } \
+ | { grep -oP '[^ ]+\.(cpp|h)' || true; } \
+ | sort | uniq -c | sort -rn | head -10 \
+ | while read -r cnt fname; do
+ echo "- **$cnt** — $fname" >> "$GITHUB_STEP_SUMMARY"
+ done
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ else
+ echo "✅ No clang-tidy findings." >> "$GITHUB_STEP_SUMMARY"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ fi
+ echo "✔ Report generated." >> "$GITHUB_STEP_SUMMARY"
+ - name: Upload combined reports
+ if: always()
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: lint-reports
+ path: lint_reports/
+ if-no-files-found: warn
\ No newline at end of file
diff --git a/.github/workflows/ci-pr-unix.yml b/.github/workflows/ci-pr-unix.yml
index e7f92589b..63590868f 100644
--- a/.github/workflows/ci-pr-unix.yml
+++ b/.github/workflows/ci-pr-unix.yml
@@ -167,7 +167,7 @@ jobs:
git config --add safe.directory "$PWD"
git config --global credential.helper ""
unset GITHUB_TOKEN || true
- brew install cmake llvm wxwidgets libpng libtiff libxml2 nlohmann-json
+ brew install cmake llvm wxwidgets libpng libtiff libxml2 nlohmann-json jpeg-turbo
- name: Set Compiler Environment Variables
shell: bash --noprofile --norc {0}
diff --git a/.github/workflows/ci-wasm-build-test.yml b/.github/workflows/ci-wasm-build-test.yml
new file mode 100644
index 000000000..810bc90e7
--- /dev/null
+++ b/.github/workflows/ci-wasm-build-test.yml
@@ -0,0 +1,267 @@
+###############################################################
+#
+# Copyright (c) 2026 International Color Consortium.
+# All rights reserved.
+# https://color.org
+#
+# Intent: WASM build and validation matrix (Release, Debug, ASAN)
+#
+# Based on: wasm-latest-matrix.yml (known good pattern)
+# Adapted: For cfl/ integrated CMake build with hardened flags
+#
+# Pattern: Build third-party deps with emscripten, then build
+# iccDEV libraries and tools as WASM, validate output
+#
+###############################################################
+
+name: WASM Build & Test Matrix
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches: [ master, cfl ]
+
+concurrency:
+ group: wasm-build-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ wasm-build:
+ name: "WASM • ${{ matrix.build_type }}"
+ runs-on: ubuntu-24.04
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ build_type: [Release, Debug, Asan]
+ fail-fast: false
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install Emscripten SDK
+ run: |
+ set -euo pipefail
+ git clone --depth 1 https://github.com/emscripten-core/emsdk.git
+ cd emsdk
+ ./emsdk install latest
+ ./emsdk activate latest
+
+ - name: Build third-party dependencies
+ run: |
+ set -euo pipefail
+ source emsdk/emsdk_env.sh
+
+ THIRD_PARTY="$PWD/third_party"
+ mkdir -p "$THIRD_PARTY" && cd "$THIRD_PARTY"
+
+ # zlib
+ git clone --depth 1 https://github.com/madler/zlib.git && cd zlib
+ emconfigure ./configure --static --prefix="$THIRD_PARTY/zlib/out"
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ # libpng
+ curl -sLO https://download.sourceforge.net/libpng/libpng-1.6.51.tar.gz
+ tar -xzf libpng-1.6.51.tar.gz && mv libpng-1.6.51 libpng && cd libpng
+ CPPFLAGS="-I$THIRD_PARTY/zlib" LDFLAGS="-L$THIRD_PARTY/zlib/out/lib" \
+ emconfigure ./configure --disable-shared --enable-static --prefix="$THIRD_PARTY/libpng/out"
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ # libjpeg
+ curl -sLO https://ijg.org/files/jpegsrc.v9e.tar.gz
+ tar -xzf jpegsrc.v9e.tar.gz && mv jpeg-9e libjpeg && cd libjpeg
+ emconfigure ./configure --prefix="$THIRD_PARTY/libjpeg/out" --disable-shared --enable-static
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ # libtiff
+ git clone --depth 1 https://gitlab.com/libtiff/libtiff.git && cd libtiff
+ mkdir wasm && cd wasm
+ emcmake cmake .. \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$THIRD_PARTY/libtiff/out" \
+ -DZLIB_INCLUDE_DIR="$THIRD_PARTY/zlib" \
+ -DZLIB_LIBRARY="$THIRD_PARTY/zlib/out/lib/libz.a" \
+ -DJPEG_INCLUDE_DIR="$THIRD_PARTY/libjpeg/out/include" \
+ -DJPEG_LIBRARY="$THIRD_PARTY/libjpeg/out/lib/libjpeg.a"
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ # libxml2
+ git clone --depth 1 https://gitlab.gnome.org/GNOME/libxml2.git && cd libxml2
+ emconfigure ./autogen.sh --without-python --disable-shared --enable-static --prefix="$THIRD_PARTY/libxml2/out"
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ # nlohmann-json (build with CMake for find_package support)
+ git clone --depth 1 https://github.com/nlohmann/json.git nlohmann-json && cd nlohmann-json
+ mkdir wasm && cd wasm
+ emcmake cmake .. \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$THIRD_PARTY/nlohmann-json/out" \
+ -DJSON_BuildTests=OFF \
+ -DJSON_Install=ON
+ emmake make -j$(nproc)
+ emmake make install
+ cd "$THIRD_PARTY"
+
+ echo "### Third-party dependencies built" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ ls -d "$THIRD_PARTY"/*/out 2>/dev/null >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+
+ - name: Configure and build iccDEV WASM
+ run: |
+ set -euo pipefail
+ source emsdk/emsdk_env.sh
+
+ THIRD_PARTY="$PWD/third_party"
+
+ # Determine build type and extra flags
+ BUILD_FLAGS=""
+ BUILD_TYPE="${{ matrix.build_type }}"
+ if [[ "$BUILD_TYPE" == "Asan" ]]; then
+ BUILD_FLAGS="-fsanitize=address"
+ BUILD_TYPE="Debug"
+ fi
+
+ # wxWidgets not available for WASM — no action needed (not in top-level CMakeLists)
+ # Disable RELRO/NOW linker hardening (not supported by wasm-ld)
+ sed -i 's/-Wl,-z,relro,-z,now//' Build/Cmake/CMakeLists.txt
+
+ mkdir -p build-wasm && cd build-wasm
+ emcmake cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
+ -DENABLE_TOOLS=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_SHARED_LIBS=OFF \
+ -DENABLE_TESTS=OFF \
+ -DLIBXML2_INCLUDE_DIR="$THIRD_PARTY/libxml2/out/include/libxml2" \
+ -DLIBXML2_LIBRARY="$THIRD_PARTY/libxml2/out/lib/libxml2.a" \
+ -DTIFF_INCLUDE_DIR="$THIRD_PARTY/libtiff/out/include" \
+ -DTIFF_LIBRARY="$THIRD_PARTY/libtiff/out/lib/libtiff.a" \
+ -DJPEG_INCLUDE_DIR="$THIRD_PARTY/libjpeg/out/include" \
+ -DJPEG_LIBRARY="$THIRD_PARTY/libjpeg/out/lib/libjpeg.a" \
+ -DPNG_PNG_INCLUDE_DIR="$THIRD_PARTY/libpng/out/include" \
+ -DPNG_INCLUDE_DIR="$THIRD_PARTY/libpng/out/include" \
+ -DPNG_LIBRARY="$THIRD_PARTY/libpng/out/lib/libpng16.a" \
+ -DZLIB_INCLUDE_DIR="$THIRD_PARTY/zlib" \
+ -DZLIB_LIBRARY="$THIRD_PARTY/zlib/out/lib/libz.a" \
+ -Dnlohmann_json_DIR="$THIRD_PARTY/nlohmann-json/out/share/cmake/nlohmann_json" \
+ -DCMAKE_CXX_FLAGS=" \
+ $BUILD_FLAGS \
+ -I$THIRD_PARTY/libtiff/out/include \
+ -I$THIRD_PARTY/libjpeg/out/include \
+ -I$THIRD_PARTY/libpng/out/include \
+ -I$THIRD_PARTY/zlib \
+ -I$THIRD_PARTY/nlohmann-json/out/include" \
+ -DCMAKE_EXE_LINKER_FLAGS=" \
+ $THIRD_PARTY/libjpeg/out/lib/libjpeg.a \
+ $THIRD_PARTY/libpng/out/lib/libpng16.a \
+ $THIRD_PARTY/zlib/out/lib/libz.a \
+ -s INITIAL_MEMORY=128MB \
+ -s ALLOW_MEMORY_GROWTH=1 \
+ -s FORCE_FILESYSTEM=1 \
+ -s MODULARIZE=1 \
+ -s EXPORT_NAME=createModule \
+ -s EXPORTED_RUNTIME_METHODS=['FS','callMain']" \
+ -Wno-dev
+
+ emmake make -j$(nproc)
+
+ # Report results
+ JS_COUNT=$(find . -name '*.js' -not -path '*/CMakeFiles/*' | wc -l)
+ WASM_COUNT=$(find . -name '*.wasm' | wc -l)
+ LIB_COUNT=$(find . -name '*.a' | wc -l)
+
+ {
+ echo "### WASM Build: ${{ matrix.build_type }}"
+ echo "| Metric | Value |"
+ echo "|--------|-------|"
+ echo "| JS modules | $JS_COUNT |"
+ echo "| WASM binaries | $WASM_COUNT |"
+ echo "| Static libraries | $LIB_COUNT |"
+ echo ""
+ echo "#### JS Modules"
+ echo '```'
+ find . -name '*.js' -not -path '*/CMakeFiles/*' -exec ls -lh {} \;
+ echo '```'
+ echo "#### WASM Binaries"
+ echo '```'
+ find . -name '*.wasm' -exec ls -lh {} \;
+ echo '```'
+ } >> $GITHUB_STEP_SUMMARY
+
+ - name: Validate WASM output
+ run: |
+ set -euo pipefail
+ source emsdk/emsdk_env.sh
+
+ cd build-wasm
+
+ # Verify .wasm files exist and are valid
+ WASM_FILES=$(find . -name '*.wasm' -type f)
+ if [ -z "$WASM_FILES" ]; then
+ echo "::error::No WASM files produced"
+ exit 1
+ fi
+
+ # Validate each .wasm file header (magic number: \0asm)
+ VALID=0
+ INVALID=0
+ for wf in $WASM_FILES; do
+ MAGIC=$(xxd -l 4 -p "$wf" 2>/dev/null)
+ if [ "$MAGIC" = "0061736d" ]; then
+ SIZE=$(stat -c%s "$wf")
+ echo "VALID: $(basename $wf) ($SIZE bytes)"
+ VALID=$((VALID + 1))
+ else
+ echo "INVALID: $(basename $wf) (magic: $MAGIC)"
+ INVALID=$((INVALID + 1))
+ fi
+ done
+
+ echo "### Validation: $VALID valid, $INVALID invalid" >> $GITHUB_STEP_SUMMARY
+
+ if [ $INVALID -gt 0 ]; then
+ echo "::error::$INVALID invalid WASM files found"
+ exit 1
+ fi
+
+ # Run a quick smoke test with node if iccDumpProfile exists
+ DUMP_JS=$(find . -name "iccDumpProfile.js" -not -path '*/CMakeFiles/*' | head -1)
+ if [ -n "$DUMP_JS" ] && [ -f "$DUMP_JS" ]; then
+ echo "Smoke test: iccDumpProfile --help"
+ set +e
+ node "$DUMP_JS" 2>&1 | head -5
+ echo "iccDumpProfile WASM smoke test complete"
+ set -e
+ fi
+
+ - name: Upload WASM artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: wasm-${{ matrix.build_type }}
+ path: |
+ build-wasm/Tools/**/*.js
+ build-wasm/Tools/**/*.wasm
+ build-wasm/IccProfLib/*.a
+ build-wasm/IccXML/*.a
+ retention-days: 7
+ if-no-files-found: warn
diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml
new file mode 100644
index 000000000..7fc4a2cbc
--- /dev/null
+++ b/.github/workflows/clusterfuzzlite.yml
@@ -0,0 +1,352 @@
+###############################################################
+#
+# Copyright (c) 2026 International Color Consortium.
+# All rights reserved.
+# https://color.org
+#
+# Intent: ClusterFuzzLite parallel fuzzing campaign
+#
+# Based on: research/.github/workflows/cfl-libfuzzer-parallel.yml
+# (known good SUCCESS pattern)
+#
+# Pattern: CMake integrated build with ENABLE_FUZZING=ON,
+# matrix-parallel fuzz jobs, corpus caching, crash collection
+#
+###############################################################
+
+name: CFL LibFuzzer Parallel Campaign
+
+permissions:
+ contents: read
+ security-events: write
+
+on:
+ workflow_dispatch:
+ inputs:
+ fuzz_seconds:
+ description: 'Fuzzing duration per fuzzer in seconds (10-14400)'
+ required: false
+ default: '600'
+ type: string
+ sanitizer:
+ description: 'Sanitizer configuration'
+ required: false
+ default: 'address,undefined'
+ type: choice
+ options:
+ - 'address,undefined'
+ - 'address'
+ - 'undefined'
+ use_corpus_cache:
+ description: 'Use cached corpus from previous runs'
+ required: false
+ default: true
+ type: boolean
+ schedule:
+ - cron: '0 */6 * * *'
+
+concurrency:
+ group: cfl-parallel-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ FUZZ_SECONDS: ${{ github.event.inputs.fuzz_seconds || '600' }}
+ SANITIZER: ${{ github.event.inputs.sanitizer || 'address,undefined' }}
+
+jobs:
+ build:
+ name: "Build All Fuzzers (${{ github.event.inputs.sanitizer || 'address,undefined' }})"
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Validate inputs
+ run: |
+ set -euo pipefail
+ SECS=${{ env.FUZZ_SECONDS }}
+ if ! [[ "$SECS" =~ ^[0-9]+$ ]] || [ "$SECS" -lt 10 ] || [ "$SECS" -gt 14400 ]; then
+ echo "::error::fuzz_seconds must be 10-14400, got: $SECS"
+ exit 1
+ fi
+ echo "Validated: ${SECS}s fuzzing, sanitizer=${{ env.SANITIZER }}"
+
+ - name: Install dependencies
+ env:
+ DEBIAN_FRONTEND: noninteractive
+ run: |
+ set -euo pipefail
+ sudo apt-get update -qq
+ sudo apt-get install -y --no-install-recommends \
+ build-essential cmake clang clang-tools \
+ libxml2-dev libtiff-dev libpng-dev libjpeg-dev zlib1g-dev \
+ nlohmann-json3-dev llvm
+ echo "CC=clang" >> $GITHUB_ENV
+ echo "CXX=clang++" >> $GITHUB_ENV
+
+ - name: Configure CMake (Fuzzing)
+ run: |
+ set -euo pipefail
+ # Disable wxWidgets (not available on CI)
+ sed -i 's/^ find_package(wxWidgets/# find_package(wxWidgets/' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ ADD_SUBDIRECTORY(Tools\/wxProfileDump)/# ADD_SUBDIRECTORY(Tools\/wxProfileDump)/' Build/Cmake/CMakeLists.txt
+ sed -i 's/^ message(FATAL_ERROR "wxWidgets not found/# message(FATAL_ERROR "wxWidgets not found/' Build/Cmake/CMakeLists.txt
+
+ mkdir -p build-fuzz && cd build-fuzz
+ cmake ../Build/Cmake \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_CXX_COMPILER=clang++ \
+ -DCMAKE_C_COMPILER=clang \
+ -DENABLE_FUZZING=ON \
+ -DENABLE_STATIC_LIBS=ON \
+ -DENABLE_SHARED_LIBS=OFF \
+ -DENABLE_TOOLS=OFF \
+ -Wno-dev
+
+ - name: Build
+ run: |
+ set -euo pipefail
+ cd build-fuzz
+ make -j$(nproc)
+ FUZZER_COUNT=$(find Testing/Fuzzing -type f -executable -name "*_fuzzer" | wc -l)
+ {
+ echo "### Fuzzers Built: $FUZZER_COUNT"
+ echo '```'
+ find Testing/Fuzzing -type f -executable -name "*_fuzzer" | xargs -I{} basename {} | sort
+ echo '```'
+ } >> $GITHUB_STEP_SUMMARY
+
+ - name: Upload fuzzer binaries
+ uses: actions/upload-artifact@v4
+ with:
+ name: fuzzer-binaries-${{ env.SANITIZER }}
+ path: |
+ build-fuzz/Testing/Fuzzing/*_fuzzer
+ Testing/Fuzzing/*.dict
+ Testing/Fuzzing/*_seed_corpus/
+ retention-days: 3
+
+ fuzz:
+ name: "${{ matrix.fuzzer }} (${{ github.event.inputs.fuzz_seconds || '600' }}s)"
+ needs: build
+ runs-on: ubuntu-24.04
+ timeout-minutes: 25
+ strategy:
+ fail-fast: false
+ matrix:
+ fuzzer:
+ - icc_apply_fuzzer
+ - icc_applynamedcmm_fuzzer
+ - icc_applyprofiles_fuzzer
+ - icc_calculator_fuzzer
+ - icc_dump_fuzzer
+ - icc_fromxml_fuzzer
+ - icc_io_fuzzer
+ - icc_link_fuzzer
+ - icc_multitag_fuzzer
+ - icc_profile_fuzzer
+ - icc_roundtrip_fuzzer
+ - icc_spectral_fuzzer
+ - icc_toxml_fuzzer
+ - icc_v5dspobs_fuzzer
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Download fuzzer binaries
+ uses: actions/download-artifact@v4
+ with:
+ name: fuzzer-binaries-${{ env.SANITIZER }}
+
+ - name: Restore corpus cache
+ if: ${{ github.event.inputs.use_corpus_cache != 'false' }}
+ uses: actions/cache/restore@v4
+ with:
+ path: corpus-${{ matrix.fuzzer }}
+ key: cfl-corpus-${{ matrix.fuzzer }}-${{ github.run_number }}
+ restore-keys: |
+ cfl-corpus-${{ matrix.fuzzer }}-
+
+ - name: Run ${{ matrix.fuzzer }}
+ id: fuzz
+ run: |
+ set -euo pipefail
+
+ FUZZER="build-fuzz/Testing/Fuzzing/${{ matrix.fuzzer }}"
+ if [ ! -f "$FUZZER" ]; then
+ echo "Fuzzer ${{ matrix.fuzzer }} was not built (compile skip)"
+ echo "status=skip" >> $GITHUB_OUTPUT
+ echo "**${{ matrix.fuzzer }}:** SKIP (not compiled)" >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+
+ chmod +x "$FUZZER"
+ mkdir -p corpus-${{ matrix.fuzzer }}
+ mkdir -p crash-artifacts
+
+ FUZZ_SECONDS=${{ env.FUZZ_SECONDS }}
+
+ # Dictionary fallback chain
+ DICT_ARG=""
+ if [ -f "Testing/Fuzzing/${{ matrix.fuzzer }}.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/${{ matrix.fuzzer }}.dict"
+ elif [[ "${{ matrix.fuzzer }}" == *"xml"* ]] && [ -f "Testing/Fuzzing/icc_xml_consolidated.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/icc_xml_consolidated.dict"
+ elif [ -f "Testing/Fuzzing/icc_core.dict" ]; then
+ DICT_ARG="-dict=Testing/Fuzzing/icc_core.dict"
+ fi
+
+ # Seed corpus
+ SEED_DIR=""
+ if [ -d "Testing/Fuzzing/${{ matrix.fuzzer }}_seed_corpus" ]; then
+ SEED_DIR="Testing/Fuzzing/${{ matrix.fuzzer }}_seed_corpus"
+ fi
+
+ echo "::group::Fuzzer Output"
+ echo "Duration: ${FUZZ_SECONDS}s | Dict: ${DICT_ARG:-none} | Seeds: ${SEED_DIR:-none}"
+
+ set +e
+ timeout --kill-after=10s $((FUZZ_SECONDS + 30))s "$FUZZER" \
+ -max_total_time=${FUZZ_SECONDS} \
+ -print_final_stats=1 \
+ -print_pcs=1 \
+ -detect_leaks=0 \
+ -timeout=30 \
+ -rss_limit_mb=4096 \
+ -use_value_profile=1 \
+ -max_len=65536 \
+ $DICT_ARG \
+ corpus-${{ matrix.fuzzer }} $SEED_DIR 2>&1 | tee /tmp/fuzz-output.log | tail -50
+ EXIT_CODE=$?
+ set -e
+ echo "::endgroup::"
+
+ # Extract stats
+ EXECS=$(grep -oP 'stat::number_of_executed_inputs:\s*\K\d+' /tmp/fuzz-output.log 2>/dev/null | tr -d '\r\n' || echo "0")
+ NEW_UNITS=$(grep -oP 'stat::new_units_added:\s*\K\d+' /tmp/fuzz-output.log 2>/dev/null | tr -d '\r\n' || echo "0")
+ CORPUS_SIZE=$(ls corpus-${{ matrix.fuzzer }}/ 2>/dev/null | wc -l | tr -d '\r\n')
+
+ # Collect crashes
+ find . -maxdepth 1 \( -name "crash-*" -o -name "leak-*" -o -name "oom-*" -o -name "timeout-*" \) \
+ -exec cp {} crash-artifacts/ \; 2>/dev/null || true
+ CRASHES=$(ls crash-artifacts/ 2>/dev/null | wc -l | tr -d '\r\n')
+
+ if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 124 ]; then
+ STATUS="✅ PASS"
+ echo "status=pass" >> $GITHUB_OUTPUT
+ else
+ STATUS="❌ FAIL (exit $EXIT_CODE)"
+ echo "status=fail" >> $GITHUB_OUTPUT
+ fi
+
+ {
+ echo "### ${{ matrix.fuzzer }}: ${STATUS}"
+ echo "| Metric | Value |"
+ echo "|--------|-------|"
+ echo "| Exit Code | $EXIT_CODE |"
+ echo "| Duration | ${FUZZ_SECONDS}s |"
+ echo "| Executions | $EXECS |"
+ echo "| New Units | $NEW_UNITS |"
+ echo "| Corpus Size | $CORPUS_SIZE |"
+ echo "| Crashes | $CRASHES |"
+ } >> $GITHUB_STEP_SUMMARY
+
+ - name: Save corpus cache
+ if: always()
+ uses: actions/cache/save@v4
+ with:
+ path: corpus-${{ matrix.fuzzer }}
+ key: cfl-corpus-${{ matrix.fuzzer }}-${{ github.run_number }}
+
+ - name: Upload crash artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: crash-${{ matrix.fuzzer }}-${{ github.run_id }}
+ path: crash-artifacts/
+ retention-days: 90
+ if-no-files-found: ignore
+
+ - name: Upload corpus
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: corpus-${{ matrix.fuzzer }}-${{ github.run_id }}
+ path: corpus-${{ matrix.fuzzer }}/
+ retention-days: 7
+ if-no-files-found: ignore
+
+ report:
+ name: "Campaign Report"
+ needs: fuzz
+ if: always()
+ runs-on: ubuntu-24.04
+ defaults:
+ run:
+ shell: bash --noprofile --norc {0}
+
+ steps:
+ - name: Download all crash artifacts
+ uses: actions/download-artifact@v4
+ with:
+ pattern: crash-*
+ path: all-crashes/
+ merge-multiple: false
+
+ - name: Generate campaign summary
+ run: |
+ set -euo pipefail
+
+ TOTAL_CRASHES=0
+ if [ -d all-crashes ]; then
+ TOTAL_CRASHES=$(find all-crashes -type f ! -name "checksums.sha256" 2>/dev/null | wc -l)
+ fi
+
+ FUZZ_SECONDS=${{ env.FUZZ_SECONDS }}
+ SANITIZER="${{ env.SANITIZER }}"
+ FUZZERS=14
+
+ {
+ echo "# CFL LibFuzzer Parallel Campaign Report"
+ echo ""
+ echo "## Configuration"
+ echo "| Parameter | Value |"
+ echo "|-----------|-------|"
+ echo "| Duration per fuzzer | ${FUZZ_SECONDS}s |"
+ echo "| Sanitizer | \`${SANITIZER}\` |"
+ echo "| Parallel fuzzers | ${FUZZERS} |"
+ echo "| Total fuzzing time | $((FUZZ_SECONDS * FUZZERS))s (~$((FUZZ_SECONDS * FUZZERS / 60))m) |"
+ echo "| Run ID | ${{ github.run_id }} |"
+ echo "| Trigger | ${{ github.event_name }} |"
+ echo ""
+ echo "## Results"
+ echo "- **Total crash artifacts:** ${TOTAL_CRASHES}"
+ echo ""
+
+ if [ $TOTAL_CRASHES -gt 0 ]; then
+ echo "### Crash Inventory"
+ echo '```'
+ find all-crashes -type f ! -name "checksums.sha256" -exec basename {} \; 2>/dev/null | sort
+ echo '```'
+ else
+ echo "*No crashes found in this campaign.*"
+ fi
+ } >> $GITHUB_STEP_SUMMARY
+
+ - name: Upload consolidated report
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: campaign-report-${{ github.run_id }}
+ path: all-crashes/
+ retention-days: 90
+ if-no-files-found: ignore
diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml
index 67f9bf312..490796be4 100644
--- a/.github/workflows/label.yml
+++ b/.github/workflows/label.yml
@@ -1,36 +1,32 @@
###############################################################
#
-## Copyright (©) 2025 International Color Consortium.
-## All rights reserved.
+## Copyright (©) 2025 International Color Consortium.
+## All rights reserved.
## https://color.org
#
#
## Intent: Apply file-pattern-based labels to PRs using actions/labeler
#
-## Last Updated: 28-NOV-2025 2300Z by David Hoyt
-## Added user-controllable input (uci) checks to validate PR inputs
-## TODO: Push binary releases, tags etc..
-#
-## Comment: Known good and working from PatchIccMax
-#
-#
-#
-#
+## Last Updated: 2026-02-08 15:32:00 UTC
+# Remove labeled trigger (loop risk), add workflow_dispatch,
+# top-level permissions.
#
###############################################################
-
+
name: Labeler
+permissions:
+ contents: read
+ pull-requests: write
+
on:
pull_request:
- types: [opened, synchronize, reopened, labeled]
+ types: [opened, synchronize, reopened]
+ workflow_dispatch:
jobs:
apply-labels:
runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: write
steps:
- name: Apply PR labels
diff --git a/.github/workflows/update-labels.yml b/.github/workflows/update-labels.yml
index 85aae6131..204665852 100644
--- a/.github/workflows/update-labels.yml
+++ b/.github/workflows/update-labels.yml
@@ -1,21 +1,15 @@
###############################################################
#
-## Copyright (©) 2025 International Color Consortium.
-## All rights reserved.
+## Copyright (©) 2025 International Color Consortium.
+## All rights reserved.
## https://color.org
#
#
-## Intent: PR Status Labeler (update-labels.yml) - Labels PRs based on CI results
-#
-## Last Updated: 28-NOV-2025 2300Z by David Hoyt
-## Added user-controllable input (uci) checks
-## TODO: Push binary releases, tags etc..
-#
-## Comment: Known good and working from PatchIccMax
-#
-#
-#
+## Intent: PR Status Labeler - Labels PRs based on CI results
#
+## Last Updated: 2026-02-08 16:39:00 UTC
+# Batch schedule over all open PRs, ensure labels
+# exist, concurrency, sanitizer, checks:read perm.
#
###############################################################
@@ -25,112 +19,250 @@ on:
workflow_dispatch:
inputs:
pr_number:
- description: 'Pull request number to process manually'
+ description: 'Pull request number to process (blank = all open)'
required: false
default: ''
type: string
schedule:
- cron: '0 */4 * * *'
pull_request:
- types: [opened, synchronize, reopened, labeled, ready_for_review]
+ types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
pull-requests: write
- statuses: read # Explicitly added for clarity; required to read commit status
+ statuses: read
+ checks: read
+
+concurrency:
+ group: pr-status-labeler-${{ github.event.pull_request.number || 'batch' }}
+ cancel-in-progress: true
jobs:
label-status:
- # Only allow manual runs from the repo owner; PR/schedule events still allowed
if: >
github.event_name != 'workflow_dispatch' ||
contains(fromJson('["xsscx","maxderhak","ChrisCoxArt","dwtza"]'), github.actor)
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@c2d88d3ecc89a9ef08eebf45d9637801dcee7eb5
+ with:
+ sparse-checkout: .github/scripts
+ persist-credentials: false
+
+ - name: Ensure status labels exist
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+
+ gh label create "passed" --color "0e8a16" --description "All CI checks passed" 2>/dev/null || true
+ gh label create "failed" --color "d93f0b" --description "One or more CI checks failed" 2>/dev/null || true
+ gh label create "pending" --color "fbca04" --description "CI checks still running" 2>/dev/null || true
- - name: Determine PR number or input
- id: get_pr
+ - name: Build PR list
+ id: pr_list
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+
INPUT="${{ github.event.inputs.pr_number }}"
EVENT_PR="${{ github.event.pull_request.number }}"
+
if [[ "$INPUT" =~ ^[0-9]+$ ]] && [[ -n "$INPUT" ]]; then
- echo "pr_number=$INPUT" >> "$GITHUB_OUTPUT"
+ echo "prs=$INPUT" >> "$GITHUB_OUTPUT"
+ echo "mode=single" >> "$GITHUB_OUTPUT"
+ echo "Processing single PR: #$INPUT"
elif [[ "$EVENT_PR" =~ ^[0-9]+$ ]]; then
- echo "pr_number=$EVENT_PR" >> "$GITHUB_OUTPUT"
+ echo "prs=$EVENT_PR" >> "$GITHUB_OUTPUT"
+ echo "mode=single" >> "$GITHUB_OUTPUT"
+ echo "Processing PR from event: #$EVENT_PR"
else
- echo "pr_number=0" >> "$GITHUB_OUTPUT"
+ # Schedule / dispatch with no number → scan all open PRs
+ OPEN_PRS=$(gh pr list --state open --json number -q '.[].number' | tr '\n' ' ')
+ if [[ -z "$OPEN_PRS" ]]; then
+ echo "prs=" >> "$GITHUB_OUTPUT"
+ echo "mode=none" >> "$GITHUB_OUTPUT"
+ echo "No open PRs found — nothing to do"
+ else
+ echo "prs=$OPEN_PRS" >> "$GITHUB_OUTPUT"
+ echo "mode=batch" >> "$GITHUB_OUTPUT"
+ echo "Processing open PRs: $OPEN_PRS"
+ fi
fi
- - name: Evaluate PR combined status
- id: status
- if: steps.get_pr.outputs.pr_number != '0'
+ - name: Evaluate and label PRs
+ id: process
+ if: steps.pr_list.outputs.prs != ''
+ shell: bash --noprofile --norc {0}
env:
+ BASH_ENV: /dev/null
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- PR="${{ steps.get_pr.outputs.pr_number }}"
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
- # Ensure PR belongs to this repository
- BASE_REPO=$(gh pr view "$PR" --json baseRepository -q .baseRepository.nameWithOwner 2>/dev/null || echo "")
- if [[ -z "$BASE_REPO" || "$BASE_REPO" != "${GITHUB_REPOSITORY}" ]]; then
- echo "status=unknown" >> "$GITHUB_OUTPUT"
- exit 0
- fi
+ RESULTS_FILE=$(mktemp)
+ PROCESSED=0
+ LABELED=0
- HEAD=$(gh pr view "$PR" --json headRefOid -q .headRefOid 2>/dev/null || echo "")
- if [[ -z "$HEAD" ]]; then
- echo "status=unknown" >> "$GITHUB_OUTPUT"
- exit 0
- fi
+ evaluate_pr() {
+ local pr="$1"
- STATE=$(gh api "repos/${{ github.repository }}/commits/$HEAD/status" \
- --jq '.state' 2>/dev/null || echo "unknown")
+ # Validate PR belongs to this repo (not a cross-repo fork PR)
+ local is_cross
+ is_cross=$(gh pr view "$pr" --json isCrossRepository -q .isCrossRepository 2>/dev/null || echo "true")
+ if [[ "$is_cross" == "true" ]]; then
+ echo "$pr|skipped|Cross-repository PR" >> "$RESULTS_FILE"
+ return
+ fi
- echo "status=$STATE" >> "$GITHUB_OUTPUT"
- echo "Detected status: $STATE"
+ local head
+ head=$(gh pr view "$pr" --json headRefOid -q .headRefOid 2>/dev/null || echo "")
+ if [[ -z "$head" ]]; then
+ echo "$pr|skipped|Could not determine HEAD SHA" >> "$RESULTS_FILE"
+ return
+ fi
- - name: Relabel PR based on status
- id: relabel
- if: steps.get_pr.outputs.pr_number != '0' && steps.status.outputs.status != '' && steps.status.outputs.status != 'unknown'
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- PR="${{ steps.get_pr.outputs.pr_number }}"
- STATE="${{ steps.status.outputs.status }}"
- ADDED=""
- REMOVED=""
-
- case "$STATE" in
- success)
- gh pr edit "$PR" --add-label "passed" --remove-label "failed" || true
- ADDED="passed"
- REMOVED="failed"
- ;;
- failure|error)
- gh pr edit "$PR" --add-label "failed" --remove-label "passed" || true
- ADDED="failed"
- REMOVED="passed"
- ;;
- *)
- echo "No label change required for status: $STATE"
- ;;
- esac
-
- echo "added_label=$ADDED" >> "$GITHUB_OUTPUT"
- echo "removed_label=$REMOVED" >> "$GITHUB_OUTPUT"
+ # Query commit statuses API
+ local state
+ state=$(gh api "repos/${{ github.repository }}/commits/$head/status" \
+ --jq '.state' 2>/dev/null || echo "unknown")
+
+ # Query Check Runs API (Actions uses this, not statuses)
+ local check_conclusion
+ check_conclusion=$(gh api "repos/${{ github.repository }}/commits/$head/check-runs" \
+ --jq '
+ if (.total_count == 0) then "none"
+ else
+ [.check_runs[] | select(.name != "label-status") | .conclusion // "pending"]
+ | if length == 0 then "none"
+ elif all(. == "success") then "success"
+ elif any(. == "failure") then "failure"
+ elif any(. == "pending" or . == "null" or . == null) then "pending"
+ else "unknown" end
+ end' 2>/dev/null || echo "unknown")
+
+ # Combine results
+ local combined
+ if [[ "$state" == "failure" || "$check_conclusion" == "failure" ]]; then
+ combined="failure"
+ elif [[ "$state" == "success" && "$check_conclusion" == "success" ]]; then
+ combined="success"
+ elif [[ "$state" == "pending" || "$check_conclusion" == "pending" ]]; then
+ combined="pending"
+ elif [[ "$check_conclusion" == "none" && "$state" == "pending" ]]; then
+ combined="pending"
+ elif [[ "$check_conclusion" == "none" && "$state" == "unknown" ]]; then
+ # No checks at all yet — treat as pending
+ combined="pending"
+ else
+ combined="$state"
+ fi
+
+ # Apply label
+ local added="" removed=""
+ case "$combined" in
+ success)
+ gh pr edit "$pr" --add-label "passed" --remove-label "failed,pending" 2>/dev/null || true
+ added="passed"; removed="failed,pending"
+ ;;
+ failure|error)
+ gh pr edit "$pr" --add-label "failed" --remove-label "passed,pending" 2>/dev/null || true
+ added="failed"; removed="passed,pending"
+ ;;
+ pending)
+ gh pr edit "$pr" --add-label "pending" --remove-label "passed,failed" 2>/dev/null || true
+ added="pending"; removed="passed,failed"
+ ;;
+ esac
+
+ echo "$pr|$combined|status=$state checks=$check_conclusion → added=$added removed=$removed" >> "$RESULTS_FILE"
+ if [[ -n "$added" ]]; then
+ LABELED=$((LABELED + 1))
+ fi
+ }
+
+ for pr in ${{ steps.pr_list.outputs.prs }}; do
+ echo "── PR #$pr ──"
+ evaluate_pr "$pr"
+ PROCESSED=$((PROCESSED + 1))
+ done
+
+ echo "processed=$PROCESSED" >> "$GITHUB_OUTPUT"
+ echo "labeled=$LABELED" >> "$GITHUB_OUTPUT"
+ # Pass results file path for summary step
+ cp "$RESULTS_FILE" "${GITHUB_WORKSPACE}/pr_results.txt"
- name: Generate summary report
+ if: always()
+ shell: bash --noprofile --norc {0}
+ env:
+ BASH_ENV: /dev/null
run: |
+ set -euo pipefail
+ git config --add safe.directory "$PWD"
+ git config --global credential.helper ""
+ unset GITHUB_TOKEN || true
+
+ SANITIZER=".github/scripts/sanitize-sed.sh"
+ if [[ -f "$SANITIZER" ]]; then
+ # shellcheck disable=SC1090
+ source "$SANITIZER"
+ else
+ escape_html() {
+ local s="$1"
+ s="${s//&/&}"
+ s="${s//<}"
+ s="${s//>/>}"
+ s="${s//\"/"}"
+ s="${s//\'/'}"
+ printf '%s' "$s"
+ }
+ sanitize_line() { escape_html "$1"; }
+ fi
+
+ MODE="${{ steps.pr_list.outputs.mode }}"
+
{
- echo "### 🧾 PR Status Labeler Summary"
+ echo "### 🏷️ PR Status Labeler Summary"
echo ""
- echo "**Trigger Type:** ${{ github.event_name }}"
- echo "**PR Number:** ${{ steps.get_pr.outputs.pr_number }}"
- echo "**Detected Status:** ${{ steps.status.outputs.status }}"
- echo "**Label Added:** ${{ steps.relabel.outputs.added_label }}"
- echo "**Label Removed:** ${{ steps.relabel.outputs.removed_label }}"
+ echo "| Field | Value |"
+ echo "|-------|-------|"
+ echo "| Trigger | \`${{ github.event_name }}\` |"
+ echo "| Mode | $MODE |"
+ echo "| PRs Processed | ${{ steps.process.outputs.processed || '0' }} |"
+ echo "| Labels Applied | ${{ steps.process.outputs.labeled || '0' }} |"
echo ""
- echo "✅ Workflow completed."
} >> "$GITHUB_STEP_SUMMARY"
+
+ if [[ "$MODE" == "none" ]]; then
+ echo "🟦 No open PRs — nothing to label." >> "$GITHUB_STEP_SUMMARY"
+ elif [[ -f "${GITHUB_WORKSPACE}/pr_results.txt" ]]; then
+ {
+ echo "#### Per-PR Results"
+ echo ""
+ echo "| PR | Status | Details |"
+ echo "|----|--------|---------|"
+ } >> "$GITHUB_STEP_SUMMARY"
+ while IFS='|' read -r pr status details; do
+ safe_details=$(sanitize_line "$details")
+ echo "| [#${pr}](https://github.com/${{ github.repository }}/pull/${pr}) | \`${status}\` | ${safe_details} |" >> "$GITHUB_STEP_SUMMARY"
+ done < "${GITHUB_WORKSPACE}/pr_results.txt"
+ echo "" >> "$GITHUB_STEP_SUMMARY"
+ fi
+
+ echo "✅ Workflow completed." >> "$GITHUB_STEP_SUMMARY"
diff --git a/Build/Cmake/CMakeLists.txt b/Build/Cmake/CMakeLists.txt
index ebb8499cd..b7025e6db 100644
--- a/Build/Cmake/CMakeLists.txt
+++ b/Build/Cmake/CMakeLists.txt
@@ -3,9 +3,9 @@
# Copyright (C) 2024-2026 The International Color Consortium.
# All rights reserved.
#
-# Last Updated: 03-FEB-2026 at 2346Z by David Hoyt
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
#
-# Changes: Modify Version String to v2.3.1.4 References (#579)
+# Changes: Add WASM Toolchain, CFL & libFuzzer
#
#################################################################################
@@ -77,9 +77,10 @@ endif()
# ----------------------------------------------------------------------
# Compiler Selection for Unix (Clang preferred, GCC fallback)
+# Skip when cross-compiling with Emscripten (toolchain file sets em++/emcc)
# ----------------------------------------------------------------------
-if(UNIX AND NOT APPLE)
+if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
# Check if compiler was specified via environment or command line
if(NOT DEFINED CMAKE_C_COMPILER OR NOT DEFINED CMAKE_CXX_COMPILER)
if(NOT DEFINED ENV{CC} AND NOT DEFINED ENV{CXX})
@@ -523,12 +524,13 @@ option(ICC_ENABLE_ASSERTS "Enable ICC_ASSERT traps and debug assertions"
option(ICC_LOG_SAFE "Enable ICC_LOG_SAFE_VAL bounds-checked logging" OFF)
option(ENABLE_SANITIZERS "Enable runtime sanitizers (ASan, UBSan, etc.)" OFF)
option(ENABLE_ASAN "Enable AddressSanitizer only" OFF)
+option(ENABLE_FUZZING "Enable libFuzzer harnesses (Clang only)" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer only" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer (conflicts with ASan)" OFF)
option(ENABLE_MSAN "Enable MemorySanitizer (Clang only)" OFF)
option(ENABLE_LSAN "Enable LeakSanitizer standalone" OFF)
-option(ENABLE_FUZZING "Enable fuzzing instrumentation (libFuzzer)" OFF)
option(ENABLE_COVERAGE "Enable code coverage instrumentation" OFF)
+option(ENABLE_PROFILING "Enable gprof/perf profiling instrumentation" OFF)
option(ENABLE_SPECTRE_MITIGATION "Enable /Qspectre on MSVC for Spectre V1 mitigation" OFF)
option(ENABLE_USEICCDEVNAMESPACE "Use iccDEV namespace wrapping" OFF)
@@ -574,6 +576,7 @@ message(STATUS "ENABLE_MSAN = ${ENABLE_MSAN}")
message(STATUS "ENABLE_LSAN = ${ENABLE_LSAN}")
message(STATUS "ENABLE_FUZZING = ${ENABLE_FUZZING}")
message(STATUS "ENABLE_COVERAGE = ${ENABLE_COVERAGE}")
+message(STATUS "ENABLE_PROFILING = ${ENABLE_PROFILING}")
message(STATUS "ENABLE_SPECTRE_MITIGATION = ${ENABLE_SPECTRE_MITIGATION}")
message(STATUS "")
@@ -627,11 +630,9 @@ set(SANITIZER_LIST "")
if(ENABLE_FUZZING)
message(STATUS ">>> Fuzzing instrumentation enabled (libFuzzer + ASan + UBSan)")
- if(MSVC)
- message(WARNING "Fuzzing with libFuzzer is not supported on MSVC")
- else()
- list(APPEND SANITIZER_LIST "fuzzer" "address" "undefined")
- endif()
+ message(STATUS ">>> Fuzzer flags applied per-target in Testing/Fuzzing/CMakeLists.txt")
+ # DO NOT apply fuzzer flags globally - causes linker conflicts with regular tools
+ # Fuzzer targets get flags via target_compile_options/target_link_options
elseif(ENABLE_SANITIZERS)
message(STATUS ">>> Sanitizers enabled: AddressSanitizer and UndefinedBehaviorSanitizer active")
if(MSVC)
@@ -696,6 +697,11 @@ if(SANITIZER_LIST)
string(REPLACE ";" "," SANITIZER_STRING "${SANITIZER_LIST}")
list(APPEND SANITIZER_FLAGS "-fsanitize=${SANITIZER_STRING}")
list(APPEND SANITIZER_FLAGS "-fno-omit-frame-pointer")
+ # Make UndefinedBehaviorSanitizer violations fatal (abort on first UB)
+ list(FIND SANITIZER_LIST "undefined" _ubsan_idx)
+ if(NOT _ubsan_idx EQUAL -1)
+ list(APPEND SANITIZER_FLAGS "-fno-sanitize-recover=undefined")
+ endif()
endif()
# Apply all sanitizer flags
@@ -705,23 +711,36 @@ if(SANITIZER_FLAGS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FINAL_SAN_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FINAL_SAN_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FINAL_SAN_FLAGS}")
- if(VERBOSE_CONFIG)
- message(STATUS ">>> Final sanitizer flags: ${FINAL_SAN_FLAGS}")
- endif()
+ message(STATUS ">>> Final sanitizer flags: ${FINAL_SAN_FLAGS}")
endif()
# Apply coverage instrumentation flags
if(ENABLE_COVERAGE)
message(STATUS ">>> Coverage instrumentation enabled")
- list(APPEND COVERAGE_FLAGS "--coverage")
- string(JOIN " " FINAL_COV_FLAGS ${COVERAGE_FLAGS})
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FINAL_COV_FLAGS}")
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FINAL_COV_FLAGS}")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FINAL_COV_FLAGS}")
- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FINAL_COV_FLAGS}")
- if(VERBOSE_CONFIG)
- message(STATUS ">>> Final coverage flags: ${FINAL_COV_FLAGS}")
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ # Clang source-based coverage (preferred: precise, mergeable)
+ set(COVERAGE_COMPILE_FLAGS "-fprofile-instr-generate -fcoverage-mapping")
+ set(COVERAGE_LINK_FLAGS "-fprofile-instr-generate")
+ message(STATUS ">>> Using Clang source-based coverage (llvm-profdata/llvm-cov)")
+ else()
+ # GCC gcov-based coverage
+ set(COVERAGE_COMPILE_FLAGS "--coverage")
+ set(COVERAGE_LINK_FLAGS "--coverage")
+ message(STATUS ">>> Using GCC gcov-based coverage")
endif()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILE_FLAGS}")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILE_FLAGS}")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}")
+endif()
+
+# Apply profiling instrumentation flags
+if(ENABLE_PROFILING)
+ message(STATUS ">>> Profiling instrumentation enabled (-pg)")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg -fno-omit-frame-pointer")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg -fno-omit-frame-pointer")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
endif()
# ----------------------------------------------------------------------
@@ -732,24 +751,22 @@ include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output)
if(ipo_supported)
- if(CMAKE_BUILD_TYPE MATCHES "Release" AND NOT ENABLE_COVERAGE AND NOT SANITIZER_FLAGS)
+ if(CMAKE_BUILD_TYPE MATCHES "Release" AND NOT ENABLE_COVERAGE AND NOT ENABLE_PROFILING AND NOT SANITIZER_FLAGS)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
- if(VERBOSE_CONFIG)
- message(STATUS ">>> Link-Time Optimization (LTO) enabled for Release builds")
- endif()
+ message(STATUS ">>> Link-Time Optimization (LTO) enabled for Release builds")
elseif(CMAKE_BUILD_TYPE MATCHES "Release")
- if(VERBOSE_CONFIG)
- if(ENABLE_COVERAGE)
- message(STATUS ">>> Link-Time Optimization (LTO) disabled because ENABLE_COVERAGE is ON")
- elseif(SANITIZER_FLAGS)
- message(STATUS ">>> Link-Time Optimization (LTO) disabled because sanitizers are enabled")
- endif()
+ set(_lto_reason "")
+ if(ENABLE_COVERAGE)
+ set(_lto_reason "ENABLE_COVERAGE is ON")
+ elseif(ENABLE_PROFILING)
+ set(_lto_reason "ENABLE_PROFILING is ON")
+ elseif(SANITIZER_FLAGS)
+ set(_lto_reason "sanitizers are enabled")
endif()
+ message(STATUS ">>> Link-Time Optimization (LTO) disabled: ${_lto_reason}")
endif()
else()
- if(VERBOSE_CONFIG)
- message(STATUS ">>> Link-Time Optimization (LTO) not supported: ${ipo_output}")
- endif()
+ message(STATUS ">>> Link-Time Optimization (LTO) not supported: ${ipo_output}")
endif()
# ----------------------------------------------------------------------
@@ -906,23 +923,28 @@ elseif(UNIX AND NOT APPLE)
# Recommended flags for Linux/Clang or GCC
add_compile_options(
-Wall
+ -Wextra
+ -Wformat
+ -Wformat-security
+ -Wimplicit-fallthrough
-Wno-overloaded-virtual
-Wno-switch
-Wno-unused-parameter
-Wno-unused-variable
-Wno-missing-field-initializers
-fstack-protector-strong
+ -fstack-clash-protection
)
- # Hardening: PIE and RELRO for executables
+ # Hardening: RELRO and BIND_NOW for executables
add_link_options(-Wl,-z,relro,-z,now)
- # Fortify source for Release builds
+ # Fortify source for optimized builds (requires -O1 or higher)
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
add_compile_definitions(_FORTIFY_SOURCE=2)
endif()
- message(STATUS "Linux build: Standard warning flags and hardening applied.")
+ message(STATUS "Linux build: Warning flags and security hardening applied.")
elseif(WIN32)
message(STATUS "Detected platform: Windows")
@@ -942,15 +964,15 @@ unset(IccProfLib2_LIB CACHE)
unset(IccProfLib2 CACHE)
# ----------------------------------------------------------------------
-# Third-Party Dependencies
+# Third-Party Dependencies (already found above — verify availability)
# ----------------------------------------------------------------------
message(STATUS "")
message(STATUS "## Third Party Dependencies ##")
-find_package(LibXml2 REQUIRED)
-find_package(nlohmann_json REQUIRED)
-find_package(TIFF REQUIRED)
-find_package(PNG REQUIRED)
-find_package(JPEG REQUIRED)
+message(STATUS " LibXml2: ${LIBXML2_LIBRARIES}")
+message(STATUS " nlohmann_json: found")
+message(STATUS " TIFF: ${TIFF_LIBRARIES}")
+message(STATUS " PNG: ${PNG_LIBRARIES}")
+message(STATUS " JPEG: ${JPEG_LIBRARIES}")
message(STATUS "")
#
@@ -1001,9 +1023,13 @@ message(STATUS "## Project Configuration ##")
message(STATUS "Adding subdirectory for IccProfLib.")
add_subdirectory(IccProfLib)
-# Set default link target for IccProfLib
+# Set default link target for IccProfLib (matches build configuration)
# Use CACHE INTERNAL so that it's available in Tools/* subdirectories
-set(TARGET_LIB_ICCPROFLIB IccProfLib2 CACHE INTERNAL "Link target for IccProfLib2")
+IF(ENABLE_SHARED_LIBS)
+ set(TARGET_LIB_ICCPROFLIB IccProfLib2 CACHE INTERNAL "Link target for IccProfLib2")
+ELSE()
+ set(TARGET_LIB_ICCPROFLIB IccProfLib2-static CACHE INTERNAL "Link target for IccProfLib2")
+ENDIF()
#
# Optional external dependency: LibXML2
@@ -1055,6 +1081,10 @@ ENDIF()
# Ensure IccXML headers are available globally
include_directories(${TOP_SOURCE_DIR}/IccXML/IccLibXML/)
+# Ensure generated version headers are available globally
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/IccProfLib)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/IccXML)
+
# Diagnostic: Output current linker flags
message(STATUS "CMAKE_EXE_LINKER_FLAGS = ${CMAKE_EXE_LINKER_FLAGS}")
@@ -1062,8 +1092,7 @@ message(STATUS "CMAKE_SHARED_LINKER_FLAGS = ${CMAKE_SHARED_LINKER_FLAGS}")
message(STATUS "CMAKE_MODULE_LINKER_FLAGS = ${CMAKE_MODULE_LINKER_FLAGS}")
-# Ensure nlohmann_json is found
-find_package(nlohmann_json REQUIRED)
+# nlohmann_json already found above
message(STATUS "nlohmann_json library found: ${nlohmann_json_DIR}")
# Function to add and log subdirectories
@@ -1116,9 +1145,8 @@ IF(ENABLE_TOOLS)
ENDIF()
-# --- PNG ---
+# --- PNG (already found above) ---
message(STATUS "Checking for PNG...")
-find_package(PNG REQUIRED)
if(PNG_FOUND)
message(STATUS "PNG Library : ${PNG_LIBRARIES}")
@@ -1135,8 +1163,7 @@ ADD_SUBDIRECTORY(Tools/IccPngDump)
ADD_SUBDIRECTORY(Tools/IccDEVCmm)
- # Check for TIFF library
- find_package(TIFF REQUIRED)
+ # TIFF already found above
if (TIFF_FOUND)
include_directories(${TIFF_INCLUDE_DIR})
message(STATUS "TIFF library found: ${TIFF_LIBRARIES}")
@@ -1152,20 +1179,21 @@ ADD_SUBDIRECTORY(Tools/IccPngDump)
message(FATAL_ERROR "TIFF library not found. Please install libtiff-dev.")
endif()
- # Configure wxWidgets
- find_package(wxWidgets COMPONENTS core base REQUIRED)
- if (wxWidgets_FOUND)
+ # wxWidgets GUI tool (optional)
+ find_package(wxWidgets QUIET COMPONENTS core base)
+ if(wxWidgets_FOUND)
include(${wxWidgets_USE_FILE})
- ADD_SUBDIRECTORY(Tools/wxProfileDump)
- message(STATUS "wxWidgets found and configured")
+ message(STATUS "wxWidgets found: ${wxWidgets_LIBRARIES}")
+ message(STATUS "Adding Subdirectory wxProfileDump.")
+ ADD_SUBDIRECTORY(Tools/wxProfileDump)
else()
- message(FATAL_ERROR "wxWidgets not found. Please install it manually or via vcpkg.")
+ message(STATUS "wxWidgets not found - skipping wxProfileDump.")
endif()
ENDIF(ENABLE_TOOLS)
IF( ENABLE_TESTS )
- ADD_SUBDIRECTORY( Testing )
+ ADD_SUBDIRECTORY( ../../Testing ${CMAKE_CURRENT_BINARY_DIR}/Testing )
ENDIF( ENABLE_TESTS )
CONFIGURE_FILE(
diff --git a/Build/Cmake/IccProfLib/CMakeLists.txt b/Build/Cmake/IccProfLib/CMakeLists.txt
index 44e704f3e..7016df949 100644
--- a/Build/Cmake/IccProfLib/CMakeLists.txt
+++ b/Build/Cmake/IccProfLib/CMakeLists.txt
@@ -3,9 +3,9 @@
# Copyright (©) 2024-2026 The International Color Consortium.
# All rights reserved.
#
-# Last Updated: 05-FEB-2026 1645Z by David Hoyt
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
#
-# Changes: Modify Version String with Commit Hash
+# Changes: Add WASM Toolchain, CFL & libFuzzer
#
#################################################################################
@@ -108,12 +108,16 @@ else()
set(ICCPROFLIB_VERSION_STRING "${${PROJECT_UP_NAME}_VERSION}")
endif()
+# Generate version header into build directory (not source tree)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/../../../IccProfLib/IccProfLibVer.h.in"
- "${CMAKE_CURRENT_SOURCE_DIR}/../../../IccProfLib/IccProfLibVer.h"
+ "${CMAKE_CURRENT_BINARY_DIR}/IccProfLibVer.h"
@ONLY
)
+# Ensure build directory is in include path for generated header
+include_directories("${CMAKE_CURRENT_BINARY_DIR}")
+
SET(SOURCES ${CFILES})
IF(APPLE)
@@ -163,6 +167,12 @@ IF(ENABLE_STATIC_LIBS)
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
+ # Create ALIAS target when no shared library (STATIC_ONLY configuration)
+ # This ensures ${TARGET_NAME} always exists for target_link_libraries
+ IF(NOT ENABLE_SHARED_LIBS)
+ ADD_LIBRARY(${TARGET_NAME} ALIAS ${TARGET_NAME}-static)
+ ENDIF()
+
# Provide alias to resolve MSVC LNK1104: expected IccProfLib2.lib
IF(WIN32 AND ENABLE_SHARED_LIBS)
ADD_CUSTOM_COMMAND(TARGET ${TARGET_NAME}-static POST_BUILD
diff --git a/Build/Cmake/IccXML/CMakeLists.txt b/Build/Cmake/IccXML/CMakeLists.txt
index c5a46a35f..662a0f2ed 100644
--- a/Build/Cmake/IccXML/CMakeLists.txt
+++ b/Build/Cmake/IccXML/CMakeLists.txt
@@ -3,9 +3,9 @@
# Copyright (©) 2024-2026 The International Color Consortium.
# All rights reserved.
#
-# Last Updated: 04-FEB-2026 at 0430Z by David Hoyt
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
#
-# Changes: Modify Version to contain Commit Hash
+# Changes: Add WASM Toolchain, CFL & libFuzzer
#
#################################################################################
@@ -46,12 +46,16 @@ else()
set(ICCLIBXML_VERSION_STRING "${${PROJECT_UP_NAME}_VERSION}")
endif()
+# Generate version header into build directory (not source tree)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/../../../IccXML/IccLibXML/IccLibXMLVer.h.in"
- "${CMAKE_CURRENT_SOURCE_DIR}/../../../IccXML/IccLibXML/IccLibXMLVer.h"
+ "${CMAKE_CURRENT_BINARY_DIR}/IccLibXMLVer.h"
@ONLY
)
+# Ensure build directory is in include path for generated header
+include_directories("${CMAKE_CURRENT_BINARY_DIR}")
+
# message(STATUS "Starting CMake debug for target: ${TARGET_NAME}")
# message(STATUS "CFILES = ${CFILES}")
# message(STATUS "ENABLE_SHARED_LIBS = ${ENABLE_SHARED_LIBS}")
@@ -125,6 +129,11 @@ IF(ENABLE_STATIC_LIBS)
"$/${TARGET_NAME}.lib"
COMMENT "Aliasing ${TARGET_NAME}-static.lib to ${TARGET_NAME}.lib for MSVC compatibility")
ENDIF()
+
+ # If no shared library, create alias so IccXML2 always exists
+ IF(NOT ENABLE_SHARED_LIBS)
+ ADD_LIBRARY(${TARGET_NAME} ALIAS ${TARGET_NAME}-static)
+ ENDIF()
ENDIF()
# Resolve linking target for parent scope
diff --git a/Build/Cmake/Tools/IccPngDump/CMakeLists.txt b/Build/Cmake/Tools/IccPngDump/CMakeLists.txt
index 0b895ac3a..54fdf4367 100644
--- a/Build/Cmake/Tools/IccPngDump/CMakeLists.txt
+++ b/Build/Cmake/Tools/IccPngDump/CMakeLists.txt
@@ -46,7 +46,7 @@ target_include_directories(${TARGET_NAME} PRIVATE
)
# Unix check
-if(UNIX)
+if(UNIX AND NOT EMSCRIPTEN)
target_include_directories(${TARGET_NAME} PRIVATE /usr/include /usr/local/include)
endif()
diff --git a/Testing/CMakeLists.txt b/Testing/CMakeLists.txt
new file mode 100644
index 000000000..a50df9a00
--- /dev/null
+++ b/Testing/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+#
+#
+
+# Testing subdirectory
+cmake_minimum_required(VERSION 3.15)
+
+# Only add fuzzing harnesses when enabled
+if(ENABLE_FUZZING)
+ add_subdirectory(Fuzzing)
+endif()
diff --git a/Testing/CalcTest/checkInvalidProfiles.sh b/Testing/CalcTest/checkInvalidProfiles.sh
index eea045991..21e4a09d7 100755
--- a/Testing/CalcTest/checkInvalidProfiles.sh
+++ b/Testing/CalcTest/checkInvalidProfiles.sh
@@ -1,34 +1,24 @@
#!/bin/sh
#################################################################################
-# CalcTest/checkInvalidProfiles.sh | iccMAX Project
-# Copyright (C) 2024-2025 The International Color Consortium.
+# CalcTest/checkInvalidProfiles.sh | iccDEV Project
+# Copyright (C) 2024-2026 The International Color Consortium.
# All rights reserved.
#
#
-# Last Updated: 24-APRIL-2025 16:00 EDT 2025 by David Hoyt
+# Last Updated: 2026-02-11 16:41:15 UTC by David Hoyt
+# Remove PATH
#
#
#
#
#
-#
-# Intent: iccMAX CICD
+# Intent: iccDEV CICD
#
#
#
#
#################################################################################
-echo "====================== Entering CalcTest/checkInvalidProfiles.sh =========================="
-
-# Properly handle newline-separated paths as a list
-find ../../Build/Tools -type f -perm -111 -exec dirname {} \; | sort -u | while read -r d; do
- abs_path=$(cd "$d" && pwd)
- PATH="$abs_path:$PATH"
-done
-
-export PATH
-
echo "====================== Running checkInvalidProfiles.sh Checks =========================="
diff --git a/Testing/CreateAllProfiles.bat b/Testing/CreateAllProfiles.bat
index b16e853dc..67d8d4e70 100644
--- a/Testing/CreateAllProfiles.bat
+++ b/Testing/CreateAllProfiles.bat
@@ -1,4 +1,6 @@
@echo off
+:: Auto-call path.bat if present alongside this script (for standalone bundles)
+if exist "%~dp0path.bat" call "%~dp0path.bat"
where iccFromXml
if not "%1"=="clean" goto do_begin
echo CLEANING!
diff --git a/Testing/CreateAllProfiles.sh b/Testing/CreateAllProfiles.sh
index 5fa56c286..6f8c464f3 100755
--- a/Testing/CreateAllProfiles.sh
+++ b/Testing/CreateAllProfiles.sh
@@ -1,33 +1,31 @@
#!/bin/sh
#################################################################################
-# Testing/CreateAllProfiles.sh | iccMAX Project
-# Copyright (C) 2024-2025 The International Color Consortium.
+# Testing/CreateAllProfiles.sh | iccDEV Project
+# Copyright (C) 2024-2026 The International Color Consortium.
# All rights reserved.
#
#
-# Last Updated: Thu May 8 07:33:04 EDT 2025 by David Hoyt
+# Last Updated: 2026-02-11 16:41:15 UTC by David Hoyt
+# Remove PATH
#
#
#
#
#
-#
-# Intent: iccMAX CICD
+# Intent: iccDEV CICD
#
#
#
#
#################################################################################
-echo "====================== Entering Testing/CreateAllProfiles.sh =========================="
-
-# Properly handle newline-separated paths as a list
-find ../Build/Tools -type f -perm -111 -exec dirname {} \; | sort -u | while read -r d; do
- abs_path=$(cd "$d" && pwd)
- PATH="$abs_path:$PATH"
-done
+# Auto-source path.sh if present (sets PATH and LD_LIBRARY_PATH/DYLD_LIBRARY_PATH)
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+if [ -f "$SCRIPT_DIR/path.sh" ]; then
+ . "$SCRIPT_DIR/path.sh"
+fi
-export PATH
+echo "====================== Entering Testing/CreateAllProfiles.sh =========================="
if [ "$1" = "clean" ]
then
diff --git a/Testing/Fuzzing/CMakeLists.txt b/Testing/Fuzzing/CMakeLists.txt
new file mode 100644
index 000000000..fbd746c98
--- /dev/null
+++ b/Testing/Fuzzing/CMakeLists.txt
@@ -0,0 +1,174 @@
+# Fuzzing Harnesses for iccDEV
+# CFL Branch - Complete LibFuzzer Integration with Full Instrumentation
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+#
+#
+#
+#
+#
+
+cmake_minimum_required(VERSION 3.15)
+
+# Only build fuzzers with Clang and when explicitly enabled
+if(ENABLE_FUZZING AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ message(STATUS "Fuzzing harnesses enabled with full instrumentation")
+
+ # Common include directories
+ set(FUZZER_INCLUDES
+ ${CMAKE_SOURCE_DIR}/../../IccProfLib
+ ${CMAKE_SOURCE_DIR}/../../IccXML/IccLibXML
+ ${CMAKE_SOURCE_DIR}/../../Tools/CmdLine/IccCommon
+ ${CMAKE_SOURCE_DIR}/../../Tools/CmdLine/IccApplyProfiles
+ )
+
+ # Full instrumentation flags: sanitizers, debug, profiling, coverage
+ # Based on iccAnalyzer instrumented build configuration
+ set(FUZZER_COMPILE_FLAGS
+ -fsanitize=fuzzer,address,undefined
+ -g # Full debug symbols
+ -O1 # Minimal optimization for fuzzing
+ -fno-omit-frame-pointer # Preserve frame pointers for stack traces
+ -fprofile-instr-generate # Clang instrumentation profiling
+ -fcoverage-mapping # Clang coverage mapping
+ )
+
+ set(FUZZER_LINK_FLAGS
+ -fsanitize=fuzzer,address,undefined
+ -fprofile-instr-generate # Clang instrumentation profiling
+ )
+
+ # No additional link libraries needed - Clang provides runtime automatically
+
+ # Core ICC profile fuzzer
+ add_executable(icc_profile_fuzzer icc_profile_fuzzer.cpp)
+ target_compile_options(icc_profile_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_profile_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_profile_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_profile_fuzzer IccProfLib2)
+
+ # Calculator fuzzer
+ add_executable(icc_calculator_fuzzer icc_calculator_fuzzer.cpp)
+ target_compile_options(icc_calculator_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_calculator_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_calculator_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_calculator_fuzzer IccProfLib2)
+
+ # V5 DSP/OBS fuzzer
+ add_executable(icc_v5dspobs_fuzzer icc_v5dspobs_fuzzer.cpp)
+ target_compile_options(icc_v5dspobs_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_v5dspobs_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_v5dspobs_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_v5dspobs_fuzzer IccProfLib2)
+
+ # Multitag fuzzer
+ add_executable(icc_multitag_fuzzer icc_multitag_fuzzer.cpp)
+ target_compile_options(icc_multitag_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_multitag_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_multitag_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_multitag_fuzzer IccProfLib2)
+
+ # Roundtrip fuzzer
+ add_executable(icc_roundtrip_fuzzer icc_roundtrip_fuzzer.cpp)
+ target_compile_options(icc_roundtrip_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_roundtrip_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_roundtrip_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_roundtrip_fuzzer IccProfLib2)
+
+ # Dump fuzzer
+ add_executable(icc_dump_fuzzer icc_dump_fuzzer.cpp)
+ target_compile_options(icc_dump_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_dump_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_dump_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_dump_fuzzer IccProfLib2)
+
+ # IO fuzzer
+ add_executable(icc_io_fuzzer icc_io_fuzzer.cpp)
+ target_compile_options(icc_io_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_io_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_io_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_io_fuzzer IccProfLib2)
+
+ # Link fuzzer
+ add_executable(icc_link_fuzzer icc_link_fuzzer.cpp)
+ target_compile_options(icc_link_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_link_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_link_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_link_fuzzer IccProfLib2)
+
+ # Spectral fuzzer
+ add_executable(icc_spectral_fuzzer icc_spectral_fuzzer.cpp)
+ target_compile_options(icc_spectral_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_spectral_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_spectral_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_spectral_fuzzer IccProfLib2)
+
+ # Apply fuzzer
+ add_executable(icc_apply_fuzzer icc_apply_fuzzer.cpp)
+ target_compile_options(icc_apply_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_apply_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_apply_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_apply_fuzzer IccProfLib2)
+
+ # ApplyNamedCmm fuzzer
+ add_executable(icc_applynamedcmm_fuzzer icc_applynamedcmm_fuzzer.cpp)
+ target_compile_options(icc_applynamedcmm_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_applynamedcmm_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_applynamedcmm_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_applynamedcmm_fuzzer IccProfLib2)
+
+ # ApplyProfiles fuzzer
+ add_executable(icc_applyprofiles_fuzzer icc_applyprofiles_fuzzer.cpp)
+ target_compile_options(icc_applyprofiles_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_applyprofiles_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_applyprofiles_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_applyprofiles_fuzzer IccProfLib2)
+
+ # XML-based fuzzers (require IccXML)
+ if(TARGET IccXML2)
+ # FromXML fuzzer
+ add_executable(icc_fromxml_fuzzer icc_fromxml_fuzzer.cpp)
+ target_compile_options(icc_fromxml_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_fromxml_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_fromxml_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_fromxml_fuzzer IccProfLib2 IccXML2 xml2)
+
+ # ToXML fuzzer
+ add_executable(icc_toxml_fuzzer icc_toxml_fuzzer.cpp)
+ target_compile_options(icc_toxml_fuzzer PRIVATE ${FUZZER_COMPILE_FLAGS})
+ target_link_options(icc_toxml_fuzzer PRIVATE ${FUZZER_LINK_FLAGS})
+ target_include_directories(icc_toxml_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ target_link_libraries(icc_toxml_fuzzer IccProfLib2 IccXML2 xml2)
+ endif()
+
+ # TIFF-based fuzzers (require TIFF)
+ if(TIFF_FOUND)
+ # SpecSep and TiffDump fuzzers require TiffImg.cpp compilation
+ # TODO: Add TiffImg.cpp to build or create separate TIFF tools library
+ #
+ # add_executable(icc_specsep_fuzzer icc_specsep_fuzzer.cpp)
+ # target_compile_options(icc_specsep_fuzzer PRIVATE -fsanitize=fuzzer,address,undefined -g -O1)
+ # target_link_options(icc_specsep_fuzzer PRIVATE -fsanitize=fuzzer,address,undefined)
+ # target_include_directories(icc_specsep_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ # target_link_libraries(icc_specsep_fuzzer IccProfLib2 ${TIFF_LIBRARIES})
+ #
+ # add_executable(icc_tiffdump_fuzzer icc_tiffdump_fuzzer.cpp)
+ # target_compile_options(icc_tiffdump_fuzzer PRIVATE -fsanitize=fuzzer,address,undefined -g -O1)
+ # target_link_options(icc_tiffdump_fuzzer PRIVATE -fsanitize=fuzzer,address,undefined)
+ # target_include_directories(icc_tiffdump_fuzzer PRIVATE ${FUZZER_INCLUDES})
+ # target_link_libraries(icc_tiffdump_fuzzer IccProfLib2 ${TIFF_LIBRARIES})
+ endif()
+
+ message(STATUS "Fuzzing harnesses configured:")
+ message(STATUS " Core: profile, calculator, v5dspobs, multitag, roundtrip, dump, io, link, spectral (9)")
+ message(STATUS " Apply: apply, applynamedcmm, applyprofiles (3)")
+ if(TARGET IccXML2)
+ message(STATUS " XML: fromxml, toxml (2)")
+ endif()
+ if(TIFF_FOUND)
+ message(STATUS " TIFF: specsep, tiffdump (disabled - requires TiffImg.cpp)")
+ endif()
+else()
+ message(STATUS "Fuzzing disabled (requires Clang and -DENABLE_FUZZING=ON)")
+endif()
diff --git a/Testing/Fuzzing/build-fuzzers.sh b/Testing/Fuzzing/build-fuzzers.sh
new file mode 100644
index 000000000..1f6b9841e
--- /dev/null
+++ b/Testing/Fuzzing/build-fuzzers.sh
@@ -0,0 +1,122 @@
+#!/bin/bash
+# Last Updated: 08-FEB-2026 at 0700Z by David Hoyt
+#
+# Changes: Add WASM Toolchain, CFL & libFuzzer
+#
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+BUILD_DIR="$REPO_ROOT/Build"
+FUZZER_DIR="$REPO_ROOT/fuzzers"
+OUTPUT_DIR="$FUZZER_DIR/bin"
+
+echo "════════════════════════════════════════════════════════════════"
+echo " Phase 14 Part 3: Building Fuzzing Harnesses"
+echo "════════════════════════════════════════════════════════════════"
+echo ""
+echo "Repository: $REPO_ROOT"
+echo "Build directory: $BUILD_DIR"
+echo "Output directory: $OUTPUT_DIR"
+echo ""
+
+# Create output directory
+mkdir -p "$OUTPUT_DIR"
+
+# Common compiler flags
+COMMON_FLAGS="-fsanitize=fuzzer,address,undefined -g -O1"
+COMMON_FLAGS="$COMMON_FLAGS -I$REPO_ROOT/Tools/CmdLine/IccAnalyzer"
+COMMON_FLAGS="$COMMON_FLAGS -I$REPO_ROOT/IccProfLib"
+
+# Common linker flags
+COMMON_LIBS="-L$BUILD_DIR/IccProfLib -lIccProfLib2 -lssl -lcrypto -lz"
+
+# Object files from iccAnalyzer build (link all to avoid missing dependencies)
+ANALYZER_OBJS="$BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/*.o"
+
+echo "──────────────────────────────────────────────────────────────────"
+echo "[1/4] Building fuzz_binary_db..."
+echo "──────────────────────────────────────────────────────────────────"
+
+clang++ $COMMON_FLAGS \
+ "$FUZZER_DIR/fuzz_binary_db.cpp" \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerBinaryDB.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerBloomFilter.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerFingerprintDB.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerSecurity.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerNinja.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerInspect.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerSignatures.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerValidation.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerLUT.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerComprehensive.cpp.o \
+ $COMMON_LIBS \
+ -o "$OUTPUT_DIR/fuzz_binary_db"
+
+echo "✅ fuzz_binary_db built successfully"
+echo ""
+
+echo "──────────────────────────────────────────────────────────────────"
+echo "[2/4] Building fuzz_bloom_filter..."
+echo "──────────────────────────────────────────────────────────────────"
+
+clang++ $COMMON_FLAGS \
+ "$FUZZER_DIR/fuzz_bloom_filter.cpp" \
+ "$BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerBloomFilter.cpp.o" \
+ -o "$OUTPUT_DIR/fuzz_bloom_filter"
+
+echo "✅ fuzz_bloom_filter built successfully"
+echo ""
+
+echo "──────────────────────────────────────────────────────────────────"
+echo "[3/4] Building fuzz_path_validation..."
+echo "──────────────────────────────────────────────────────────────────"
+
+clang++ $COMMON_FLAGS \
+ "$FUZZER_DIR/fuzz_path_validation.cpp" \
+ "$BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerSecurity.cpp.o" \
+ -o "$OUTPUT_DIR/fuzz_path_validation"
+
+echo "✅ fuzz_path_validation built successfully"
+echo ""
+
+echo "──────────────────────────────────────────────────────────────────"
+echo "[4/4] Building fuzz_fingerprint_match..."
+echo "──────────────────────────────────────────────────────────────────"
+
+clang++ $COMMON_FLAGS \
+ "$FUZZER_DIR/fuzz_fingerprint_match.cpp" \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerBinaryDB.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerBloomFilter.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerFingerprintDB.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerSecurity.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerNinja.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerInspect.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerSignatures.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerValidation.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerLUT.cpp.o \
+ $BUILD_DIR/Tools/IccAnalyzer/CMakeFiles/iccAnalyzer.dir/home/xss/copilot/iccLibFuzzer/Tools/CmdLine/IccAnalyzer/IccAnalyzerComprehensive.cpp.o \
+ $COMMON_LIBS \
+ -o "$OUTPUT_DIR/fuzz_fingerprint_match"
+
+echo "✅ fuzz_fingerprint_match built successfully"
+echo ""
+
+echo "════════════════════════════════════════════════════════════════"
+echo " Build Complete!"
+echo "════════════════════════════════════════════════════════════════"
+echo ""
+echo "Fuzzers built:"
+echo " 1. $OUTPUT_DIR/fuzz_binary_db"
+echo " 2. $OUTPUT_DIR/fuzz_bloom_filter"
+echo " 3. $OUTPUT_DIR/fuzz_path_validation"
+echo " 4. $OUTPUT_DIR/fuzz_fingerprint_match"
+echo ""
+echo "To run a fuzzer:"
+echo " cd $OUTPUT_DIR"
+echo " ./fuzz_binary_db -max_total_time=600 -max_len=1048576"
+echo ""
+echo "To run all fuzzers:"
+echo " $FUZZER_DIR/run-all-fuzzers.sh"
+echo ""
diff --git a/Testing/Fuzzing/icc.dict b/Testing/Fuzzing/icc.dict
new file mode 100644
index 000000000..79663dac1
--- /dev/null
+++ b/Testing/Fuzzing/icc.dict
@@ -0,0 +1,64 @@
+# Recommended dictionary for ICC profile fuzzing (updated)
+"dcam"
+"clro"
+"MCH"
+"sech"
+"tepm"
+"h\x01\x00\x00"
+"\x00\x00\x00\x00\x00\x00\x00\x11"
+"SGNT"
+"aO\x85\xff"
+"13iu"
+"\x10\xff\xff\xff\xff\xff\xff\xff"
+"\xff\x02\x00d"
+"\x06\x00\x00\x00\x00\x00\x00\x00"
+"\x0f\x00\x00\x00\x00\x00\x00\x00"
+"\x3f\xff\xff\xff\xff\xff\xfcg"
+"O%\xb4/"
+"\xff\xff\xff\xff\xff\xff\xff0"
+"LS"
+"B2A0"
+"\xff\xff\xff\xff\xff\xff\xff\x01"
+"\x18\x00\x00\x00\x00\x00\x00\x00"
+"\x01\x00\x00\x00\x00\x00\x00\x00"
+"\x00\x00\x00\x00\x00\x00\x00\x00"
+"CLR"
+"J\x12\xbe\xa3"
+"|\x00\x00\x00\x00\x00\x00\x00"
+"\x08\x00\x00\x00\x00\x00\x00\x00"
+"\xab\xf0\xff\xff\xff\xff\xff\x3f"
+"\xbd\xf2\xc4"
+"\x01\x00\x00\x00\x00\x00\x00\x10"
+"\x01\x00\x00\x00\x00\x00\x01\x81"
+"MC"
+"\x05\x00\x00\x00\x00\x00\x00\x00"
+"srpc"
+"\x01\x00\x00\x00\x00\x00\xc7\x7f"
+"Lab "
+"u\xb4\x9c8"
+"\xff\xff\xff\xff\xff\xff\xff\x0f"
+"mHCM"
+"B2B3"
+"hdrc"
+"mvis"
+"\x01\x00\x00\x00\x00\x00\x00G"
+"\xbc\x17h"
+"\x89\x9fYG"
+"\x01\x00\x00\x00\x00\x00\x00\x14"
+"!YMC"
+"uidm"
+"\x3f\x3f\x3f\x3f"
+"\xbc$3"
+"\x01\x00\x00\x00\x00\x00\x00\x07"
+"\x0e\x00\x00\x00\x00\x00\x00\x00"
+"CL"
+"\x00\x00\x00\x00\x00\x00\x00\xf6"
+"\x00\x00\x00\x00\x00\x00\x00\x10"
+"i\xe5\xb1"
+"\xff\xff\xff\xff\xff\xff\xff\x03"
+"atad"
+"\xa7\xa6\xa6\x00"
+"dLab"
+"\x04\x00\x00\x00\x00\x00\x00\x00"
+"\x01\x00"
+"zxml"
diff --git a/Testing/Fuzzing/icc_apply_fuzzer.cpp b/Testing/Fuzzing/icc_apply_fuzzer.cpp
new file mode 100644
index 000000000..1cd3a9816
--- /dev/null
+++ b/Testing/Fuzzing/icc_apply_fuzzer.cpp
@@ -0,0 +1,155 @@
+/** @file
+File: IccApplyBPC.cpp
+
+Contains: Implementation of Black Point Compensation calculations.
+
+Version: V1
+
+Copyright: (c) see ICC Software License
+*/
+
+/*
+* The ICC Software License, Version 0.2
+*
+*
+* Copyright (c) 2003-2012 The International Color Consortium. All rights
+* reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in
+* the documentation and/or other materials provided with the
+* distribution.
+*
+* 3. In the absence of prior written permission, the names "ICC" and "The
+* International Color Consortium" must not be used to imply that the
+* ICC organization endorses or promotes products derived from this
+* software.
+*
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
+* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*
+* This software consists of voluntary contributions made by many
+* individuals on behalf of the The International Color Consortium.
+*
+*
+* Membership in the ICC is encouraged when this software is used for
+* commercial purposes.
+*
+*
+* For more information on The International Color Consortium, please
+* see .
+*
+*
+*/
+
+
+#include
+#include
+#include
+#include
+#include "IccCmm.h"
+#include "IccUtil.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 130 || size > 1024 * 1024) return 0;
+
+ // Use fixed parameters that match real tool usage
+ // Tools typically use Perceptual intent and Linear interpolation
+ icRenderingIntent intent = icPerceptual;
+ icXformInterp interp = icInterpLinear;
+
+ // Write the COMPLETE profile without modification
+ char tmp_file[] = "/tmp/fuzz_apply_XXXXXX";
+ int fd = mkstemp(tmp_file);
+ if (fd == -1) return 0;
+ write(fd, data, size);
+ close(fd);
+
+ CIccCmm cmm;
+ if (cmm.AddXform(tmp_file, intent, interp) == icCmmStatOk) {
+ icStatusCMM beginStatus = cmm.Begin();
+ if (beginStatus == icCmmStatOk) {
+ // Verify CMM is valid and has apply object before use
+ // GetApply() can return non-null but with invalid internal state
+ // so we need to try a test apply to verify it's actually usable
+ if (!cmm.Valid()) {
+ unlink(tmp_file);
+ return 0;
+ }
+
+ CIccApplyCmm *pApply = cmm.GetApply();
+ if (!pApply) {
+ unlink(tmp_file);
+ return 0;
+ }
+
+ // Get actual channel counts
+ icUInt16Number nSrcChannels = cmm.GetSourceSamples();
+ icUInt16Number nDstChannels = cmm.GetDestSamples();
+
+ // Validate channel counts
+ if (nSrcChannels == 0 || nDstChannels == 0 ||
+ nSrcChannels > 16 || nDstChannels > 16) {
+ unlink(tmp_file);
+ return 0;
+ }
+
+ // Allocate buffers based on actual channel counts
+ icFloatNumber in[128] = {0};
+ icFloatNumber out[128] = {0};
+
+ // Initialize test values - ensure we don't exceed array bounds
+ int maxInit = (8 * nSrcChannels < 128) ? 8 * nSrcChannels : 127;
+ for (int i = 0; i < maxInit; i++) {
+ in[i] = (i % 10) * 0.1f;
+ }
+
+ // Apply transforms with bounds checking
+ for (int i = 0; i < 8 && (i + 1) * nDstChannels <= 128 &&
+ (i + 1) * nSrcChannels <= 128; i++) {
+ if (cmm.Apply(out + i * nDstChannels, in + i * nSrcChannels) != icCmmStatOk) {
+ unlink(tmp_file);
+ return 0;
+ }
+ }
+
+ // Test edge cases
+ icFloatNumber edge_in[64] = {0};
+ icFloatNumber edge_out[64] = {0};
+ for (int i = 0; i < 4 && (i + 1) * nDstChannels <= 64 &&
+ (i + 1) * nSrcChannels <= 64; i++) {
+ if (cmm.Apply(edge_out + i * nDstChannels, edge_in + i * nSrcChannels) != icCmmStatOk) {
+ unlink(tmp_file);
+ return 0;
+ }
+ }
+
+ // Exercise CMM info methods
+ cmm.GetNumXforms();
+ cmm.GetSourceSpace();
+ cmm.GetDestSpace();
+ }
+ }
+
+ unlink(tmp_file);
+ return 0;
+}
diff --git a/Testing/Fuzzing/icc_apply_fuzzer.dict b/Testing/Fuzzing/icc_apply_fuzzer.dict
new file mode 100644
index 000000000..87a95cb54
--- /dev/null
+++ b/Testing/Fuzzing/icc_apply_fuzzer.dict
@@ -0,0 +1,269 @@
+###### Recommended dictionary. ######
+"\x00\x00\x00\x00\x00\x00\x00%"
+"\x01\x00\x00\x00\x00\x00\x01\xff"
+"\xff\xff\x1f\x02"
+"\x01\x02"
+"\x08\x00\x00\x00\x00\x00\x00\x00"
+"abst"
+"\xff\xff\xff\xff"
+"\x01\x00\x00\x00\x00\x00\x00\x00"
+"\xcc\x00\x00\x00"
+"rXYZ"
+"\x99\x02\x00\x00\x00\x00\x00\x00"
+"!baL"
+"\xff\xffU\xe5m\x94\xfb\xf9"
+"wu\x00\x00"
+"\x00\x00\x00\x00\x00\x00\x00\x03"
+"\x01\x00\x00\x00\x00\x00\x0c\xd5"
+"\x01\x00\x00\x00"
+"@v\x09\x00\x00\x00\x00\x00"
+"\x06\x00\x00\x00\x00\x00\x00\x00"
+"nmcl"
+"\x02\x00\x00\x00"
+"@2B3"
+"\xff\xff\xff\xff\xff\xff\xff\x02"
+"\x01\x00"
+"Lba"
+"4\x0920"
+"G\x00\x00\x00\x00\x00\x00\x00"
+"\xf4\xfd\x03\x7f"
+"A2B3"
+"mcl"
+"YYXr"
+"\x00\x00\x00\x00\x00\x00\x000"
+"zxml"
+"\xfe\x01\x00\x00"
+"kcmn"
+"\xff\xff\xff\xff\xff\xff\xffq"
+"3\x00\x00\x00\x00\x00\x00\x00"
+"\x8a~\x94f"
+"\x01\x00\x00\x00\x00\x00\x1c\xf9"
+"\x9b\xa9\xff\xaa"
+"\x01\x00\x00\x00\x00\x00\x02\x06"
+"!ba"
+"\xff\xff\xff\xcb"
+"\xff\xff\xff\xff\xff\xff\x00?"
+"mpet"
+"\xfe\xff\xff\xff"
+"\xd8\xd9\xd9\xd9"
+"spmg"
+"\x7f\x00\x00\x00"
+"2\xf7\xeeO"
+"\xff\x0d\x93H"
+"\x03\x00\x00\x00"
+"6}\x00\x00\x00\x00\x00\x00"
+"cnec"
+"\xff\xff\xff\xff\xff\xff\x03F"
+"\x93i\x9e9\xd5|\x097"
+"\x05\x00\x00\x00"
+"D2B3"
+"\x00\x00\x00\x00\x00\x00\x02x"
+"\x01\x00\x00\x00\x00\x00\x02\x9c"
+"\x00\xf0"
+"\x88T\x00\x00\x00\x00\x00\x00"
+"uf32"
+"\xe9>\x0a\x00\x00\x00\x00\x00"
+"\x04\x00\x00\x00\x00\x00\x00\x00"
+"\xbf\xc3\x02o\xe5V\x00\x00"
+"\x00\x00\x00\x01"
+"\xd3\x04\x00\x00\x00\x00\x00\x00"
+"dahc"
+"\x0f\x00\x00\x00\x00\x00\x00\x00"
+"\x00\x00\x00\x04"
+"2B2A"
+"\x00\x00\x00\x00\x00\x00\x00\x82"
+"\xff\x08"
+"\xff\xff"
+"CH`"
+"W>\x8bn\xe5V\x00\x00"
+"\x01\x00ux"
+"3B2A"
+"denc"
+"cah\x05"
+"-0\x090"
+"ICCp"
+"ZYXr"
+"\x01\x01"
+"\x00\x0016"
+"cahc"
+"\x00\x00\x00\x00\x00\x04\xa8q"
+"\x00\x00\x00\x00\x00\x00\x00\x00"
+"\x00\x00V\xe5n3\xb2h"
+"GG"
+"\x00\x00\xd8\x0a"
+"ab"
+"!\x1f\xcd\xa6"
+"baL"
+"N\x01\x00\x00\x00\x00\x00\x00"
+"[\x01"
+"\xff\xff\xff\xff\xff\xffw\xe6"
+"7tpt"
+"\xb7\x08\x00\x00\x00\x00\x00\x00"
+"\x00\x00\x00\x00\x00\x00ux"
+"\x01\x00\x10\x00\x00\x00\x00\x00"
+"\xff\xff\xff\xff\xff\xff\xff\xff"
+"bsdn"
+"\x06\x00\x00\x00"
+"R\xb1B"
+"[YXr"
+"qXYZ"
+":41\x0a"
+"\x94i\x9e9\xd5|\x097"
+"R\xb1"
+"\x01\x00\x00\x00\x00\x00ux"
+"\xff\x01"
+"\x00\x00V\xe5m\xea\xfb2"
+"\x83\x00\x00\x00\x00\x00\x00\x00"
+"\xff\xff\xff\xff\xff\xff\x00$"
+"\xff\xff\xff\xff\xff\xff\xff\x12"
+"\xff\xff\xff\xff\xff\xff\x02|"
+"CM\xaa"
+"_in"
+"M\x07H"
+"ncvs"
+"\xe6\xb4t\xa4xO_\xa4"
+"`bst"
+"QPA\x84"
+"rvcn"
+"N10__cxxabiv117__class_type_infoE"
+"CRTk"
+"CL"
+"ocvs"
+"\x03\x00\x00\x00\x00\x00\x00\x00"
+"jTRC"
+"\x01KCL"
+"pa\x11\x00"
+"\x01\x00\x00\x00\x00\x00\x00\x10"
+"\x01\x00>\x00\x00\x00\x00\x00"
+"\x00\x00\x00\x00\x00\x00\x03y"
+"GR\xc1"
+"4N\x02\x00"
+"\x10\x00\x00\x00\x00\x00\x00\x00"
+"\xff\x04XY"
+"\xd0\x00\x00\x00\x00\x00\x00\x00"
+"St9type_info"
+"\x01\x00\x00\x00\x00\x00\x02\xdc"
+"\x9c\x96C\x8f"
+"\xff\xff\xff\xff\xff\xff\xff\x01"
+"\x01\x00\x00\x00\x00\x00\x00p"
+"1B2A"
+"p\xd1\xab\xde\xff\x7f\x00\x00"
+"@2B1"
+"HRAY"
+"svcn"
+"\x0100."
+"\xff\xff\xff\x0f"
+"\xf3\x01\x00\x00\x00\x00\x00\x00"
+"0B2A"
+"bACS"
+"\x00\x00pr"
+"CLR"
+"l\x14\x00\x00"
+"\x01\x00\x00\x00\x00\x00\x00 "
+"\x00\x00\x00m"
+"MCH"
+"4B2A"
+"A("
+"BRTk"
+"sdin"
+"\x00\x18KK"
+"\xdeg\x04\x00\x00\x00\x00\x00"
+"a\x01"
+"[c\x00\x00\x00\x00\x00\x00"
+"\x03\x00"
+"\x01\x00\x00\x00\x00\x01|\x00"
+"O\x84PA"
+"\xff\xff`\xac\xea\x1e\x0e\xf8"
+"B2B3"
+"\x00\x00\x00\x00\x00\x00\x00\xd0"
+"\xa8Zx"
+"\x01\x00\x00\x00\x00\x00\x00\x0d"
+"SAYL"
+"gamt"
+"\x89\xa4\x04\x00\x00\x00\x00\x00"
+"\xff\xff\xff\xff\xff\xff\xff\x1d"
+"RMps"
+"1\x0903"
+"XMCr"
+"CR\x02\x00\x00\x00\x00\x00"
+"\x00\x00\x00\x00\x00\x00\x00\x80"
+"\x81\x00\x00\x00\x00\x00\x00\x00"
+"tvcn"
+"\xe0\x8a\x01\x00"
+"\x01\x00\x00\x00\x00\x00\x00\x82"
+"\xcaG]\x81"
+"\x01\x00\x00\x00\x00\x00\x00>"
+".
+*
+*
+*/
+
+
+#include
+#include
+#include
+#include
+#include
+#include "IccCmm.h"
+#include "IccUtil.h"
+#include "IccDefs.h"
+#include "IccApplyBPC.h"
+#include "IccEnvVar.h"
+
+// Fuzzer input structure (packed):
+// [0-3]: Profile data (remaining bytes)
+// Profile header: [0]: flags byte
+// bit 0: use BPC
+// bit 1: use D2Bx/B2Dx tags
+// bit 2: adjust PCS luminance
+// bit 3: use V5 sub-profile
+// bit 4-5: interpolation (0=linear, 1=tetrahedral)
+// bit 6-7: reserved
+// [1]: rendering intent (0-3 base, +modifiers)
+// [2-3]: source color space signature (16-bit index)
+// [4-5]: dest color space signature (16-bit index)
+// [6]: interface type hint (0=pixel2pixel, 1=named2pixel, 2=pixel2named, 3=named2named)
+// [7-9]: reserved for future use
+// [10+]: ICC profile data
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ // Minimum: 10 byte header + 128 byte minimal ICC profile
+ if (size < 138 || size > 2 * 1024 * 1024) return 0;
+
+ // Parse fuzzing configuration from header
+ uint8_t flags = data[0];
+ bool useBPC = (flags & 0x01) != 0;
+ bool useD2BxB2Dx = (flags & 0x02) != 0;
+ bool adjustPcsLuminance = (flags & 0x04) != 0;
+ bool useV5SubProfile = (flags & 0x08) != 0;
+ icXformInterp interp = ((flags >> 4) & 0x01) ? icInterpTetrahedral : icInterpLinear;
+
+ // Rendering intent with modifiers
+ uint8_t intent_byte = data[1];
+ icRenderingIntent intent = (icRenderingIntent)(intent_byte & 0x03);
+
+ // Color space signatures - map to common spaces
+ icColorSpaceSignature colorSpaces[] = {
+ icSigXYZData, // 0
+ icSigLabData, // 1
+ icSigRgbData, // 2
+ icSigCmykData, // 3
+ icSigGrayData, // 4
+ icSigNamedData, // 5
+ icSig2colorData, // 6
+ icSig3colorData, // 7
+ icSig4colorData, // 8
+ icSig5colorData, // 9
+ icSig6colorData, // 10
+ icSigUnknownData // 11 (fallback)
+ };
+
+ uint16_t srcSpaceIdx = ((uint16_t)data[2] << 8) | data[3];
+ uint16_t dstSpaceIdx = ((uint16_t)data[4] << 8) | data[5];
+
+ icColorSpaceSignature srcSpace = colorSpaces[srcSpaceIdx % 12];
+ icColorSpaceSignature dstSpace = colorSpaces[dstSpaceIdx % 12];
+
+ // Interface type hint (not enforced, determined by profile)
+ uint8_t interfaceHint = data[6] & 0x03;
+
+ // Write profile data to temporary file
+ const uint8_t *profile_data = data + 10;
+ size_t profile_size = size - 10;
+
+ char tmp_profile[] = "/tmp/fuzz_namedcmm_XXXXXX.icc";
+ int fd = mkstemp(tmp_profile);
+ if (fd == -1) return 0;
+
+ ssize_t written = write(fd, profile_data, profile_size);
+ close(fd);
+
+ if (written != (ssize_t)profile_size) {
+ unlink(tmp_profile);
+ return 0;
+ }
+
+ // Determine if first profile should be treated as input profile
+ // (mirrors iccApplyNamedCmm.cpp logic at lines 329-337)
+ bool bFirstInput = true;
+ if (srcSpace == icSigXYZData || srcSpace == icSigLabData) {
+ // Source is PCS - check if profile is abstract or PCS-based
+ CIccProfile *pProf = OpenIccProfile(tmp_profile);
+ if (pProf) {
+ if (pProf->m_Header.deviceClass != icSigAbstractClass &&
+ (pProf->m_Header.colorSpace == icSigXYZData ||
+ pProf->m_Header.colorSpace == icSigLabData)) {
+ bFirstInput = true;
+ }
+ delete pProf;
+ }
+ } else {
+ bFirstInput = true;
+ }
+
+ // Create CIccNamedColorCmm (mirrors line 340)
+ CIccNamedColorCmm namedCmm(srcSpace, dstSpace, bFirstInput);
+
+ // Build hint manager for profile attachment (mirrors lines 354-389)
+ CIccCreateXformHintManager Hint;
+
+ if (useBPC) {
+ Hint.AddHint(new CIccApplyBPCHint());
+ }
+
+ if (adjustPcsLuminance) {
+ Hint.AddHint(new CIccLuminanceMatchingHint());
+ }
+
+ // Add environment variable hints (exercise IccEnvVar.h)
+ // icCmmEnvSigMap maps icSignature -> icFloatNumber
+ if ((flags & 0x80) != 0) {
+ icCmmEnvSigMap envVars;
+ envVars[0x656E7631] = 1.0; // 'env1' -> 1.0
+ Hint.AddHint(new CIccCmmEnvVarHint(envVars));
+ }
+
+ // Add profile to CMM (mirrors lines 382-392)
+ icStatusCMM stat = namedCmm.AddXform(
+ tmp_profile,
+ intent,
+ interp,
+ nullptr, // No PCC profile for fuzzing simplicity
+ icXformLutColor,
+ useD2BxB2Dx,
+ &Hint,
+ useV5SubProfile
+ );
+
+ if (stat != icCmmStatOk) {
+ unlink(tmp_profile);
+ return 0;
+ }
+
+ // Initialize CMM (mirrors line 398)
+ stat = namedCmm.Begin();
+
+ if (stat != icCmmStatOk) {
+ unlink(tmp_profile);
+ return 0;
+ }
+
+ // Get actual CMM interface type (determined by profiles)
+ icApplyInterface interface = namedCmm.GetInterface();
+
+ // Get source and destination color spaces
+ icColorSpaceSignature actualSrcSpace = namedCmm.GetSourceSpace();
+ icColorSpaceSignature actualDstSpace = namedCmm.GetDestSpace();
+
+ int nSrcSamples = icGetSpaceSamples(actualSrcSpace);
+ int nDstSamples = icGetSpaceSamples(actualDstSpace);
+
+ // Validate sample counts
+ if (nSrcSamples <= 0 || nSrcSamples > 16 ||
+ nDstSamples <= 0 || nDstSamples > 16) {
+ unlink(tmp_profile);
+ return 0;
+ }
+
+ // Apply transformations based on interface type
+ // (mirrors iccApplyNamedCmm.cpp lines 470-560)
+
+ switch (interface) {
+ case icApplyNamed2Pixel: {
+ // Named color to pixel transformation
+ icFloatNumber dstPixel[16];
+ const char *testNames[] = {
+ "White", "Black", "Red", "Green", "Blue",
+ "Cyan", "Magenta", "Yellow", "Gray"
+ };
+
+ for (size_t i = 0; i < sizeof(testNames) / sizeof(testNames[0]); i++) {
+ icFloatNumber tint = 1.0;
+ namedCmm.Apply(dstPixel, testNames[i], tint);
+
+ // Test tint variations
+ tint = 0.5;
+ namedCmm.Apply(dstPixel, testNames[i], tint);
+
+ tint = 0.0;
+ namedCmm.Apply(dstPixel, testNames[i], tint);
+ }
+ break;
+ }
+
+ case icApplyPixel2Pixel: {
+ // Pixel to pixel transformation (most common case)
+ icFloatNumber srcPixel[16];
+ icFloatNumber dstPixel[16];
+
+ // Test 1: Black (all zeros)
+ memset(srcPixel, 0, sizeof(icFloatNumber) * nSrcSamples);
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 2: White (all ones)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.0;
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 3: Gray (all 0.5)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 0.5;
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 4: Primary colors
+ for (int j = 0; j < nSrcSamples && j < 8; j++) {
+ memset(srcPixel, 0, sizeof(icFloatNumber) * nSrcSamples);
+ srcPixel[j] = 1.0;
+ namedCmm.Apply(dstPixel, srcPixel);
+ }
+
+ // Test 5: Edge cases - negative values
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = -0.1;
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 6: Edge cases - values > 1.0
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.5;
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 7: NaN/Inf handling (critical for fuzzing)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 0.0 / 0.0; // NaN
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.0 / 0.0; // +Inf
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+
+ // Test 8: Random values from remaining fuzz data
+ if (size > 138) {
+ size_t remaining = size - 138;
+ for (int i = 0; i < nSrcSamples && i < (int)remaining; i++) {
+ // Normalize to 0.0-2.0 range to test clipping
+ srcPixel[i] = ((icFloatNumber)data[138 + i] / 127.5) - 1.0;
+ }
+ namedCmm.Apply(dstPixel, srcPixel);
+ }
+
+ // Test 9: Batch apply (tests multi-pixel path)
+ icFloatNumber batchSrc[48]; // 3 pixels * 16 channels max
+ icFloatNumber batchDst[48];
+
+ for (int i = 0; i < nSrcSamples * 3; i++) {
+ batchSrc[i] = ((icFloatNumber)(i % 256)) / 255.0;
+ }
+ namedCmm.Apply(batchDst, batchSrc, 3);
+
+ break;
+ }
+
+ case icApplyNamed2Named: {
+ // Named color to named color transformation
+ icChar srcName[256];
+ icChar dstName[256];
+ const char *testNames[] = {"White", "Black", "Red"};
+
+ for (size_t i = 0; i < sizeof(testNames) / sizeof(testNames[0]); i++) {
+ strncpy(srcName, testNames[i], sizeof(srcName) - 1);
+ srcName[sizeof(srcName) - 1] = '\0';
+
+ icFloatNumber tint = 1.0;
+ namedCmm.Apply(dstName, srcName, tint);
+ }
+ break;
+ }
+
+ case icApplyPixel2Named: {
+ // Pixel to named color transformation
+ icFloatNumber srcPixel[16];
+ icChar dstName[256];
+
+ // Test white point
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.0;
+ }
+ namedCmm.Apply(dstName, srcPixel);
+
+ // Test black point
+ memset(srcPixel, 0, sizeof(icFloatNumber) * nSrcSamples);
+ namedCmm.Apply(dstName, srcPixel);
+
+ break;
+ }
+
+ default:
+ // Unknown interface - should not occur if CMM is valid
+ break;
+ }
+
+ // Exercise CMM query methods (mirrors IccApplyNamedCmm usage)
+ namedCmm.GetNumXforms();
+ namedCmm.Valid();
+ namedCmm.GetSourceSpace();
+ namedCmm.GetDestSpace();
+ namedCmm.GetLastSpace();
+ namedCmm.GetLastParentSpace();
+
+ // Test encoding conversion functions (mirrors lines 489, 523, 541)
+ // These are critical paths that handle different encoding formats
+ icFloatNumber testPixel[16];
+ icFloatNumber convertedPixel[16];
+
+ for (int i = 0; i < nDstSamples; i++) {
+ testPixel[i] = 0.5;
+ }
+
+ // Test various encoding conversions
+ icFloatColorEncoding encodings[] = {
+ icEncodeValue,
+ icEncodePercent,
+ icEncodeUnitFloat,
+ icEncodeFloat,
+ icEncode16Bit,
+ icEncode16BitV2
+ };
+
+ for (size_t i = 0; i < sizeof(encodings) / sizeof(encodings[0]); i++) {
+ // ToInternalEncoding test (source encoding)
+ CIccCmm::ToInternalEncoding(actualSrcSpace, encodings[i],
+ convertedPixel, testPixel, true);
+
+ // FromInternalEncoding test (destination encoding)
+ CIccCmm::FromInternalEncoding(actualDstSpace, encodings[i],
+ convertedPixel, testPixel, false);
+ }
+
+ unlink(tmp_profile);
+ return 0;
+}
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer.options b/Testing/Fuzzing/icc_applynamedcmm_fuzzer.options
new file mode 100644
index 000000000..28123f07a
--- /dev/null
+++ b/Testing/Fuzzing/icc_applynamedcmm_fuzzer.options
@@ -0,0 +1,3 @@
+max_len = 2097152
+timeout = 120
+rss_limit_mb = 6144
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/3f4bae629098a8b8b155af6fe7de5c1f.icc b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/3f4bae629098a8b8b155af6fe7de5c1f.icc
new file mode 100644
index 000000000..495b894d1
Binary files /dev/null and b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/3f4bae629098a8b8b155af6fe7de5c1f.icc differ
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/49127c6269d09bd5c2e3c7cc62a9e369.icc b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/49127c6269d09bd5c2e3c7cc62a9e369.icc
new file mode 100644
index 000000000..cddf69a39
Binary files /dev/null and b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/49127c6269d09bd5c2e3c7cc62a9e369.icc differ
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/491cfbace7410fd4c6aa25e46d1071ac.icc b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/491cfbace7410fd4c6aa25e46d1071ac.icc
new file mode 100644
index 000000000..48c26323d
Binary files /dev/null and b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/491cfbace7410fd4c6aa25e46d1071ac.icc differ
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/4d4e791b507283d31308c761c515c648.icc b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/4d4e791b507283d31308c761c515c648.icc
new file mode 100644
index 000000000..186b548e6
Binary files /dev/null and b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/4d4e791b507283d31308c761c515c648.icc differ
diff --git a/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/ff9ac22d18ff50970f35d38449a6638e.icc b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/ff9ac22d18ff50970f35d38449a6638e.icc
new file mode 100644
index 000000000..eba9d3f6b
Binary files /dev/null and b/Testing/Fuzzing/icc_applynamedcmm_fuzzer_seed_corpus/ff9ac22d18ff50970f35d38449a6638e.icc differ
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer.cpp b/Testing/Fuzzing/icc_applyprofiles_fuzzer.cpp
new file mode 100644
index 000000000..23019d825
--- /dev/null
+++ b/Testing/Fuzzing/icc_applyprofiles_fuzzer.cpp
@@ -0,0 +1,190 @@
+/** @file
+File: IccApplyBPC.cpp
+
+Contains: Implementation of Black Point Compensation calculations.
+
+Version: V1
+
+Copyright: (c) see ICC Software License
+*/
+
+/*
+* The ICC Software License, Version 0.2
+*
+*
+* Copyright (c) 2003-2012 The International Color Consortium. All rights
+* reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in
+* the documentation and/or other materials provided with the
+* distribution.
+*
+* 3. In the absence of prior written permission, the names "ICC" and "The
+* International Color Consortium" must not be used to imply that the
+* ICC organization endorses or promotes products derived from this
+* software.
+*
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
+* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*
+* This software consists of voluntary contributions made by many
+* individuals on behalf of the The International Color Consortium.
+*
+*
+* Membership in the ICC is encouraged when this software is used for
+* commercial purposes.
+*
+*
+* For more information on The International Color Consortium, please
+* see .
+*
+*
+*/
+
+
+#include
+#include
+#include
+#include
+#include
+#include "IccCmm.h"
+#include "IccUtil.h"
+#include "IccDefs.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 200 || size > 5 * 1024 * 1024) return 0;
+
+ // Split input: first part is profile data, rest is control data
+ size_t profile_size = (size * 3) / 4;
+ if (profile_size < 130) return 0;
+
+ const uint8_t *profile_data = data;
+ const uint8_t *control_data = data + profile_size;
+ size_t control_size = size - profile_size;
+
+ if (control_size < 4) return 0;
+
+ // Extract fuzzing parameters from control data
+ icRenderingIntent intent = (icRenderingIntent)(control_data[0] % 4);
+ icXformInterp interp = (control_data[1] & 1) ? icInterpLinear : icInterpTetrahedral;
+ bool use_bpc = (control_data[2] & 1) != 0;
+ bool use_d2bx = (control_data[3] & 1) != 0;
+
+ // Write profile to temporary file
+ char tmp_profile[] = "/tmp/fuzz_applyprofiles_XXXXXX.icc";
+ int fd = mkstemp(tmp_profile);
+ if (fd == -1) return 0;
+ write(fd, profile_data, profile_size);
+ close(fd);
+
+ // Create CMM and add profile
+ CIccCmm cmm(icSigUnknownData, icSigUnknownData, true);
+
+ CIccCreateXformHintManager hint;
+ if (use_bpc) {
+ // BPC hint would be added here if available
+ }
+
+ icStatusCMM stat = cmm.AddXform(tmp_profile, intent, interp, nullptr,
+ icXformLutColor, use_d2bx, &hint);
+
+ if (stat == icCmmStatOk) {
+ stat = cmm.Begin();
+
+ if (stat == icCmmStatOk) {
+ // Get color space information
+ icColorSpaceSignature srcSpace = cmm.GetSourceSpace();
+ icColorSpaceSignature dstSpace = cmm.GetDestSpace();
+
+ int nSrcSamples = icGetSpaceSamples(srcSpace);
+ int nDstSamples = icGetSpaceSamples(dstSpace);
+
+ // Validate sample counts
+ if (nSrcSamples > 0 && nSrcSamples <= 16 &&
+ nDstSamples > 0 && nDstSamples <= 16) {
+
+ // Test various pixel values
+ icFloatNumber srcPixel[16];
+ icFloatNumber dstPixel[16];
+
+ // Test 1: Black (all zeros)
+ memset(srcPixel, 0, sizeof(icFloatNumber) * nSrcSamples);
+ cmm.Apply(dstPixel, srcPixel);
+
+ // Test 2: White (all ones)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.0f;
+ }
+ cmm.Apply(dstPixel, srcPixel);
+
+ // Test 3: Gray (all 0.5)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 0.5f;
+ }
+ cmm.Apply(dstPixel, srcPixel);
+
+ // Test 4: Primary colors (varied)
+ for (int j = 0; j < nSrcSamples && j < 8; j++) {
+ memset(srcPixel, 0, sizeof(icFloatNumber) * nSrcSamples);
+ srcPixel[j] = 1.0f;
+ cmm.Apply(dstPixel, srcPixel);
+ }
+
+ // Test 5: Edge cases from control data
+ if (control_size >= 4 + nSrcSamples) {
+ for (int i = 0; i < nSrcSamples; i++) {
+ // Normalize byte values to 0.0-1.0 range
+ srcPixel[i] = (icFloatNumber)control_data[4 + i] / 255.0f;
+ }
+ cmm.Apply(dstPixel, srcPixel);
+ }
+
+ // Test 6: Out of range values (negative and >1.0)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = -0.1f;
+ }
+ cmm.Apply(dstPixel, srcPixel);
+
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 1.1f;
+ }
+ cmm.Apply(dstPixel, srcPixel);
+
+ // Test 7: NaN and infinity (if supported)
+ for (int i = 0; i < nSrcSamples; i++) {
+ srcPixel[i] = 0.0f / 0.0f; // NaN
+ }
+ cmm.Apply(dstPixel, srcPixel);
+
+ // Exercise CMM query methods
+ cmm.GetNumXforms();
+ cmm.Valid();
+ cmm.GetLastParentSpace();
+ cmm.GetLastSpace();
+ }
+ }
+ }
+
+ unlink(tmp_profile);
+ return 0;
+}
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer.options b/Testing/Fuzzing/icc_applyprofiles_fuzzer.options
new file mode 100644
index 000000000..cc80b1fe1
--- /dev/null
+++ b/Testing/Fuzzing/icc_applyprofiles_fuzzer.options
@@ -0,0 +1,6 @@
+[libfuzzer]
+max_len = 5242880
+timeout = 45
+rss_limit_mb = 8192
+detect_leaks = 0
+use_value_profile = 1
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/38615eb718d4cc8ea2989136bbfe4451.icc b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/38615eb718d4cc8ea2989136bbfe4451.icc
new file mode 100644
index 000000000..cb6a9ebb2
Binary files /dev/null and b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/38615eb718d4cc8ea2989136bbfe4451.icc differ
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/555da72fcc6dc2c8e5d4ee0145314c23.icc b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/555da72fcc6dc2c8e5d4ee0145314c23.icc
new file mode 100644
index 000000000..0f7fdb342
Binary files /dev/null and b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/555da72fcc6dc2c8e5d4ee0145314c23.icc differ
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/6fe651cddfd8f1d12e317ee236145ddc.icc b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/6fe651cddfd8f1d12e317ee236145ddc.icc
new file mode 100644
index 000000000..14a6ad34f
Binary files /dev/null and b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/6fe651cddfd8f1d12e317ee236145ddc.icc differ
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/8289c909bf9abd5aba5bc1d9bee826a3.icc b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/8289c909bf9abd5aba5bc1d9bee826a3.icc
new file mode 100644
index 000000000..29458fccc
Binary files /dev/null and b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/8289c909bf9abd5aba5bc1d9bee826a3.icc differ
diff --git a/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/be974a7451bb9197e5d51a4a56b9d125.icc b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/be974a7451bb9197e5d51a4a56b9d125.icc
new file mode 100644
index 000000000..aad9be5db
Binary files /dev/null and b/Testing/Fuzzing/icc_applyprofiles_fuzzer_seed_corpus/be974a7451bb9197e5d51a4a56b9d125.icc differ
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer.cpp b/Testing/Fuzzing/icc_calculator_fuzzer.cpp
new file mode 100644
index 000000000..61d9b1066
--- /dev/null
+++ b/Testing/Fuzzing/icc_calculator_fuzzer.cpp
@@ -0,0 +1,157 @@
+/** @file
+File: IccApplyBPC.cpp
+
+Contains: Implementation of Black Point Compensation calculations.
+
+Version: V1
+
+Copyright: (c) see ICC Software License
+*/
+
+/*
+* The ICC Software License, Version 0.2
+*
+*
+* Copyright (c) 2003-2012 The International Color Consortium. All rights
+* reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in
+* the documentation and/or other materials provided with the
+* distribution.
+*
+* 3. In the absence of prior written permission, the names "ICC" and "The
+* International Color Consortium" must not be used to imply that the
+* ICC organization endorses or promotes products derived from this
+* software.
+*
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
+* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*
+* This software consists of voluntary contributions made by many
+* individuals on behalf of the The International Color Consortium.
+*
+*
+* Membership in the ICC is encouraged when this software is used for
+* commercial purposes.
+*
+*
+* For more information on The International Color Consortium, please
+* see .
+*
+*
+*/
+
+
+#include "IccProfile.h"
+#include "IccTag.h"
+#include "IccTagLut.h"
+#include "IccUtil.h"
+#include "IccMpeFactory.h"
+#include
+#include
+#include
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 128) return 0;
+ if (size > 5 * 1024 * 1024) return 0; // Max 5MB
+
+ CIccProfile *pProfile = nullptr;
+ CIccMemIO *pIO = nullptr;
+
+ try {
+ pIO = new CIccMemIO;
+ if (!pIO) return 0;
+
+ if (!pIO->Attach((icUInt8Number*)data, size)) {
+ delete pIO;
+ return 0;
+ }
+
+ pProfile = new CIccProfile;
+ if (!pProfile) {
+ delete pIO;
+ return 0;
+ }
+
+ if (!pProfile->Attach(pIO)) {
+ delete pProfile;
+ delete pIO;
+ return 0;
+ }
+
+ // Find and exercise calculator-containing tags
+ // Process only first matching tag to avoid CMM lifecycle issues
+ for (icSignature sig : {
+ icSigAToB0Tag, icSigAToB1Tag, icSigAToB2Tag, icSigAToB3Tag,
+ icSigBToA0Tag, icSigBToA1Tag, icSigBToA2Tag, icSigBToA3Tag,
+ icSigDToB0Tag, icSigDToB1Tag, icSigDToB2Tag, icSigDToB3Tag,
+ icSigGamutTag, icSigPreview0Tag, icSigPreview1Tag, icSigPreview2Tag
+ }) {
+ CIccTag *pTag = pProfile->FindTag(sig);
+ if (!pTag) continue;
+
+ // Validate tag (triggers calculator validation)
+ std::string sigPath = "";
+ std::string report;
+ pTag->Validate(sigPath, report, pProfile);
+
+ // Attempt to write (exercises serialization) - BEFORE CMM ops
+ CIccMemIO *pOutIO = new CIccMemIO;
+ if (pOutIO) {
+ pOutIO->Alloc(size + 4096);
+ pTag->Write(pOutIO);
+ delete pOutIO;
+ }
+
+ // Exercise LUT/MPE type-specific paths
+ icTagTypeSignature tagType = pTag->GetType();
+ if (tagType == icSigLutAtoBType || tagType == icSigLutBtoAType) {
+ // Exercise MPE chain traversal and calculator elements
+ CIccTagLutAtoB *pLut = (CIccTagLutAtoB*)pTag;
+ if (pLut) {
+ // Trigger MPE chain validation and channel info
+ icUInt16Number nInputChannels = pLut->InputChannels();
+ icUInt16Number nOutputChannels = pLut->OutputChannels();
+
+ // Exercise tag description
+ std::string desc;
+ pTag->Describe(desc, 100);
+ }
+ }
+
+ // Only process first matching tag
+ break;
+ }
+
+ // Overall profile validation
+ std::string validationReport;
+ pProfile->Validate(validationReport);
+
+ delete pProfile;
+
+ } catch (...) {
+ if (pProfile) delete pProfile;
+ }
+
+ return 0;
+}
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer.options b/Testing/Fuzzing/icc_calculator_fuzzer.options
new file mode 100644
index 000000000..13c1dd99e
--- /dev/null
+++ b/Testing/Fuzzing/icc_calculator_fuzzer.options
@@ -0,0 +1,6 @@
+[libfuzzer]
+max_len = 15728640
+timeout = 45
+rss_limit_mb = 8192
+detect_leaks = 0
+use_value_profile = 1
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/297572192966da924d8091586b8e76ca.icc b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/297572192966da924d8091586b8e76ca.icc
new file mode 100644
index 000000000..964ca4aad
Binary files /dev/null and b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/297572192966da924d8091586b8e76ca.icc differ
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/46aa625bfd6b8fb4ad75bf6c2254dc1b.icc b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/46aa625bfd6b8fb4ad75bf6c2254dc1b.icc
new file mode 100644
index 000000000..0dc8f6971
Binary files /dev/null and b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/46aa625bfd6b8fb4ad75bf6c2254dc1b.icc differ
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/51965e441f99330326b18c101c5a99c3.icc b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/51965e441f99330326b18c101c5a99c3.icc
new file mode 100644
index 000000000..0eac155c8
Binary files /dev/null and b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/51965e441f99330326b18c101c5a99c3.icc differ
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/a3fe13b83e7cb992f9ecc60c21ed9722.icc b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/a3fe13b83e7cb992f9ecc60c21ed9722.icc
new file mode 100644
index 000000000..cfbd03e1f
Binary files /dev/null and b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/a3fe13b83e7cb992f9ecc60c21ed9722.icc differ
diff --git a/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/daa1dd2131b1b2152829372daa5d7f65.icc b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/daa1dd2131b1b2152829372daa5d7f65.icc
new file mode 100644
index 000000000..918059598
Binary files /dev/null and b/Testing/Fuzzing/icc_calculator_fuzzer_seed_corpus/daa1dd2131b1b2152829372daa5d7f65.icc differ
diff --git a/Testing/Fuzzing/icc_core.dict b/Testing/Fuzzing/icc_core.dict
new file mode 100644
index 000000000..e86952c78
--- /dev/null
+++ b/Testing/Fuzzing/icc_core.dict
@@ -0,0 +1,327 @@
+# ICC Core Fuzzer Dictionary
+# Consolidated from 7 specialized dictionaries - 722 unique entries
+# Created: 2026-02-03
+# Format: LibFuzzer dictionary (hex escapes for binary data)
+# Governance: FUZZER_DICTIONARY_GOVERNANCE.md compliant
+# Backup: dictionary-backup-20260203-194505/
+
+# =============================================================================
+# SECTION 1: ICC Profile Header Signatures (4-byte tags)
+# =============================================================================
+
+"acsp"
+"mntr"
+"scnr"
+"prtr"
+"link"
+"spac"
+"abst"
+"nmcl"
+
+# =============================================================================
+# SECTION 2: Color Space Signatures
+# =============================================================================
+
+"RGB "
+"CMYK"
+"Lab "
+"XYZ "
+"Luv "
+"YCbr"
+"Yxy "
+"HSV "
+"HLS "
+"CMY "
+"Gray"
+"2CLR"
+"3CLR"
+"4CLR"
+"5CLR"
+"6CLR"
+"7CLR"
+"8CLR"
+"9CLR"
+"ACLR"
+"BCLR"
+"CCLR"
+"DCLR"
+"ECLR"
+"FCLR"
+"CLR "
+"CLR"
+"MC"
+"MCH"
+
+# =============================================================================
+# SECTION 3: ICC Tag Signatures
+# =============================================================================
+
+"A2B0"
+"A2B1"
+"A2B2"
+"A2B3"
+"B2A0"
+"B2A1"
+"B2A2"
+"B2B1"
+"B2B3"
+"B2D0"
+"B2D1"
+"B2D2"
+"B2D3"
+"bTRC"
+"bXYZ"
+"chad"
+"chrm"
+"clro"
+"clrt"
+"clot"
+"clut"
+"cprt"
+"crdi"
+"desc"
+"dmdd"
+"dmnd"
+"dtim"
+"gamt"
+"gmrp"
+"gTRC"
+"gXYZ"
+"kTRC"
+"lumi"
+"meas"
+"meta"
+"mpet"
+"ncl2"
+"ncol"
+"para"
+"pre0"
+"pre1"
+"pre2"
+"psd0"
+"psd1"
+"psd2"
+"psd3"
+"ps2s"
+"ps2i"
+"rTRC"
+"rXYZ"
+"targ"
+"tech"
+"vcgt"
+"view"
+"vued"
+"wtpt"
+"zxml"
+
+# =============================================================================
+# SECTION 4: Platform/Device Signatures
+# =============================================================================
+
+"ADBE"
+"APPL"
+"MSFT"
+"SGI "
+"SUNW"
+"TGNT"
+
+# =============================================================================
+# SECTION 5: Rendering Intent Signatures
+# =============================================================================
+
+"srpc"
+"hdrc"
+"mvis"
+"bacs"
+"bACS"
+"baL"
+"!baL"
+"!ba"
+"dLab"
+"Lab"
+"Lba"
+"baL"
+
+# =============================================================================
+# SECTION 6: TIFF Byte Order Markers (for specsep fuzzer)
+# =============================================================================
+
+"II"
+"MM"
+"II\x2a\x00"
+"MM\x00\x2a"
+"II+"
+"MM\x00+"
+
+# =============================================================================
+# SECTION 7: V5 DSP Observer Tags
+# =============================================================================
+
+"osca"
+"psca"
+"qsca"
+"qtnm"
+"lkni"
+"MC7k"
+
+# =============================================================================
+# SECTION 8: Binary Patterns - 8-byte sequences
+# =============================================================================
+
+"\x00\x00\x00\x00\x00\x00\x00\x00"
+"\x01\x00\x00\x00\x00\x00\x00\x00"
+"\x02\x00\x00\x00\x00\x00\x00\x00"
+"\x03\x00\x00\x00\x00\x00\x00\x00"
+"\x04\x00\x00\x00\x00\x00\x00\x00"
+"\x05\x00\x00\x00\x00\x00\x00\x00"
+"\x06\x00\x00\x00\x00\x00\x00\x00"
+"\x07\x00\x00\x00\x00\x00\x00\x00"
+"\x08\x00\x00\x00\x00\x00\x00\x00"
+"\x0e\x00\x00\x00\x00\x00\x00\x00"
+"\x0f\x00\x00\x00\x00\x00\x00\x00"
+"\x10\x00\x00\x00\x00\x00\x00\x00"
+"\x12\x00\x00\x00\x00\x00\x00\x00"
+"\x18\x00\x00\x00\x00\x00\x00\x00"
+"\xff\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\x01"
+"\xff\xff\xff\xff\xff\xff\xff\x02"
+"\xff\xff\xff\xff\xff\xff\xff\x03"
+"\xff\xff\xff\xff\xff\xff\xff\x0f"
+"\xff\xff\xff\xff\xff\xff\xff0"
+
+# =============================================================================
+# SECTION 9: Binary Patterns - 4-byte sequences
+# =============================================================================
+
+"\x00\x00\x00\x00"
+"\x01\x00\x00\x00"
+"\x02\x00\x00\x00"
+"\x03\x00\x00\x00"
+"\x04\x00\x00\x00"
+"\x05\x00\x00\x00"
+"\x06\x00\x00\x00"
+"\x08\x00\x00\x00"
+"\x10\x00\x00\x00"
+"\xff\xff\xff\xff"
+"\x00\x00\x00\x01"
+"\x00\x00\x00\x02"
+"\x00\x00\x00\x03"
+"\x00\x00\x00\x04"
+
+# =============================================================================
+# SECTION 10: Binary Patterns - 2-byte sequences
+# =============================================================================
+
+"\x00\x00"
+"\x01\x00"
+"\x02\x00"
+"\x08\x00"
+"\x10\x00"
+"\x2a\x00"
+"\x00\x2a"
+"\x2b\x00"
+"\x00\x2b"
+"\xff\xff"
+
+# =============================================================================
+# SECTION 11: TIFF Tags (from specsep dictionary)
+# =============================================================================
+
+"\xfe\x00"
+"\xff\x00"
+"\x00\x01"
+"\x01\x01"
+"\x02\x01"
+"\x03\x01"
+"\x06\x01"
+"\x11\x01"
+"\x12\x01"
+"\x15\x01"
+"\x16\x01"
+"\x17\x01"
+"\x1a\x01"
+"\x1b\x01"
+"\x1c\x01"
+"\x28\x01"
+"\x52\x01"
+"\x53\x01"
+
+# =============================================================================
+# SECTION 12: Mixed ASCII/Binary Patterns (high frequency)
+# =============================================================================
+
+"h\x01\x00\x00"
+"LS"
+"CL"
+"tr"
+"ab"
+" ab"
+"ra"
+"ch"
+"lf"
+"ui32"
+"sf32"
+"fl64"
+"uidm"
+"svcn"
+"gbd "
+"dLuv"
+"!YMC"
+"ecpf"
+
+# =============================================================================
+# SECTION 13: Size/Length Values (common in ICC structures)
+# =============================================================================
+
+"\x0c\x0c\x0c\x0c"
+"\x14\x00\x00\x00"
+"\x18\x00\x00\x00"
+"\x1c\x00\x00\x00"
+"\x20\x00\x00\x00"
+"\x24\x00\x00\x00"
+"\x28\x00\x00\x00"
+"\x2c\x00\x00\x00"
+"\x30\x00\x00\x00"
+"\x3c\x00\x00\x00"
+"\x40\x00\x00\x00"
+"\x44\x00\x00\x00"
+"\x48\x00\x00\x00"
+"\x4c\x00\x00\x00"
+"\x50\x00\x00\x00"
+
+# =============================================================================
+# SECTION 14: High-Frequency Fuzzer-Discovered Patterns
+# =============================================================================
+
+"\x3f\x3f\x3f\x3f"
+"\xc6\xc5\xc5\xc5"
+";9\xc5\xc5"
+"\xa7\xa6\xa6\x00"
+"\xab\xf0\xff\xff\xff\xff\xff\x3f"
+"\x3f\xff\xff\xff\xff\xff\xfcg"
+"O%\xb4/"
+"\xbd\xf2\xc4"
+"\xbc\x17h"
+"\xbc$3"
+"i\xe5\xb1"
+"u\xb4\x9c8"
+"\x89\x9fYG"
+"J\x12\xbe\xa3"
+"aO\x85\xff"
+"\xff\x02\x00d"
+"|\x00\x00\x00\x00\x00\x00\x00"
+"\x01\x00\x00\x00\x00\x00\x01\x81"
+"\x01\x00\x00\x00\x00\x00\xc7\x7f"
+"\x01\x00\x00\x00\x00\x00\x00G"
+"\x01\x00\x00\x00\x00\x00\x00\x14"
+"\x01\x00\x00\x00\x00\x00\x00\x07"
+"\x01\x00\x00\x00\x00\x00\x00\x10"
+"\x00\x00\x00\x00\x00\x00\x00\x11"
+"\x00\x00\x00\x00\x00\x00\x00\xf6"
+"\x00\x00\x00\x00\x00\x00\x00\x10"
+"\x00\x00\x00\x00\x00\x00\x04\x04"
+"\x10\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\x0f\x00\x00\x00\x00\x00"
+
+# NOTE: This is a curated subset. Full 722 entries available in source dictionaries.
+# To regenerate complete list: cat fuzzers/*.dict | grep '^"' | sort -u
diff --git a/Testing/Fuzzing/icc_dump_fuzzer.cpp b/Testing/Fuzzing/icc_dump_fuzzer.cpp
new file mode 100644
index 000000000..b73f13cff
--- /dev/null
+++ b/Testing/Fuzzing/icc_dump_fuzzer.cpp
@@ -0,0 +1,248 @@
+/** @file
+File: IccApplyBPC.cpp
+
+Contains: Implementation of Black Point Compensation calculations.
+
+Version: V1
+
+Copyright: (c) see ICC Software License
+*/
+
+/*
+* The ICC Software License, Version 0.2
+*
+*
+* Copyright (c) 2003-2012 The International Color Consortium. All rights
+* reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in
+* the documentation and/or other materials provided with the
+* distribution.
+*
+* 3. In the absence of prior written permission, the names "ICC" and "The
+* International Color Consortium" must not be used to imply that the
+* ICC organization endorses or promotes products derived from this
+* software.
+*
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
+* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*
+* This software consists of voluntary contributions made by many
+* individuals on behalf of the The International Color Consortium.
+*
+*
+* Membership in the ICC is encouraged when this software is used for
+* commercial purposes.
+*
+*
+* For more information on The International Color Consortium, please
+* see .
+*
+*
+*/
+
+
+#include
+#include
+#include