mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
872890bef314e5b877ce9fb1f4c2f4616d3870fc
491
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1596d6a275 | test: reduce wording coupling and prune redundant coverage | ||
|
|
562779bd71 |
fix(mcp): advertise absolute paths for tools that reject relative inputs (#9179)
* fix(mcp): advertise absolute paths for tools that reject relative inputs The domain schemas accepted relative paths, then handlers rejected them with isAbsolute. Those inputs now use absoluteLocalPathStringSchema, and the duplicate handler checks are gone. * fix(javascript): own recovery path admission in the schema Relative recovery paths now fail in absoluteLocalPathStringSchema, including the additional_paths index. Drop the duplicate handler check, and make the CLI, MCP, and tools/list tests assert that schema fact. * test: assert absolute-path facts instead of error prose Recovery and web-script tests keep the field, format code, and regex expectation. They no longer freeze the schema's explanatory sentence. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
923e6f77e3 |
fix(javascript): keep timer cancellation within the Node handle family (#7712)
* fix(javascript): keep timer cancellation within Node's handle family Node keeps timeouts and immediates in disjoint handle families. clearTimeout and clearInterval are aliases that act only on a Timeout from setTimeout or setInterval, while clearImmediate acts only on an Immediate from setImmediate. Crossing the families returns without doing anything, so the scheduled callback still runs. REA linked a cancellation whenever the cleared binding traced back to any timer schedule, so clearImmediate(setTimeout(work, 10)) reported a complete cancellation and the graph emitted a resolved cancels-timer relation. It also flagged no uncertainty for it. Require the cancel and the schedule to share a handle family before linking them. A cross-family clear now keeps linkedTimerId null and resolution unresolved, and the graph reports the cancellation as a candidate with an ambiguous-target unknown instead of a resolved fact. Verified against Node v24.22.1 over all nine schedule/cancel pairs: setTimeout and setInterval with clearTimeout or clearInterval cancel, and setImmediate with clearImmediate cancels; the other five combinations leave the callback running. Node's own lib/timers.js shows why: clearTimeout requires timer?._onTimeout, clearImmediate requires immediate?._onImmediate and returns early otherwise, and clearInterval delegates to clearTimeout. * chore: retrigger title and readiness checks after the title was corrected * fix(javascript): say cross-family timer clearing is runtime-dependent Review feedback from @morluto: the previous wording claimed clearing across the timer handle families "returns without doing anything, so the timer keeps running". That does not hold on every supported Node version. Measured on Node 24.22.1, all three cross-family pairs fire: | scheduled with | cancelled with | Node 24.22.1 | | --- | --- | --- | | `setTimeout` | `clearImmediate` | fires | | `setImmediate` | `clearTimeout` | fires | | `setImmediate` | `clearInterval` | fires | `clearImmediate` cancelling a `Timeout` on Node 22 is the case the old claim missed. Within a family the clear always cancels, which is why the analysis links only same-family pairs; across families the outcome depends on the runtime, which is exactly why the analysis reports `unresolved` rather than `complete` in either direction. The behavior and the tests are unchanged. The `timerHandleFamily` doc comment, the linked-origin comment, and the test comment now state the runtime dependence and why it justifies an unresolved result. |
||
|
|
abfccc0683 |
fix(javascript): bound symbolic property and member queries (#8876)
Fixes #8838. Admit all symbolic query entry points through the existing resolver budget, preserve cache-hit depth and restore entered frames in finally. Retain independent facts with partial source coverage instead of losing entire scripts to recursive spreads. Validated 273 focused tests, check:fast/docs:check, real CLI/stdio-MCP projections and complete parent reads, independent public-source oracle, and unchanged full results for the official Electron control and Bitwarden selected preload. Full technical CI 42308068679 passes, including all eight shards and required aggregates. Greptile reviews this head at 5/5; no completed Codex review of this head is available. The Author readiness-only exception is explicitly authorized; no human-review attestation is added. Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage) |
||
|
|
55ee36809d |
fix(javascript): apply package type scope to application sources (#7883)
* fix(javascript): apply package type scope to application sources Application analysis parsed .js sources without their package scope, so an unbound require in a type-module package became a resolved CommonJS dependency. The parent now observes each source's nearest same-container package.json, stopping at node_modules, and the worker parses with that package type as reference imports already do. Fixes #7323 * fix(javascript): keep package type unknown behind unavailable manifests The package-type lookup indexed only readable files, so a nearer package.json whose bytes were unavailable was skipped and an ancestor's "type" applied to sources it does not own. Index unavailable manifests by container and directory too, and stop the walk with "unknown" when one is the nearest owner, as the Next app-context lookup already does. * fix(javascript): keep Electron preloads outside module package scope Electron's preload loader ignores a package's "type": "module" and gives a .js preload `require`; ESM preloads need the .mjs suffix. Applying the package type to every .js source marked such a preload as an ES module, so its `require("electron")` edges and IPC pairing disappeared. A source that requires an Electron module now keeps the context REA inferred before package scope was applied. Node-loaded sources in a module scope still drop unbound `require`. * fix(javascript): admit Electron script loaders only on renderer evidence The previous commit dropped a module package type whenever a source required an Electron module. A Node-loaded ES module that requires Electron, or one whose Electron call goes through a local `require`, then regained the false require edges the package scope removes. The parser applies the package type unconditionally again. The worker re-reads a package-module script that requires an Electron module once outside the package scope. The source keeps that context only when the static pass there directly observes a contextBridge exposure or a renderer IPC operation, which only Electron's renderer-side loaders provide. * fix(javascript): check Electron renderer evidence without a second static pass |
||
|
|
9044b6fb36 |
fix(kuna): mark project-dependent operations unavailable on export failure (#8748)
* fix(kuna): mark project-dependent operations unavailable on export failure * fix(providers): preserve sibling operations in capability error copies * fix(kuna): propagate sibling unavailability on export fallback * fix(binary): preserve observed sibling recovery instructions across fallback observations * fix(binary): build sibling unavailability from structured reasons without rewriting display text * test(kuna): synchronize fallback test on settled export workspace retirement * test(kuna): release and join export synchronization barrier across exit paths * test(kuna): synchronize fallback test on observed export process exit * test(kuna): gate fallback completion on settled export cache build * test(kuna): preserve build promise chain and restore spy after teardown |
||
|
|
3e0599a949 |
fix(javascript): preserve loader identity across terminal origins (#9107)
Exclude only proven separate values from Webpack loader alias walks; preserve unsupported, projected and unresolved origins as unknown and retain the real alias-state bound. Refs #7253. The full Bitwarden precision regression remains open. Validation: 336 focused tests and check:fast passed; real Bitwarden preload identity retained; full main/preload completed with disclosed partial coverage. Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage) |
||
|
|
99bcd3e4e0 |
fix(javascript): report empty Electron coverage instead of silent zeroes (#8332)
* fix(javascript): report empty Electron coverage instead of silent zeroes `analyze_javascript_application` reported `browser_windows: 0` and `ipc.operations: 0` with only generic partial/unknown flags, so a bundle that imports `electron` but whose Electron facts were not recovered read exactly like an application with no IPC at all. Issue #8201 is that false negative on a real Rolldown bundle. Two additions, both scoped to "Electron is referenced but nothing was recovered": **A machine-readable indicator.** `electron_summary` gains `electron_semantics: "recovered" | "empty"`. It is absent when no analyzed source links an Electron module, so its presence distinguishes "not an Electron application" from "Electron coverage is empty". It follows the existing `protocol_registrations.exactOptional()` precedent, so previously captured evidence stays valid. **A graph limitation.** The limitation list already carried a note when at least one Electron fact was recovered but said nothing when zero were. It now states that zero IPC operations and zero BrowserWindows mean empty coverage rather than absence. `electronModuleReferenced` is the single owner of the module test. It reuses `ELECTRON_MODULE`, which matches exactly the supported entry points (`electron`, `electron/common`, `/main`, `/renderer`, `/utility`), so `electron-helper` does not count, and it ignores type-only links because importing Electron types promises no boundary fact. Five tests cover recovered, empty, not-referenced, type-only, and supported subpath versus unrelated package. The empty, recovered and type-only cases fail on unmodified `main`. * chore: retrigger title and readiness checks after the title was corrected * fix(javascript): report Electron coverage from retained facts Four review findings on the coverage indicator. **A rejected application projection hid the reference.** A file whose expanded relationships exceed the result budget is replaced by a placeholder with no module links before the summary runs, while `graphLimitations` reads the raw analysis. The same response could then warn in prose and omit the field. The summary now takes the retained analysis for coverage only, and both outputs read one `electronSemanticsState`, so they cannot disagree. **A skipped semantic analysis hid the reference too.** `electronModuleReferenced` read only `semantic.ir.moduleLinks`, which is absent when semantic analysis is skipped for a budget and drops `import("electron")` or `module.require("electron")` forms. It now falls back to the retained `javascript.references`, which the static producer records for those inputs, while still excluding `type-import` links when the richer owner is available. `electronBoundaryFactCount` is now the single owner of the fact total, replacing a duplicate inline reduce in the graph builder. **Omission no longer claims absence.** The schema comment said a missing `electron_semantics` means the application does not use Electron. The predicate only establishes that no retained fact names one, and unread sources, failed analysis, and dynamic module names can hide its use, so the contract now says unknown. Two regressions cover a rejected projection and a skipped semantic analysis; both fail on the previous commit. * fix(javascript): count a dynamic Electron import as coverage Review finding: `electronModuleReferenced` returned as soon as module links existed, so a literal dynamic import was ignored. Verified against the two fact owners: import("electron") -> references: [dynamic-import electron] moduleLinks: [] require("electron") -> references: [require electron] moduleLinks: [require electron] An application that only loads Electron through `import("electron")` therefore got neither `electron_semantics: "empty"` nor the empty-coverage warning, and the zero counts stayed unexplained. The two owners now answer their own half instead of one short-circuiting the other. Module links answer a runtime static import or require and keep excluding `type-import`; the retained reference is read for every kind except `static-import`, which is exactly the type-only risk. A dynamic specifier is still never counted by either owner, so this remains an observed-reference claim rather than an absence claim. The regression uses `import("electron")` and fails on the previous commit. * fix(javascript): read only module-loading Electron references Two review findings, both verified against the static reference producer. **A retained runtime static import was skipped.** The excluded kind was meant to protect the type-only distinction, but the static analyzer records a whole `import type { IpcMain } from "electron"` declaration as no reference at all: import type { IpcMain } from "electron" -> references: [] import { app } from "electron" -> references: [static-import electron] require("electron") -> references: [require electron] import("electron") -> references: [dynamic-import electron] new Worker(new URL("electron", m)) -> references: [worker electron] When semantic analysis is skipped for a budget the module links are absent, so that exclusion dropped a real runtime ESM import and left the zero counts unexplained. **A worker URL path is not an Electron module.** `new Worker(new URL("electron", import.meta.url))` records a `worker` reference whose specifier is a path relative to the importing source, so counting it reported an ordinary worker file as `electron_semantics: "empty"`. `ELECTRON_REFERENCE_KINDS` is now an explicit allowlist of the kinds that load a module by package specifier — `static-import`, `dynamic-import` and `require` — so `worker` and `service-worker` URL paths are excluded. Module links are still read first for the type-only distinction. Three regressions cover a skipped-semantics ESM import, a skipped-semantics whole type-only import, and a worker URL path; two of them fail on the previous commit. * fix(javascript): own Electron applicability in the static producer Review finding. A `worker` reference records a URL relative to the importing source, so `new Worker(new URL("electron", import.meta.url))` names a local file and is not an Electron module. The boundary owner already read only module-loading reference kinds, but `finalizeStaticAnalysis` still treated every matching static reference as `analysis_coverage.applicability.electron: "observed"`, which `javascriptStaticSourceCoverageSummary` publishes. One response could therefore say Electron was observed in its coverage view while the Electron summary omitted `electron_semantics`. `ELECTRON_REFERENCE_KINDS` now lives next to `ELECTRON_MODULE` in the domain owner and is consumed by both the static producer and the Electron boundary owner, so the two outputs cannot disagree. A worker URL path is `unknown` for both, while a `require("electron")` is `observed` for both. The regression asserts the domain applicability fact and the boundary summary agree for a worker URL path; it fails when the producer stops filtering by kind. * fix(javascript): require a loaded Electron module for applicability Review finding. A CommonJS `require.resolve("electron")` records a `require` reference with `operation: "resolve"`: it locates the module without loading it, so no Electron boundary fact is expected. Both predicates dropped the operation, so such a source reported `electron_semantics: "empty"` and the missing-coverage warning even though Electron was never loaded. Measured on the static producer: require.resolve("electron") -> references: [require resolve electron] applicability.electron: unknown require("electron") -> references: [require load electron] applicability.electron: observed import("electron") -> references: [dynamic-import load electron] applicability.electron: observed `electronModuleReferenced` and `finalizeStaticAnalysis` now both require `operation === "load"`, so the boundary summary and the coverage view stay aligned and reachability-only references no longer claim missing coverage. The regression asserts a resolve-only source produces neither the field nor the warning, and it fails when the operation check is removed. * fix(javascript): classify Electron loads in the static fact owner Review finding, and the correct home for the previous two rounds of the same complaint. A Webpack/Rspack factory may name a local module `"electron"`, and the static producer then records a loaded `require` reference whose specifier is that bundle module key. Treating the key as a package specifier reported `electron_semantics: "empty"` plus the missing-coverage warning for a bundle that never loads Electron. Three changes, all in the domain owner, so no consumer re-derives the question: - `JavaScriptModuleRangeIndex.hasKey` answers whether a specifier is a recovered bundle module key. A `require` whose specifier is such a key loads a module inside the artifact. - `electronObserved` in `finalizeStaticAnalysis` now requires a loaded module-load reference whose specifier is not a bundle key. The published `analysis_coverage.applicability.electron` therefore owns every exclusion: `type-import`, `require.resolve`, `worker`/`service-worker` URL paths, and bundle-local keys. - `electronModuleReferenced` consumes that owned fact instead of re-deriving it, keeping the boundary summary and the coverage view from disagreeing. Historical evidence without the field still falls back to the retained references. Measured: bundle-local module ID "electron" -> applicability unknown require("electron") -> applicability observed webpack external n(45840) -> applicability observed The regression covers both the local key and the real external in the same factory shape; it fails without the bundle-key exclusion. * fix(javascript): identify a bundle module by its loader, not its key Both review findings were right, and they show why the previous key-set approach was wrong: bundle-key membership is a property of the file, but whether a specifier names a module inside the artifact is a property of the reference. `JavaScriptStaticReference` now records the loader that produced it, and `electronObserved` excludes `factory-loader` references instead of every specifier that happens to match a recovered module key. Measured on Node 24.22.1: local module keyed "electron" -> unknown (factory-loader) external 45840 = require("electron") -> observed (commonjs-loader) factory keyed "electron" that loads the pkg -> observed (commonjs-loader) The third case is the regression: a bundle whose factory is *named* `"electron"` but whose body does `e.exports = require("electron")` is a real Electron load, and the old key-set check suppressed it. The real load keeps its `commonjs-loader` identity even when the same specifier also names a factory, so one response can rely on the loader while the other relies on the key without either lying. `JavaScriptModuleRangeIndex.hasKey` is removed; it answered the wrong question. One regression covers all three shapes and fails when the `factory-loader` exclusion is dropped. * fix(javascript): carry the reference loader across the worker boundary The worker schema is strict, so the new `loader` field broke the worker handshake: `unrecognized_keys` on every `references` entry. That surfaced in CI as both a worker-schema test failure and a package-baseline failure reporting `worker_failure_reason: "io"` for a one-file Electron app. `referenceShape` now accepts the same three loader identities as the domain type, so the fact survives the worker boundary instead of collapsing every analysis to an execution failure. Verified: `JavaScriptAnalysisWorkerSchemas.test.ts` 10 passed, and the full domain/application/javascript suite is 5484 passed | 10 skipped. The remaining `SourceMapCommand` and `DarwinHelperCache` findings are present on unmodified `main` and are not from this change. * fix(javascript): remove the superseded key set and forward a deferred loader Two review findings. **The `keys` set is dead.** `JavaScriptModuleRangeIndex.keys` is left over from the abandoned key-membership approach: the loader-identity refactor removed `hasKey` and the write in `add`, so nothing reads it. Removed. **The deferred require path dropped the loader identity.** `requireCallSpecifier` returns a `loader` for `commonjs-loader`, `factory-loader` and `create-require`, and the direct `addReference` call forwards it, but `finishModuleRequireCalls` did not. A `createRequire` loader saved under a member literally named `require` therefore lost its identity: import { createRequire } from "node:module"; const holder = { require: createRequire(import.meta.url) }; holder.require("x"); before: require "x" with loader undefined after: require "x" with loader "create-require" Three regressions cover it; one fails without the change. I did **not** change the inline-type-only import case. `isTypeOnlyImportDeclaration` deliberately keeps a declaration whose bindings are all inline type-only, because `hasUncertainTypeOnlyModuleSideEffect` reports that decision as `type-only-module-side-effect` uncertainty: whether TypeScript emits the import depends on the consumer's compiler options. Narrowing it to erased broke `javascriptStaticCoverage.test.ts` and `javascriptSemanticAnalysis.require_links.test.ts`, which assert exactly that contract. If Electron applicability should ignore that reference, the decision belongs where applicability is derived, not in the shared declaration predicate — say the word and I will do it there instead. * fix(javascript): keep a commonjs-loader reference out of the bundle module index Review finding. `finalizeLocatedFindings` attaches `module_runtime` to every finding that sits inside a factory module range, whoever produced it. `resolveReference` then sent every reference carrying `module_runtime` through the bundle-module lookup, so a real CommonJS load inside a factory body resolved to the local module key that shared its name: "electron": (e, t, n) => { e.exports = require("electron") } fact: specifier "electron", loader "commonjs-loader", module_key "electron" before: resolved through the bundle module index to `#module:electron` after: resolves as an external package specifier Only a `factory-loader` specifier names a recovered bundle module key, which is what `loader` already records after the loader-identity change. The module lookup is now gated on it, and an absent `loader` keeps the previous behaviour for older findings. |
||
|
|
172f248884 |
fix(javascript): emit no fs acquisition for a rejected literal mode (#8624)
* fix(javascript): emit no fs acquisition for a rejected literal mode
`fs.open`, `fs.openSync` and `fs/promises.open` validate the `mode` in argument 2 before
touching the filesystem, so a mode Node rejects throws synchronously and no descriptor
exists. The guard read only the flag, so a rejected mode kept its acquisition and any
linked release.
Measured on Node 24.22.1:
fs.openSync(p, "r", -1) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", 1.5) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", NaN) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", 1e21) -> THREW ERR_OUT_OF_RANGE
fs.openSync(p, "r", true) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", 0n) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", {}) -> THREW ERR_INVALID_ARG_TYPE
fs.openSync(p, "r", "8") -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, "r", "") -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, "r", "0o644") -> THREW ERR_INVALID_ARG_VALUE
--- accepted ---
fs.openSync(p, "r", 0o644) -> ok
fs.openSync(p, "r", 2147483647) -> ok
fs.openSync(p, "r", -0) -> ok
fs.openSync(p, "r", "0644") -> ok
fs.openSync(p, "r", null) -> ok
fs.openSync(p, "r", void 0) -> ok
The guard decides only exactly-readable literals. A numeric mode is validated as an
unsigned 32-bit integer; a string mode must be a canonical octal digit string, padded
leading zeros allowed, and its value must stay inside uint32; `null`, `undefined` and
`void <expr>` default the mode and are kept, as is a computed value.
Three boundaries are deliberate:
- Values in `[2^31, 2^32-1]` **abort the Node process** rather than throwing
(`Assertion failed: args[2]->IsInt32()`), so they stay undecided instead of being
claimed as a synchronous throw.
- A spread before the mode slot shifts the runtime position.
- A unary chain is resolved through its innermost readable operand, and a nested
boolean or string result is left undecided rather than applied as a number.
Regressions cover both directions. Full suite here: 5466 passed | 7 skipped.
* fix(javascript): remove the superseded string mode check
Review finding. `rejectsOpenModeLiteral` builds `octal` from a string literal or a
no-expression template and returns inside that branch for every value it matched. A
`StringLiteral` therefore never reaches the later `isStringLiteral` check, which cannot
run and is a second copy of the canonical-octal rule to maintain.
* test(javascript): cover the spread and boundary paths of the mode guard
Review feedback from @greptile-apps: the new mode tests skipped both paths the
guard deliberately leaves undecided. Two spread cases are added:
- `openSync(...args, "r", "0600")`, where a spread in the flag slot shifts the
runtime position of the mode;
- `openSync(p, "r", ...modes)`, where a spread occupies the mode slot itself.
Both must keep the acquisition, because neither establishes the mode value. The
boundary cases were already present: `2147483647` and `2147483648` are keep
cases (`2147483648` aborts the process rather than throwing) and `4294967296` is
the reject case, alongside the octal `37777777777` keep and `40000000000` reject
pair.
127 cases pass in this file.
|
||
|
|
3708538e85 |
feat(javascript): compare static endpoints with HAR captures (#9090)
* feat(javascript): compare static endpoints with HAR captures * test(javascript): preserve original HAR fixture bytes during formatting * test(javascript): load the byte-preserved HAR fixture |
||
|
|
7b6fc6f13d |
fix(javascript): resolve electron findings through aliases of interop results (#8914)
* fix(javascript): resolve electron findings through aliases of interop results * fix(javascript): resolve interop aliases iteratively and check target mutations * fix(javascript): retain possible writes through uncertain aliases and guard positive reads * perf(javascript): cache alias change status across semantic interop lookups |
||
|
|
a2134b3a74 |
fix(javascript): resolve module-level Electron options in functions (#8960)
* fix(javascript): resolve module-level Electron options in functions * fix(javascript): limit module option ancestry walk * fix(javascript): preserve uncertain captured argument ordering * fix(javascript): retain hoisted activation uncertainty * fix(javascript): retain function ownership through loops * fix(javascript): account for classic global activations * fix(javascript): bind first activations to lexical identities * fix(javascript): retain uncertain global activation timing * fix(javascript): isolate receiver facts and share computed reads * fix(javascript): retain projected and opaque global activations * fix(javascript): refresh activation bounds as opaque effects grow * fix(ci): format website watch directories without changing configuration * fix(javascript): share source reads across opaque-effect updates * fix(javascript): retain constructed-code global activation bounds * fix(javascript): share runtime activation and receiver facts * fix(javascript): reuse pattern reads and function positions * fix(javascript): isolate copied paths and preserve runtime reads * style(javascript): format migrated reference imports * fix(javascript): normalize constructor activation and receiver effects * fix(javascript): retain aliased receiver timing and lexical global this * fix(javascript): preserve effect-free receiver identity across activations * fix(javascript): retain outer activation and prototype effects * fix(javascript): index implicit reads without deep path rescans * fix(javascript): retain aliased prototype and parameter read uncertainty * fix(javascript): index reflected reads through intrinsic invocation facts * fix(javascript): retain unresolved global activation reads * chore: refresh PR branch after review fix publication * fix(javascript): retain projected receiver reads and cache method facts * fix(javascript): retain inherited and implicit activation reads * fix(javascript): retain caller-owned this uncertainty * fix(javascript): retain reflected reads with unresolved spread arguments * fix(javascript): preserve enclosing activation for decorators * fix(javascript): track intrinsic literal prototype effects * fix(javascript): retain catch activation from implicit reads * perf(javascript): reuse effective keys for projected reads * fix(javascript): retain possible caller-owned ambient effects |
||
|
|
89317a39fa |
fix(javascript): remove duplicate childNodes import (#8886)
* fix(javascript): remove duplicate childNodes import
The squash of #7967 re-added the import already introduced in
|
||
|
|
09c889b3b5 |
test(javascript): cover Request endpoint identity and provenance (#6365)
fix(javascript): resolve inline Request URLs in fetch calls |
||
|
|
54d819f382 |
fix(javascript): emit no timer for a literal non-function callback (#8044)
* fix(javascript): emit no timer for a literal non-function callback
Node validates the timer callback before creating a handle:
if (typeof callback !== 'function')
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
The throw is synchronous, before any timer exists, but the analysis reported a
`schedule` operation for such calls.
Measured on Node 24.22.1:
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(`code`, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1n, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(true, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(void 0, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout([], 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout({}, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout() -> THROWS ERR_INVALID_ARG_TYPE
setImmediate("code") -> THROWS ERR_INVALID_ARG_TYPE
setInterval("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
setTimeout(function(){}, 0) -> OK
setTimeout(() => {}, 0) -> OK
setTimeout(fn, 0) -> OK
setTimeout(class {}, 0) -> OK (fails only when the class is invoked)
So a schedule is dropped only for an exactly readable literal that is not a
function, or for a missing argument. An identifier, a call result or a spread may
still be a function at runtime and keeps its schedule.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): scope the timer callback rule to Node's timers module
greptile flagged two problems with the first version.
P1 - browser timers disappeared. The rule was applied to bare globals, but
browser and Electron renderer sources reach this analysis without a Node/browser
distinction, and there `setTimeout("refresh()", 0)` is a real timer: the host
evaluates the string. The rule now applies only when the timer is imported from
`node:timers`, where that form cannot exist. A bare global schedule keeps its
fact whatever argument 0 holds.
P2 - unused import. `readExactJavaScriptLiteral` was imported but never used; the
helper reads shapes through `unwrapJavaScriptExpression`. Removed.
Measured on Node 24.22.1 (the throw is what makes a node:timers schedule
impossible; the bare-global case is intentionally left alone):
import { setTimeout } from "node:timers";
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(fn, 0) -> OK
--- bare global, host may be a browser ---
setTimeout("code", 0) -> kept as a schedule
24 cases cover both sides plus the cancellation link.
* fix(javascript): reject every non-function global and unary timer callback
Reviewing the timer callback guard against Node 24.22.1 found two gaps. Both are
in the same "a call that always throws must not emit a resolved fact" family.
**An unmodified global that is not a function was accepted.** The guard read
literal nodes only, so a bare global identifier passed through even though Node
rejects it before creating a handle:
setTimeout(undefined) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(NaN) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(Infinity) -> THREW ERR_INVALID_ARG_TYPE
`isUnmodifiedGlobalReference` keeps a shadowed binding out of the rule, so a
local `const NaN = () => {}` keeps its schedule.
**Only `void` was treated as a non-function unary operator.** Every unary operator
yields a primitive, and Node rejects a primitive callback:
setTimeout(!0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(~0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(-1) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(+"1") -> THREW ERR_INVALID_ARG_TYPE
setTimeout(typeof 1) -> THREW ERR_INVALID_ARG_TYPE
A class expression is still kept, because it passes Node's own `typeof` check and
only fails when invoked.
Fifteen regressions cover the rejected forms plus arrow, identifier, class,
spread and shadowed-global cases; ten fail on the previous commit.
* fix(javascript): let sloppy eval shadow the non-function global callback
Review finding. The new global check read `undefined`, `NaN` and `Infinity` as
unmodified globals, but sloppy `eval` can introduce a local function under one of
those names, which then shadows the global. Measured on Node 24.22.1 in script
scope:
function g() { eval("var undefined = () => {}"); setTimeout(undefined, 0); }
-> schedules a timer
function g() { eval("var NaN = () => {}"); setTimeout(NaN, 0); }
-> schedules a timer
`semanticOpaqueNameMayHaveChanged` now has to agree that no visible activation can
introduce such a name before the global reading is conclusive. In a module the
same `eval` is strict and introduces no outer binding, so the global reading still
holds and the timer is still dropped.
Three regressions cover the sloppy forms, and a fourth pins the no-eval cases as
still dropped. The three sloppy cases fail without the guard.
|
||
|
|
b668cea2af |
fix(javascript): emit no fs acquisition for a literal non-flag string (#8125)
* fix(javascript): emit no fs acquisition for a literal non-flag string
`fs.open` and `fs.openSync` validate a literal flag before touching the
filesystem:
if (!isFd(flags) && !FLAGS_SET.has(flags))
throw new ERR_INVALID_ARG_VALUE('flags', flags, ...);
The throw is synchronous, before a descriptor exists, but the analysis reported
an `acquire` operation for those calls.
Measured on Node 24.22.1 against a file that does not exist, so each valid flag
is exercised without an EEXIST masking it:
ACCEPTED as a flags string:
a a+ as as+ ax ax+ r r+ rs rs+ sa sa+ sr sr+ w w+ wx wx+ xa xa+ xw xw+
REJECTED with ERR_INVALID_ARG_VALUE:
"" A R W rb wb s s+ x r,x
A Python-style `"rb"`/`"wb"` is the common case: those are valid in Python and
fatal at the Node call site.
`FS_OPEN_FLAGS` in `javascriptStaticAnalysisHelpers.ts` already holds exactly
those 22 accepted flags. It is now exported and reused so the accepted set has
one owner. A numeric flag is a platform bitmask and a computed value may still be
valid, so only an exactly readable non-flag string is dropped.
`fs.createReadStream` keeps its acquisition: it does not validate the flag
synchronously, it returns a stream and reports the failure on the error event.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject every fs flag literal Node rejects
The first version only decided string flags. Node's check covers every flag
shape, and each one below throws `ERR_INVALID_ARG_VALUE` or `ERR_OUT_OF_RANGE`
synchronously, before a descriptor exists:
openSync(p, 1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, 1e400) -> THREW ERR_OUT_OF_RANGE
openSync(p, NaN) -> THREW ERR_OUT_OF_RANGE
openSync(p, Infinity) -> THREW ERR_OUT_OF_RANGE
openSync(p, 0n) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, true) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, false) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, {}) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, []) -> THREW ERR_INVALID_ARG_VALUE
--- accepted ---
openSync(p, 0) / 1 / 65536 / +5 -> ok
openSync(p) / undefined / null -> ok
openSync(p, "r") / "wx" / "sr" -> ok
`NaN` and `Infinity` are identifiers rather than literals, so they are decided
only through `isUnmodifiedGlobalReference`; a shadowed binding keeps the
acquisition.
* fix(javascript): judge an fs flag by its value, and ignore a shifting spread
Two P1 findings on the first version.
**A unary operator was judged by its operand.** `openSync(p, void 1.5)` passes
`undefined`, so Node applies the default read flag, but the branch saw a
fractional operand and dropped a real acquisition. `openSync(p, -0)` is the valid
zero bitmask and was also dropped. `staticFlagBits` now evaluates the operator:
openSync(p, void 1.5) -> ok (keeps the acquisition)
openSync(p, void 0) -> ok
openSync(p, -0) -> ok
openSync(p, -0.0) -> ok
openSync(p, +0) -> ok
openSync(p, ~-1) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, +1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, !0) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, ~0) -> THREW EINVAL
openSync(p, typeof 1) -> THREW ERR_INVALID_ARG_VALUE
**A spread before the flag slot shifts the runtime position.** With
`args = [path, "w"]`, `openSync(...args, "0600")` opens with mode `"0600"`, but
argument 1 is then the mode rather than the flag. The check now keeps the
acquisition whenever argument 0 is a spread, because the flag it inspects is not
provably the flag at runtime.
* fix(javascript): leave a signed fs bitmask undecided
greptile caught that the flag check rejected every negative integer, but Node
accepts some signed bitmasks. Measured on Node 24.22.1 against an existing file:
openSync(p, -2147483648) -> ok
openSync(p, -1073741824) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -2) -> THREW EINVAL
openSync(p, 2147483647) -> THREW EINVAL
openSync(p, 2147483648) -> THREW ERR_OUT_OF_RANGE
openSync(p, -2147483649) -> THREW ERR_OUT_OF_RANGE
So the sign does not decide: some negative values open a descriptor and others do
not, and which is which is a platform bitmask question this analysis cannot
settle. The check now rejects only a non-integer or a value outside the signed
32-bit range, and leaves an in-range negative undecided — the same choice
`greptile` asked for, on the principle that a call which may succeed must keep
its acquisition.
`!`, `typeof` and `delete` are still rejected, because those yield a boolean or a
string regardless of the operand.
* fix(javascript): read a no-expression template literal flag
Review finding. A no-expression template literal is exactly readable, so it is the
same flag string Node receives, but the guard only read `StringLiteral`:
fs.openSync(p, ` + "`rb`" + `) -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, ` + "`r`" + `) -> ok
The flag slot now accepts an interpolation-free template; an interpolated template
stays undecided.
Four regressions cover both directions; two fail without the change.
|
||
|
|
f285fd4f82 |
fix(javascript): emit no child process for a literal non-object options slot (#8019)
* fix(javascript): emit no child process for a literal non-object options slot
Node validates the child_process options object before creating anything, so a
literal primitive or a literal array in that slot throws ERR_INVALID_ARG_TYPE
synchronously and no child process exists. The analysis still reported a complete
spawn for them.
Measured on Node 24.22.1:
spawn(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], null) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], []) -> THROWS ERR_INVALID_ARG_TYPE
execFile(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
spawn(cmd, args, undefined) -> SPAWNED
spawn(cmd, args, {}) -> SPAWNED
execFile(cmd, [], null) -> SPAWNED
fork(worker, [], null) -> SPAWNED
exec(cmd, "inherit") -> SPAWNED
The asymmetries matter: `spawn` rejects `null` and exempts only `undefined`,
while `fork` and `execFile` skip any nullish options, and `exec` spreads its
options and validates none of them. Drop a candidate only when the options slot
is a provable literal primitive or array, so an unresolved or non-literal value
stays a candidate.
`spawn(cmd, [], null)` is the sharpest case: it was indistinguishable from the
valid `undefined` form, including a resolved connects-stdio relation, for a call
that dies before any child exists.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject literal options Node shall reject, and drop a stray file
Two review findings on #8019.
greptile: `rejectsOptionsShape` still kept `spawn("tool", [], 0n)`, `-0` and
`1e400`, which Node also rejects before creating a child
(`ERR_INVALID_ARG_TYPE: The "options" argument must be of type object`). Those
forms are exactly the ones `readExactJavaScriptLiteral` refuses to price — a
BigInt, a non-finite numeric literal and a negated numeric literal — so read the
shape directly instead of widening the shared exact-value reader. `NaN` is an
identifier rather than a literal, so it still needs scope resolution and is left
as a documented gap.
Also remove `.rea-hopper-stage-YOryZ6/`, a local Hopper installation stub that an
`add -A` swept into the commit. It is unrelated to this change.
* fix(javascript): keep a void options slot, reject ! and ~
greptile flagged that `isUnpricedPrimitiveLiteral` dropped `spawn("tool", [], void 0)`,
`fork(..., void 0)` and `execFile(..., void 0)`. It checked the unary operand without
the operator, but `void <expr>` evaluates to `undefined`, which is the one options
value Node accepts in place of an object.
Measured on Node 24.22.1:
spawn(cmd, [], void 0) -> SPAWNED
spawn(cmd, [], void 5) -> SPAWNED
spawn(cmd, [], -0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], -5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], +5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], !0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], ~0) -> THROWS ERR_INVALID_ARG_TYPE
So the operator now decides: `void` keeps the spawn, `!` and `~` reject whatever
their operand, and `+`/`-` reject only when the operand is a numeric literal.
* fix(javascript): reject a non-finite global in the child-process options slot
An unshadowed `NaN` or `Infinity` in the options slot is a number, and Node's
options check rejects a number exactly as it rejects any other primitive. The
guard read only literal nodes, so the global identifier passed through. Measured
on Node 24.22.1:
spawn("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
spawn("e", Infinity) -> THREW ERR_INVALID_ARG_TYPE
execFile("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
fork("x.js", NaN) -> THREW ERR_INVALID_ARG_TYPE
--- accepted ---
exec("e", NaN) -> ok, because exec validates no options
spawn("e", undefined) -> ok, the one accepted global
spawn("e", null) -> ok
spawn("e", []) / ({}) -> ok
`isUnshadowedGlobal` keeps a local binding named `NaN` out of the rule, so a
shadowed value keeps its fact. Seven regressions cover the rejected forms plus
`exec`, `undefined`, `null`, an array, an object and a shadowed `NaN`; all seven
fail on the previous commit.
|
||
|
|
3640f01512 |
fix(javascript): emit no promise for a literal non-callable executor (#8264)
* fix(javascript): emit no promise for a literal non-callable executor
Node's `Promise` constructor calls the executor synchronously and throws a
`TypeError` when it is not callable, before any promise object exists. The
analysis reported a `constructor` promise operation with a hardcoded
`sourceResolution: "complete"`, which the graph projects as
`creates-promise` `resolved`.
Measured on Node 24.22.1:
new Promise() -> THREW TypeError
new Promise(1) / (-1) -> THREW TypeError
new Promise("work") / (`work`) -> THREW TypeError
new Promise(true) / (!0) -> THREW TypeError
new Promise(null) / (void 1) -> THREW TypeError
new Promise(1n) / (/re/) -> THREW TypeError
new Promise([]) / ({}) -> THREW TypeError
--- accepted ---
new Promise(() => {}) -> OK Promise
new Promise(async () => {}) -> OK Promise
new Promise(function () {}) -> OK Promise
new Promise(class {}) -> OK Promise
A class passes Node's own `typeof` check, so it keeps its operation. An
identifier, a call result or a spread may still supply a function and also keeps
its operation.
The filter gates the candidate push rather than `promiseCandidateDetails`, which
also decides whether an `await` becomes an `awaited-expression`: gating there
made `await new Promise(1)` emit a spurious task fact, whereas gating at the push
site makes it emit nothing, which is the honest answer.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject a non-finite executor, not only a literal
greptile caught that `new Promise(1e309)` still emitted a promise. Babel stores
that literal as `Infinity`, and `readExactJavaScriptLiteral` refuses to price a
non-finite number, so the guard missed it.
Measured on Node 24.22.1 — every one throws before a promise exists:
new Promise(1e309) -> THREW TypeError
new Promise(1e400) -> THREW TypeError
new Promise(Infinity) -> THREW TypeError
new Promise(-Infinity) -> THREW TypeError
new Promise(NaN) -> THREW TypeError
A numeric literal is now read directly rather than through the exact-value
reader. `Infinity` and `NaN` are identifiers rather than literals, so they are
decided through `isUnmodifiedGlobalReference`; a shadowed binding keeps its
promise, which is the same scoping rule the timer fix uses.
|
||
|
|
6aeaa04e54 | fix(javascript): reuse shared AST child traversal (#7967) | ||
|
|
d5ba7cec60 |
fix(javascript): link a response body reader only to a settled fetch (#8028)
* fix(javascript): link a response body reader only to a settled fetch
`collectResponseConsumers` treated any `.json()`/`.text()`/`.arrayBuffer()` on a
binding holding a request result as a response consumer, and gave it the
request's endpoint at `resolution: "complete"`. That holds only for a settled
`fetch`.
Measured on Node 24.22.1:
await fetch(u) -> Response json/text/arrayBuffer = function
fetch(u) unawaited -> Promise json = undefined; pending.json() THROWS TypeError
http.get(u) -> ClientRequest json/text/arrayBuffer = undefined
http.request(u) -> ClientRequest json = undefined; request.text() THROWS TypeError
new WebSocket(u) -> WebSocket socket.json() THROWS TypeError
await http.get(u) -> ClientRequest (await does not help)
So four shapes emitted a resolved "this endpoint's body is read here" fact for a
call that throws before any body is read: an un-awaited fetch, `http.get`,
`http.request`, and a `WebSocket` handshake.
Link only requests that settle to a `Response`: a `fetch` call whose value is
awaited through expression wrappers. A binding that also holds a request kind
without a body reader keeps the consumer a candidate (`partial`) rather than a
proven one, and a binding with no settled fetch emits no consumer at all, which
matches the `collectReleases` / `forkRejectsStdioArray` precedent — the receiver
provably has no such member, so there is no fact to report.
* chore: retrigger title and readiness checks after the title was corrected
|
||
|
|
703c11c90f |
fix(javascript): recover configuration file reads from fs/promises (#8446)
* fix(javascript): recover configuration file reads from fs/promises * fix(javascript): disallow nonexistent readFileSync on fs/promises --------- Co-authored-by: Tawhidul Islam <276017696+FhDBro@users.noreply.github.com> |
||
|
|
007e181f68 |
fix(javascript): require exactly one fork IPC channel (#8017)
* fix(javascript): require exactly one fork IPC channel
Node's fork() rejects an explicit stdio array that carries more than one IPC
channel, and the throw is synchronous, before any child process exists:
if (ArrayPrototypeFilter(options.stdio, ...'ipc').length > 1)
throw new ERR_IPC_ONE_PIPE()
The analysis already dropped an array with no "ipc" (#7992) but still reported a
resolved fork for an array carrying it twice.
Measured on Node 24.22.1:
fork({stdio: ["ipc", "ipc"]}) -> THROWS ERR_IPC_ONE_PIPE
fork({stdio: ["pipe", "ipc", "ipc"]}) -> THROWS ERR_IPC_ONE_PIPE
fork({stdio: ["ipc"]}) -> SPAWNED connected=true
fork({stdio: ["pipe", "ipc"]}) -> SPAWNED connected=true
fork({stdio: ["pipe", "pipe", "pipe", "ipc"]})-> SPAWNED connected=true
So the required count is exactly one, not at least one. Count the resolved "ipc"
literals and reject the candidate unless the count is one; a spread or an
unreadable element may still change the count and is left alone, as before.
* chore: retrigger title and readiness checks after the title was corrected
|
||
|
|
19082acddb | test(electron): consolidate handler presence coverage in public workflows | ||
|
|
c28d47bf13 | fix(electron): retain wrapped protocol handler registrations | ||
|
|
b912ec184c |
fix(javascript): accept retained empty endpoint seeds (#6462)
Application analysis preserves the complete literal request endpoint,
including the empty string, and the event seed already accepts a
retained empty name. The endpoint seed still required a non-empty
value, so trace-javascript-semantics refused to select a retained ""
endpoint with invalid_request.
The endpoint seed now accepts any string and keeps exact matching.
Boundary-field seeds keep their minimum because boundary nodes are only
produced from method names.
Found while fixing #6458; the event half of that report landed in
|
||
|
|
2d11fd92d2 |
fix(javascript): reconcile Node targets with entry assets (#8670)
* fix(javascript): reconcile Node targets with entry assets * test(inspector): read reconciled evidence payload * Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test(inspector): read the workflow evidence payload * Update tests/boundary/inspector/javascriptRuntimeNodeTarget.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test(javascript): restore Evidence workflow result access --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |
||
|
|
59f5502a40 |
fix(javascript): emit no event operation for a literal non-function listener (#8621)
* fix(javascript): emit no event operation for a literal non-function listener
`EventEmitter` validates a listener before registering or removing anything:
if (typeof listener !== 'function')
throw new ERR_INVALID_ARG_TYPE('listener', 'function', listener);
The throw is synchronous, before any listener exists, but the analysis reported a
`register` or `remove` operation for those calls.
Measured on Node 24.22.1:
emitter.on(1) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", 1) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", null) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", true) -> THREW ERR_INVALID_ARG_TYPE
emitter.on("x", {}) / ([]) -> THREW ERR_INVALID_ARG_TYPE
emitter.once / addListener / prependListener / prependOnceListener /
off / removeListener with a literal -> THREW ERR_INVALID_ARG_TYPE
--- accepted ---
emitter.on("x", () => {}) -> ok
emitter.on("x", function () {}) -> ok
`rejectsEventListener` drops the operation only for an exactly readable literal
that cannot be a function. An identifier, a call result or a spread may still
supply a function and keeps its operation, and a class expression passes Node's
own `typeof` check.
`emit` and `removeAllListeners` are untouched: `emit` passes its remaining
arguments as data to listeners that already exist, and `removeAllListeners` takes
only an optional event name.
* fix(javascript): price a non-finite listener and keep a shifted spread
Two review findings on the event-listener guard.
**A non-finite numeric literal was missed.** Babel stores `1e999` as a
`NumericLiteral` holding `Infinity`, and the exact-value reader refuses to price a
non-finite number, so the guard never saw it:
e.on("x", 1e999) -> THREW ERR_INVALID_ARG_TYPE
A numeric literal is always a number and therefore never callable, so the guard now
decides on the literal node directly.
**A leading spread shifts the listener position.** `e.on(...args, 1)` does not pass
`1` as the listener: Node spreads the array and ignores the trailing argument once
the listener is satisfied. The guard read `arguments[1]` regardless, so it dropped a
real registration. It now leaves the position undecided when argument 0 is a spread.
Five regressions cover both forms; all five fail without the fixes.
|
||
|
|
20a282a181 |
fix(javascript): report SVG script references and skipped bodies (#8627)
Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com> |
||
|
|
60149282fc |
test: strengthen integration oracles and verification (#8295)
* test: strengthen integration oracles and verification * test: bound lifecycle gates and strengthen workflow checks * test: verify complete paged and extracted results * test: preserve bytecode and cancellation evidence * test: strengthen retained evidence and fixture cleanup * test: require cross-projection bridge identities * test: retain fixture owners through cleanup failures * test: verify graph examples and project selectors * test: tighten provider and lifecycle regressions * test: consolidate hex coverage and bound cutter cleanup * test: bound subprocess output waits and strengthen graph checks * test: check mcp recovery and preserve cleanup owners * test: verify structured mcp argument failures * test: preserve artifact cleanup and gdb limit evidence * test: preserve evidence identities and bound provider gates * test: retain browser and comparison cleanup owners * test: bound startup and strengthen fixture oracles * test: bound lifecycle gates and verify recovered evidence * test: verify public outcomes and lifecycle ownership * test: assert precise evidence and release lifecycle gates * test: verify selected facts and bound cleanup waits * test: check retained evidence and decoded records * test: verify final reports and retain cleanup ownership * test: bound reviewed startup gates and retain cleanup * test: verify PE errors and preserve MCP cleanup * test: preserve MCP fixture cleanup ownership * test: release provider gates before late startup |
||
|
|
a989c0e458 |
refactor(evidence): unify retained inputs across domains (#8299)
Declare logical Evidence sources once and resolve exact retained inputs through shared application workflows across 20 composition tools. Preserve domain-owned authentication, source closure, caller choices, coverage and resource admission. Validation includes public CLI/MCP regressions, local Codex terra/high trials and fresh-import real-artifact workflows. Measurements, failed attempts and coverage limits are recorded in docs/verification/evidence-input-composition-2026-10-31.md. Fixes #8293. Fixes #8281. Assisted-by: Suika (gensokyo-skills:suika-scatter-gather-planning) |
||
|
|
47fa6ab379 |
feat(electron): report custom URL protocol registrations
Expose static Electron protocol call facts through the shared application graph and existing CLI/MCP Evidence views. Preserve canonical case-insensitive scheme identity, original spellings, exact source provenance and explicit unknowns; keep registration success and runtime routing unproven. Integration fixes also enforce module-only privileged API ownership, reuse the shared finding-key owner, and align the owning type documentation with the Evidence semantics. Validation and bounded real Obsidian CLI/MCP measurements are recorded in the PR. Co-authored-by: UnknownHacker1 <35694178+UnknownHacker1@users.noreply.github.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> |
||
|
|
e5c46dff03 |
fix(javascript): emit no socket for a literal out-of-range port (#8137)
* fix(javascript): emit no socket for a literal out-of-range port
`net` and `tls` validate a numeric port before creating a socket, so a literal
port Node rejects throws synchronously and no socket exists — but the analysis
reported an `acquire` operation.
Measured on Node 24.22.1:
net.connect(0) / 80 / 65535 -> Socket created
net.connect(65536) -> THREW ERR_SOCKET_BAD_PORT
net.connect(-1) / 1.5 -> THREW ERR_SOCKET_BAD_PORT
net.connect(NaN) / Infinity -> THREW ERR_SOCKET_BAD_PORT
net.connect(1e400) -> THREW ERR_SOCKET_BAD_PORT
net.connect(0n) / true / false -> THREW ERR_INVALID_ARG_TYPE
net.connect(null) -> THREW ERR_INVALID_ARG_TYPE
net.connect("") -> THREW ERR_SOCKET_BAD_PORT
net.connect("/tmp/x.sock") -> Socket created (Unix socket path)
net.connect({ port: 80 }) -> Socket created
net.connect(undefined) / {} / [] -> THREW ERR_MISSING_ARGS (undecided)
--- tls mirrors net ---
tls.connect(80) -> Socket created
tls.connect(65536) / -1 / NaN -> THREW ERR_SOCKET_BAD_PORT
A string is a Unix socket path and an object may carry a path, so only an
exactly readable non-port literal is decided. `undefined` and an empty object
throw `ERR_MISSING_ARGS`, which is a missing option rather than a bad port, so
they stay undecided. `NaN` and `Infinity` are identifiers rather than literals
and are decided only through `isUnmodifiedGlobalReference`.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): place the port check away from the fs flag check
`#8125` adds an fs flag guard at the top of `collectAcquisitions` and this branch
added a socket port guard in the same spot, so the two conflicted on every
rebase. The port check now filters the completed candidate list from
`collectJavaScriptSemanticResources` and its helpers sit at the end of the file,
which cannot collide with the fs flag guard.
* fix(javascript): drop the redundant in-loop port guard
The caller now filters the completed candidate list, so the loop guard became
dead code. Removing it also keeps this branch from editing the same lines as the
fs flag guard in #8125.
|
||
|
|
675d4076de |
test: strengthen regression oracles and cleanup ownership (#8152)
* test: strengthen suite oracles and cleanup ownership * test: reuse setup across independent scenarios (#8153) * Potential fix for pull request finding 'CodeQL / Improper code sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
8f920bb65b |
fix(javascript): emit no http request for a target that is not a URL (#8147)
* fix(javascript): emit no http request for a target that is not a URL
`node:http` parses a string target with `new URL(...)` before a request object
exists, so a literal that is not an absolute URL throws `ERR_INVALID_URL`
synchronously. The analysis reported `endpoint` at `resolution: "complete"` for
those calls, and the graph then projected `constructs-request` as `resolved`.
Measured on Node 24.22.1:
http.get("nope") -> THREW ERR_INVALID_URL
http.get("example.test/api") -> THREW ERR_INVALID_URL
http.get("/api/items") -> THREW ERR_INVALID_URL
http.get("/api/items", { host }) -> THREW ERR_INVALID_URL
http.get("") -> THREW ERR_INVALID_URL
http.get("//example.test/x") -> THREW ERR_INVALID_URL
http.get("http://") -> THREW ERR_INVALID_URL
http.request("nope") -> THREW ERR_INVALID_URL
https.get("/api/items") -> THREW ERR_INVALID_URL
--- accepted ---
http.get("http://example.test/x") -> OK ClientRequest
http.get("HTTP://example.test/x") -> OK ClientRequest
http.get("http:/x") -> OK ClientRequest
http.get("http:///x") -> OK ClientRequest
http.get("http:x") -> OK ClientRequest
`URL.canParse` is the exact predictor of that branch, so a relative spelling such
as `"http:x"` keeps its operation. It already has precedent in this codebase
(`javascriptPackageImportAliases.ts`).
Two boundaries are deliberate. `fetch` and `new WebSocket` are host-supplied
globals: a browser resolves a relative target against the document URL, so their
spellings are not judged by Node's absolute-URL rule. A protocol mismatch such as
`http.get("ftp://...")` is also left alone because an options object can rescue
it, so it is not a provable throw.
The guard is scoped to `method === "get" | "request"`, which only
`requestMethod`'s `http`/`https` builtin branch can produce.
* chore: retrigger title and readiness checks after the title was corrected
|
||
|
|
767f6afc18 |
perf(javascript): index member-effect paths for projected reads (#8108)
* perf(javascript): index member-effect paths for projected reads Each projected read scanned every member-write record of its binding and tested the record path against the selection. On the #7774 reproducer only 3.7% of scanned records pass that test. Index each binding's append-only records by path in a lazily built trie that extends itself with newly appended records, and visit only records whose exact keys a selected choice names or whose keys are wildcards. Candidates come back in record order and still pass the exact predicate, now owned next to the index as semanticEffectPathMatches. The semantic IR and the complete analyze-javascript-application Evidence are byte-identical. On the full reproducer each worker phase drops from about 80 s to 29 s, and to 8 s together with #8103. Refs #7774 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(javascript): stop escape checks at the first indexed match The escape check only asks whether any matching escape is unsuperseded, so it does not need ordered candidates. Share one trie traversal between the ordered write candidates and an early-exit visitor for this check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a47dcca010 |
perf(javascript): keep one member-effect record per stored fact (#8103)
Member-mutation indexing appended a binding write or escape record for every traversal state that reached it. States that differ only in coverage fields the record never stores (origin, owner initializer, getter widening) produced identical records, and each later receiver query rescanned all of them. Record each stored fact once, alongside the existing required-module effect deduplication. Propagation is unchanged. Effect ordering also re-walked the same top-level statement ancestry for every candidate record. Cache it with the other initializer positions, and test the selected path before the costlier receiver comparison. On the #7774 reproducer the semantic IR and the complete analyze-javascript-application Evidence are byte-identical, while the CLI run drops from 146 s to 40 s. Refs #7774 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6b68fb642a |
fix(ci): remove unused exports that fail deep static checks (#8095)
* fix(ci): remove unused exports that fail deep static checks Knip on main reports three analysis-view hub re-exports, two type re-exports, an unused artifact-format guard and an alias constant. Drop the dead re-exports and guard, and give the debugger control-reply budget its own literal: it is a separate budget from the caller command-output default, as its comment already states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(config): read the Rizin control budget from its own constant The Rizin control-reply admission limit still took its value from the caller command-output default, while GDB used DEBUGGER_CONTROL_REPLY_BYTES. Read the dedicated constant in both so the control budget has one owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
81378efb71 |
fix(electron): keep literal IPC sends ambiguous beside unresolved handlers (#8120)
* fix(electron): keep literal IPC sends ambiguous beside unresolved handlers A main handler on an unresolved channel can receive any literal renderer send of a compatible mode, but pairing ignored it unless it had a scoped receiver. The send was then reported unpaired, or paired with another handler. Treat an unresolved-channel handler as a competitor that never pairs. The wrapper pass seeds only calls with a literal first argument, so it now records which invocations of a dynamic site it traced. The syntax finding gains `invocation_channels: "literal"` only when every invocation is a traced direct call of an unexported local function supplying a literal channel. Such a site is already represented by its per-invocation findings and does not compete. Fixes #8118 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): keep guarded wrapper registrations competing in IPC pairing A guarded wrapper registration keeps its predicate on a call path and has no IPC finding. It still counted toward `invocation_channels: "literal"`, so pairing lost it: a send on that channel was reported unpaired, or paired uniquely with a direct handler. The enumerated site now carries these channels as `guarded_invocation_channels`, and each one competes for its own channel without pairing. Index each binding's direct call sites by appending in place. Spreading the earlier calls on every reference was quadratic in a file's call count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
72039ecca9 |
fix(environment): retain conflicting scenario MIME uncertainty (#8082)
* fix(environment): retain conflicting scenario MIME uncertainty * test(mcp): assert structured absolute-path diagnostics * test(mcp): assert typed selector and stdio diagnostics |
||
|
|
9d68aac76d |
fix(javascript): stop reporting self-defaulted receivers as module loads (#8109)
The CommonJS loader search treated any repeated state on its own ancestor chain as an unknown loader. A self-default such as `session.messages = session.messages || []` forms that cycle, because a member read inside the assigned value was attributed to the later call's read site and selected the same write again. Each `push` on the value was then reported as an unresolved `require` load, and graph coverage became partial. Record a member read inside a flowed value as a depth-1 captured selection at its own position, as member mutations already do for flowed origins. Treat a revisited state as adding no source; the cycle's other entries are still expanded once each. Remove the superseded ancestor tracking. Fixes #8102 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
24398a89e4 |
fix(source-maps): resolve empty sourceRoot sources against the map URL (#8107)
webpack 5 and tsc write "sourceRoot": "" for map-relative sources. Since
|
||
|
|
249b90c5e3 |
refactor(javascript): break the semantic analysis import cycle (#8063)
* refactor(javascript): break the semantic analysis import cycle Split shared semantic facts out of the analysis hub so projectors of effects, evaluation, and identity no longer import one another in a cycle. * refactor(javascript): share the import specifier reader Read static imports in one module used by the boundary checker and both cycle guards, so those rules cannot drift apart. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
5c05eb88e5 |
fix(javascript)!: preserve operator influence in semantic traces (#7939)
* fix(javascript)!: preserve operator influence in semantic traces * test(javascript): align semantic influence regressions * style(javascript): format semantic IR * test(javascript): follow literal argument seed influence * fix(javascript): keep one literal node per operator operand Reuse an existing literal for operator influence, and treat TypeScript assertion wrappers as transparent when deciding whether a call result is used. * test(mcp): assert bundle path issues by field compare_bundles reports each relative path as an invalid_format issue instead of one joined diagnostic sentence. * fix(javascript): index operator literals by range and initializer Repeated string operands rescanned every retained literal and every initializer flow. Lookup now uses those indexes, and a 1200-declaration file still projects one literal per operand. * fix(javascript): keep literal and call facts on their own occurrences Operator literals match their own source range, so a nested function does not inherit the enclosing declaration's literal. A direct argument still reaches the call, while the assigned result follows modeled returns. for-loop initializers and updates are discarded call results. * test(javascript): expect conditional operand literals beside binding values A conditional binding keeps one candidate literal per union value and one operator literal per operand token. Those are different facts, so the aggregate payload check counts four nodes and compares literal seeds in query order. * perf(javascript): index call return flows by call site projectCalls filtered every call return for each assigned result. Group those flows once while projecting returns-to-call edges. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
afc18131f4 | fix(ci): reuse shared JavaScript AST child traversal | ||
|
|
54f5cf7e14 |
fix(javascript): retain static facts under projection pressure (#7986)
* fix(javascript): retain static facts under projection pressure * fix(javascript): scope IPC ambiguity to omitted projections * fix(javascript): preserve IPC uncertainty for omitted sources * fix(javascript): qualify IPC pairing by wrapper coverage * fix(javascript): retain provisional IPC wrapper matches |
||
|
|
fca331804a |
fix(javascript): select JSON and addon loaders by exact suffix (#7821)
Node's CommonJS loader matches registered extensions case-sensitively, so
require("./data.JSON") and require("./addon.NODE") run as JavaScript.
Derive target_json_status and the native loads relation from the exact
loader suffix instead of the inventory's case-folded file kind.
Fixes #7623
|
||
|
|
2bfd84b2c8 |
fix(javascript): emit no fork when a literal stdio array omits the IPC channel (#7992)
* fix(javascript): emit no fork when a literal stdio array omits the IPC channel
Node's fork() requires an explicit stdio array to carry the IPC channel:
if (!ArrayPrototypeIncludes(options.stdio, 'ipc'))
throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio')
The throw is synchronous, before any child process exists, but the analysis
reported a resolved fork with stdioMode "array" — byte-identical to the valid
`[..., "ipc"]` form — which projects to a child-process node, a resolved spawns
relation and resolved connects-stdio.
Measured on Node 24.22.1:
fork({stdio: ["inherit","inherit","inherit"]}) -> THROWS ERR_CHILD_PROCESS_IPC_REQUIRED
fork({stdio: ["pipe","pipe","pipe"]}) -> THROWS ERR_CHILD_PROCESS_IPC_REQUIRED
fork({stdio: ["ipc"]}) -> SPAWNED connected=true
fork({stdio: ["inherit","inherit","inherit","ipc"]}) -> SPAWNED connected=true
fork({stdio: "pipe"}) -> SPAWNED connected=true
spawn({stdio: ["inherit","inherit","inherit"]})-> SPAWNED connected=false
A string stdio is normalised with "ipc" appended, so only the array form can omit
it. The same array is valid for spawn, which has no IPC requirement.
Drop a fork candidate only when its stdio array is fully literal and provably
lacks "ipc"; a spread or a non-literal element may still supply the channel and
stays a candidate.
* chore: retrigger title and readiness checks after the title was corrected
|
||
|
|
50b2ab137f |
fix(browser): preserve DOM length units in captures and comparisons (#7971)
* fix(browser): declare DOM node value length units * fix(browser): preserve unknown DOM length comparisons |
||
|
|
63f05d1cad |
fix(javascript): preserve uncertainty for unexamined sources (#7911)
* fix(javascript): preserve uncertainty for unexamined sources * fix(javascript): compare observed package parsing status |
||
|
|
95f0625781 |
fix(javascript): preserve uncertain with-scope member writes (#7914)
* fix(javascript): preserve uncertain with-scope member writes * fix: retain nested initializer candidates * fix(javascript): admit initializer values in worker transport * fix(tests): remove duplicate MCP test imports |