From ee2f5b965a7b5cb0036444330d89fa32302f7845 Mon Sep 17 00:00:00 2001 From: ShuaixinHuang <1836656514@qq.com> Date: Fri, 6 Nov 2026 20:48:17 +0800 Subject: [PATCH] fix(desktop): bound exported task array-index conversion (#9139) --- src/domain/desktop/appTaskPython.ts | 6 ++- .../providers/desktopTaskArrayIndex.test.ts | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/boundary/providers/desktopTaskArrayIndex.test.ts diff --git a/src/domain/desktop/appTaskPython.ts b/src/domain/desktop/appTaskPython.ts index 50d0fbd68e..456e8f6060 100644 --- a/src/domain/desktop/appTaskPython.ts +++ b/src/domain/desktop/appTaskPython.ts @@ -61,7 +61,11 @@ def _selects(member, conditions) -> bool: def _step(items: list, token: str, last: bool): """One step into an array, by index or by a selector that must match one member, or none when it is the last.""" if INDEX.fullmatch(token): - return ("member", items[int(token)]) if int(token) < len(items) else ("none", None) + # A canonical decimal longer than the array length cannot name a member. + if len(token) > len(str(len(items))): + return "none", None + index = int(token) + return ("member", items[index]) if index < len(items) else ("none", None) conditions = _selector(token) if conditions is None: return ("unresolved", f"{token} is not a selector of the form [KEY=VALUE]") if _bracketed(token) else ( diff --git a/tests/boundary/providers/desktopTaskArrayIndex.test.ts b/tests/boundary/providers/desktopTaskArrayIndex.test.ts new file mode 100644 index 0000000000..80f1ada014 --- /dev/null +++ b/tests/boundary/providers/desktopTaskArrayIndex.test.ts @@ -0,0 +1,52 @@ +import { execFileSync } from "node:child_process"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { expect, it } from "vitest"; +import { PYTHON_TASK_EVALUATOR } from "../../../src/domain/desktop/appTaskPython.js"; +import { locatePointer } from "../../../src/domain/desktop/viewPointer.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +it("keeps oversized array indices absent without crashing exported task checks", async () => { + const huge = "9".repeat(5000); + const cases = [ + { items: [1], pointer: `/${huge}` }, + { items: [], pointer: `/${huge}` }, + { items: [1], pointer: "/0" }, + { items: [1], pointer: "/1" }, + { items: Array.from({ length: 12 }, (_, index) => index), pointer: "/11" }, + { items: Array.from({ length: 12 }, (_, index) => index), pointer: "/12" }, + { items: [1], pointer: "/01" }, + ]; + const folder = await createTestTempDirectory("task-array-index-"); + const script = join(folder, "fixture.py"); + await writeFile( + script, + `import json, re, math, sys +${PYTHON_TASK_EVALUATOR} +results = [] +for case in json.load(sys.stdin): + items, pointer = case["items"], case["pointer"] + located = _locate(items, pointer) + check = {"id": "absent", "kind": "value_absent", "file": "state.json", "pointer": pointer} + verdict = evaluate([check], {"state.json": {"format": "json", "view": items, "truncated": False}})[0] + results.append([located[0], located[1], verdict["result"]]) +print(json.dumps(results)) +`, + ); + const results = JSON.parse( + execFileSync("python3", ["-I", "-B", script], { + input: JSON.stringify(cases), + encoding: "utf8", + }), + ); + expect(results).toEqual( + cases.map(({ items, pointer }) => { + const located = locatePointer(items, pointer); + return [ + located.kind, + located.kind === "value" ? located.value : null, + located.kind === "none", + ]; + }), + ); +});