-
-
Notifications
You must be signed in to change notification settings - Fork 132
fix: native-compile winston (ES5 .call(this) super-chain → 59/59 corpus) #5368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| #!/usr/bin/env bash | ||
| # Regression: a getter installed via `Object.defineProperty(Class.prototype, | ||
| # name, { get })` must run with `this` bound to the INSTANCE, not the prototype | ||
| # object it lives on. | ||
| # | ||
| # Such a getter is an ordinary method closure whose body reads `this` from its | ||
| # captured receiver slot (not IMPLICIT_THIS). The inherited-accessor walk used | ||
| # to merely set IMPLICIT_THIS and call the closure, so the getter observed the | ||
| # prototype — `this.<ownField>` came back undefined. winston's | ||
| # `Object.defineProperty(Logger.prototype, 'transports', { get() { const { | ||
| # pipes } = this._readableState; … } })` (read as `this.transports` inside the | ||
| # Logger constructor) then threw "Cannot convert undefined or null to object". | ||
| # | ||
| # Fix: the inherited prototype-accessor path routes through | ||
| # `invoke_accessor_getter`, which clones the getter closure with `this` rebound | ||
| # to the real receiver (matching the own-accessor read path). | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | ||
| REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
| PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" | ||
|
|
||
| if [[ ! -x "$PERRY" ]]; then | ||
| PERRY="$REPO_ROOT/target/debug/perry" | ||
| fi | ||
| if [[ ! -x "$PERRY" ]]; then | ||
| echo "SKIP: perry binary not found (build with cargo build -p perry)" | ||
| exit 0 | ||
| fi | ||
|
|
||
| TMPDIR="$(mktemp -d)" | ||
| trap 'rm -rf "$TMPDIR"' EXIT | ||
|
|
||
| SRC="$TMPDIR/defineproperty_proto_getter.ts" | ||
| BIN="$TMPDIR/defineproperty_proto_getter" | ||
|
|
||
| cat >"$SRC" <<'TS' | ||
| let failures = 0; | ||
|
|
||
| // Plain class: getter reads an own data field through `this`. | ||
| class A { | ||
| _x = 42; | ||
| } | ||
| Object.defineProperty(A.prototype, "px", { | ||
| get() { return (this as any)._x; }, | ||
| }); | ||
| const a: any = new A(); | ||
| if (a.px !== 42) { | ||
| console.log("FAIL: defineProperty prototype getter read prototype, not instance (px=" + a.px + ")"); | ||
| failures = failures + 1; | ||
| } | ||
|
|
||
| // Getter that DESTRUCTURES a nested own object — the exact winston shape. | ||
| class L { | ||
| _readableState: any; | ||
| constructor() { this._readableState = { pipes: null }; } | ||
| } | ||
| Object.defineProperty(L.prototype, "transports", { | ||
| configurable: false, | ||
| enumerable: true, | ||
| get() { | ||
| const { pipes } = (this as any)._readableState; | ||
| return !Array.isArray(pipes) ? [pipes].filter(Boolean) : pipes; | ||
| }, | ||
| }); | ||
| const l: any = new L(); | ||
| let threw = false; | ||
| let result: any = "unset"; | ||
| try { | ||
| result = l.transports; | ||
| } catch (e) { | ||
| threw = true; | ||
| } | ||
| if (threw) { | ||
| console.log("FAIL: destructuring getter threw (this bound to prototype, _readableState undefined)"); | ||
| failures = failures + 1; | ||
| } else if (JSON.stringify(result) !== "[]") { | ||
| console.log("FAIL: destructuring getter wrong result: " + JSON.stringify(result)); | ||
| failures = failures + 1; | ||
| } | ||
|
|
||
| if (failures !== 0) { | ||
| throw new Error("defineProperty prototype getter this-binding regression failed"); | ||
| } | ||
| console.log("defineProperty proto getter this ok"); | ||
| TS | ||
|
|
||
| "$PERRY" compile --no-cache --no-auto-optimize "$SRC" -o "$BIN" >"$TMPDIR/compile.log" 2>&1 || { | ||
| echo "FAIL: compile failed" | ||
| sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 | ||
| exit 1 | ||
| } | ||
|
|
||
| "$BIN" >"$TMPDIR/run.log" 2>&1 || { | ||
| echo "FAIL: program failed" | ||
| sed 's/^/ /' "$TMPDIR/run.log" | tail -80 | ||
| exit 1 | ||
| } | ||
|
|
||
| if ! grep -q "defineProperty proto getter this ok" "$TMPDIR/run.log"; then | ||
| echo "FAIL: expected success marker" | ||
| sed 's/^/ /' "$TMPDIR/run.log" | tail -80 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "PASS: defineProperty prototype getter this-binding" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #!/usr/bin/env bash | ||
| # Regression: reading a native-module callable export AS A VALUE | ||
| # (`const f = util.inherits`) and invoking it indirectly must dispatch to the | ||
| # real runtime impl. The indirect call resolves through the per-module | ||
| # NM_DISPATCH_REGISTRY, which is populated by `js_nm_install_<module>()`. The | ||
| # *direct* call form (`util.inherits(a, b)`) is statically lowered straight to | ||
| # the runtime extern and never touches that registry, so a module reached ONLY | ||
| # through the value-read path used to leave the registry empty — the indirect | ||
| # call silently resolved to `undefined`. | ||
| # | ||
| # Concretely: winston's `class Logger extends Transform` (readable-stream) | ||
| # relies on `require('inherits')(Transform, Duplex)` — an indirect | ||
| # `util.inherits` value-call — to wire the ES5 super-chain so the nested | ||
| # `Readable.call(this)` `if (!(this instanceof Readable))` guard takes the | ||
| # in-place branch and sets `this._readableState`. With the install skipped, | ||
| # the guard saw `false`, returned a discarded `new Readable()`, and | ||
| # `this._readableState.needReadable = true` threw on `null`. | ||
| # | ||
| # This test pins both halves: | ||
| # 1. RUNTIME: the value-read `inherits(Sub, Base)` registers the ES5 parent | ||
| # edge so `new Sub() instanceof Base` is true and base ctor writes persist. | ||
| # 2. CODEGEN: the `PropertyGet { NativeModuleRef("util"), "inherits" }` | ||
| # value-read emits `call void @js_nm_install_util()`. | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | ||
| REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
| PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" | ||
|
|
||
| if [[ ! -x "$PERRY" ]]; then | ||
| PERRY="$REPO_ROOT/target/debug/perry" | ||
| fi | ||
| if [[ ! -x "$PERRY" ]]; then | ||
| echo "SKIP: perry binary not found (build with cargo build -p perry)" | ||
| exit 0 | ||
| fi | ||
|
|
||
| TMPDIR="$(mktemp -d)" | ||
| trap 'rm -rf "$TMPDIR"' EXIT | ||
|
|
||
| SRC="$TMPDIR/nm_value_read_install.ts" | ||
| BIN="$TMPDIR/nm_value_read_install" | ||
| OBJ="$TMPDIR/nm_value_read_install.o" | ||
|
|
||
| cat >"$SRC" <<'TS' | ||
| import * as util from "util"; | ||
|
|
||
| // Read the native-module callable export AS A VALUE, then invoke indirectly. | ||
| const inh: any = (util as any).inherits; | ||
|
|
||
| function Base(this: any) { | ||
| if (!(this instanceof Base)) return new (Base as any)(); | ||
| this._state = { x: 1 }; | ||
| } | ||
| function Sub(this: any) { | ||
| (Base as any).call(this); | ||
| this._state.y = 2; // mirrors readable-stream's post-super `this._x` writes | ||
| } | ||
| inh(Sub, Base); | ||
|
|
||
| const s: any = new (Sub as any)(); | ||
|
|
||
| let failures = 0; | ||
| if (!(s instanceof Base)) { | ||
| console.log("FAIL: value-read util.inherits did not register the ES5 parent edge"); | ||
| failures = failures + 1; | ||
| } | ||
| if (!s._state || s._state.x !== 1 || s._state.y !== 2) { | ||
| console.log("FAIL: base ctor writes did not persist through the super-chain"); | ||
| failures = failures + 1; | ||
| } | ||
|
|
||
| if (failures !== 0) { | ||
| throw new Error("native-module value-read inherits regression failed"); | ||
| } | ||
| console.log("nm value-read inherits ok"); | ||
| TS | ||
|
|
||
| "$PERRY" compile --no-cache --no-auto-optimize "$SRC" -o "$BIN" >"$TMPDIR/compile.log" 2>&1 || { | ||
| echo "FAIL: compile failed" | ||
| sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 | ||
| exit 1 | ||
| } | ||
|
|
||
| "$BIN" >"$TMPDIR/run.log" 2>&1 || { | ||
| echo "FAIL: program failed" | ||
| sed 's/^/ /' "$TMPDIR/run.log" | tail -80 | ||
| exit 1 | ||
| } | ||
|
|
||
| if ! grep -q "nm value-read inherits ok" "$TMPDIR/run.log"; then | ||
| echo "FAIL: expected success marker" | ||
| sed 's/^/ /' "$TMPDIR/run.log" | tail -80 | ||
| exit 1 | ||
| fi | ||
|
|
||
| ( | ||
| cd "$TMPDIR" | ||
| "$PERRY" compile --no-cache --no-auto-optimize --trace llvm --no-link \ | ||
| "$SRC" -o "$OBJ" >"$TMPDIR/trace-compile.log" 2>&1 | ||
| ) || { | ||
| echo "FAIL: trace compile failed" | ||
| sed 's/^/ /' "$TMPDIR/trace-compile.log" | tail -80 | ||
| exit 1 | ||
| } | ||
|
|
||
| TRACE_DIR="$TMPDIR/.perry-trace/llvm" | ||
| if [[ ! -d "$TRACE_DIR" ]]; then | ||
| echo "FAIL: LLVM trace directory not found" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! grep -R "call void @js_nm_install_util()" "$TRACE_DIR" >/dev/null; then | ||
| echo "FAIL: expected js_nm_install_util() call on the native-module value-read path" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "PASS: native-module value-read install codegen" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 323
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 2099
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 3198
Use the local
TAG_UNDEFINEDconstant on line 160 for consistency.Line 160 uses
crate::value::TAG_UNDEFINEDwhile the rest of this file uses the localTAG_UNDEFINEDconstant defined at line 60. Both constants have the identical value (0x7FFC_0000_0000_0001), but the inconsistency should be resolved. Since this file already defines and uses its ownTAG_UNDEFINEDthroughout (e.g., lines 216, 418, 456), line 160 should use that same local constant for consistency.🤖 Prompt for AI Agents