refactor: apply JPEGDEC patches via git apply, not string replace

The previous patch_jpegdec.py used in-place string replacement with a
single shared marker for two distinct DC writes (main store and
successive-approximation update). If only one of the two anchors
matched, the file was written half-patched and the shared marker locked
the partial state in for every subsequent run.

Generate the fixes as `git format-patch` artifacts under
scripts/jpegdec_patches/, then apply them in lexical order via
`git apply`. Idempotency is decided by git itself: `--check --reverse`
succeeds means already applied; `--check` succeeds means appliable;
neither aborts the build rather than leaving a half-patched file.

No behaviour change to the patched JPEGDEC source: same redirect, same
DC guards, same intent. Just stops the pre-build script from doing
something it has no business doing.
This commit is contained in:
Jeremy Klein
2026-05-19 20:05:07 +02:00
committed by jpirnay
parent 597edeebd8
commit 9e68ced046
3 changed files with 128 additions and 99 deletions
@@ -0,0 +1,28 @@
From 5dff5afab0c68d0d0c4385d72e6f0c030b613960 Mon Sep 17 00:00:00 2001
From: patch <patch@local>
Date: Mon, 18 May 2026 20:57:54 -0700
Subject: [PATCH 1/2] Redirect pMCU to sMCUs[0] when iMCU < 0 (MCU_SKIP)
---
src/jpeg.inl | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/jpeg.inl b/src/jpeg.inl
index a60b548..26bcf6f 100644
--- a/src/jpeg.inl
+++ b/src/jpeg.inl
@@ -1824,7 +1824,10 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
unsigned short *pFast;
uint32_t usHuff; // this prevents an unnecessary & 65535 for shorts
signed int iPositive, iNegative, iCoeff;
- signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];
+ // CrossPoint patch: redirect pMCU to sMCUs[0] when MCU_SKIP to avoid
+ // a wild pointer (~33 MB past sMCUs) that store-faults on AC writes.
+ signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs
+ : &pJPEG->sMCUs[iMCU & 0xffffff];
uint32_t ulBitOff;
my_ulong ulCode, ulBits, ulTemp; // local copies to allow compiler to use register vars
uint8_t *pBuf;
--
2.50.1 (Apple Git-155)
@@ -0,0 +1,41 @@
From f6238b54c2de34c8e29b0d371a7e902d9cf579bf Mon Sep 17 00:00:00 2001
From: patch <patch@local>
Date: Mon, 18 May 2026 20:58:22 -0700
Subject: [PATCH 2/2] Guard pMCU[0] DC writes against MCU_SKIP
---
src/jpeg.inl | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/jpeg.inl b/src/jpeg.inl
index 26bcf6f..1bd38b2 100644
--- a/src/jpeg.inl
+++ b/src/jpeg.inl
@@ -1855,7 +1855,11 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
{
// (*iDCPredictor) |= iPositive; // in case the scan is run more than once
// pMCU[0] = *iDCPredictor; // store in MCU[0]
- pMCU[0] |= iPositive;
+ // CrossPoint patch: guard against MCU_SKIP. The pMCU
+ // redirect makes &sMCUs[0] safe to dereference, but
+ // writing here would clobber the just-decoded Y DC.
+ if (iMCU >= 0)
+ pMCU[0] |= iPositive;
}
goto mcu_done; // that's it
}
@@ -1887,7 +1891,10 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
ulCode <<= pJPEG->cApproxBitsLow; // successive approximation shift value
(*iDCPredictor) += ulCode;
}
- pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
+ // CrossPoint patch: guard against MCU_SKIP. See note on the
+ // matching SA write above.
+ if (iMCU >= 0)
+ pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients
pFast = &pJPEG->usHuffAC[pJPEG->ucACTable * HUFF11SIZE];
--
2.50.1 (Apple Git-155)
+59 -99
View File
@@ -1,125 +1,85 @@
""" """
PlatformIO pre-build script: patch JPEGDEC for safe MCU_SKIP handling. PlatformIO pre-build script: apply CrossPoint's JPEGDEC patches via `git apply`.
When iMCU is MCU_SKIP (-8), JPEGDecodeMCU_P computes pMCU as The upstream JPEGDEC pin still has the wild-pointer + DC-write bugs in
&sMCUs[iMCU & 0xffffff] = &sMCUs[0xFFFFF8], a wild pointer ~33 MB past JPEGDecodeMCU_P that surface when EIGHT_BIT_GRAYSCALE decodes a 3-component
the array. EIGHT_BIT_GRAYSCALE decoding of a 3-component progressive progressive JPEG (each Y MCU drags two MCU_SKIP calls behind it for Cb/Cr).
JPEG calls JPEGDecodeMCU_P with MCU_SKIP twice per Y MCU (Cb then Cr), The patches in `scripts/jpegdec_patches/` carry the fix; this script applies
so every MCU exercises the wild pointer. each one against the libdep working tree.
Two patches, both required: Each patch's idempotency is decided by git itself:
* `git apply --check --reverse` succeeds -> already applied, skip
* `git apply --check` succeeds -> apply
* neither succeeds -> abort the build
1. Redirect pMCU to &sMCUs[0] when iMCU < 0. Without this, the AC Patches live in `scripts/jpegdec_patches/` as one-commit-per-fix files
decode loop (`pMCU[iIndex] = ...`) store-faults on any progressive (see the file headers for context). Applied in lexical order.
JPEG whose first scan carries AC coefficients (iScanEnd > 0).
2. Guard the two pMCU[0] DC writes with `if (iMCU >= 0)`. Without
this, patch 1 just relocates the corruption: chroma-skip DC writes
land at sMCUs[0] and clobber the freshly-decoded Y DC, producing
all-black output for progressive JPEGs at JPEG_SCALE_EIGHTH grayscale.
The AC loop body (`pMCU[iIndex] = ...` writes and matching reads) is
not separately guarded. It is unreachable on the JPEG_SCALE_EIGHTH
DC-only first-scan path, and guarding the writes without also skipping
bit consumption would desync the bitstream for the next MCU.
Both patches are idempotent.
""" """
Import("env") Import("env")
import os import os
import subprocess
import sys
PATCH_DIR = os.path.join(env["PROJECT_DIR"], "scripts", "jpegdec_patches")
def patch_jpegdec(env): def patch_jpegdec(env):
libdeps_dir = os.path.join(env["PROJECT_DIR"], ".pio", "libdeps") libdeps_dir = os.path.join(env["PROJECT_DIR"], ".pio", "libdeps")
if not os.path.isdir(libdeps_dir): if not os.path.isdir(libdeps_dir):
return return
patches = _patch_files()
if not patches:
return
for env_dir in os.listdir(libdeps_dir): for env_dir in os.listdir(libdeps_dir):
jpeg_inl = os.path.join(libdeps_dir, env_dir, "JPEGDEC", "src", "jpeg.inl") jpeg_dir = os.path.join(libdeps_dir, env_dir, "JPEGDEC")
if os.path.isfile(jpeg_inl): if not os.path.isdir(os.path.join(jpeg_dir, ".git")):
_apply_mcu_skip_pointer_fix(jpeg_inl) continue
_apply_dc_write_guards(jpeg_inl) for patch in patches:
_apply_one(jpeg_dir, patch)
def _apply_mcu_skip_pointer_fix(filepath): def _patch_files():
MARKER = "// CrossPoint patch: safe pMCU for MCU_SKIP" if not os.path.isdir(PATCH_DIR):
with open(filepath, "r") as f: return []
content = f.read() return sorted(
os.path.join(PATCH_DIR, name)
if MARKER in content: for name in os.listdir(PATCH_DIR)
return if name.endswith(".patch")
OLD = " signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];"
NEW = (
" " + MARKER + "\n"
" signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs\n"
" : &pJPEG->sMCUs[iMCU & 0xffffff];"
) )
if OLD not in content:
print( def _apply_one(jpeg_dir, patch_path):
"WARNING: JPEGDEC MCU_SKIP pointer patch target not found in %s " name = os.path.basename(patch_path)
"-- library may have been updated" % filepath if _git_apply_succeeds(jpeg_dir, patch_path, reverse=True):
)
return return
if not _git_apply_succeeds(jpeg_dir, patch_path, reverse=False):
content = content.replace(OLD, NEW, 1) # Not applied, not appliable -- the libdep source has diverged from
with open(filepath, "w") as f: # what the patch expects. Don't write a half-patched file.
f.write(content) result = subprocess.run(
print("Patched JPEGDEC: safe pMCU for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath) ["git", "apply", "--check", patch_path],
cwd=jpeg_dir,
capture_output=True,
def _apply_dc_write_guards(filepath): text=True,
MARKER = "// CrossPoint patch: guard pMCU DC writes for MCU_SKIP"
with open(filepath, "r") as f:
content = f.read()
if MARKER in content:
return
OLD_DC = """\
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients"""
NEW_DC = """\
""" + MARKER + """
if (iMCU >= 0)
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients"""
OLD_SA = """\
pMCU[0] |= iPositive;
}
goto mcu_done; // that's it"""
NEW_SA = """\
if (iMCU >= 0)
pMCU[0] |= iPositive;
}
goto mcu_done; // that's it"""
if OLD_DC not in content:
print(
"WARNING: JPEGDEC DC write guard target not found in %s "
"-- library may have been updated" % filepath
) )
return sys.stderr.write(
"ERROR: JPEGDEC patch %s does not apply cleanly:\n%s%s\n"
content = content.replace(OLD_DC, NEW_DC, 1) % (name, result.stdout, result.stderr)
if OLD_SA in content:
content = content.replace(OLD_SA, NEW_SA, 1)
else:
print(
"WARNING: JPEGDEC successive-approximation DC guard target not found in %s "
"-- continuing without that half of the patch" % filepath
) )
raise SystemExit(1)
subprocess.run(["git", "apply", patch_path], cwd=jpeg_dir, check=True)
print("Applied JPEGDEC patch: %s" % name)
with open(filepath, "w") as f:
f.write(content) def _git_apply_succeeds(jpeg_dir, patch_path, *, reverse):
print("Patched JPEGDEC: guard pMCU[0] DC writes for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath) cmd = ["git", "apply", "--check"]
if reverse:
cmd.append("--reverse")
cmd.append(patch_path)
return subprocess.run(
cmd, cwd=jpeg_dir, capture_output=True, text=True
).returncode == 0
patch_jpegdec(env) patch_jpegdec(env)