|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Regression: reading a native-module callable export AS A VALUE |
| 3 | +# (`const f = util.inherits`) and invoking it indirectly must dispatch to the |
| 4 | +# real runtime impl. The indirect call resolves through the per-module |
| 5 | +# NM_DISPATCH_REGISTRY, which is populated by `js_nm_install_<module>()`. The |
| 6 | +# *direct* call form (`util.inherits(a, b)`) is statically lowered straight to |
| 7 | +# the runtime extern and never touches that registry, so a module reached ONLY |
| 8 | +# through the value-read path used to leave the registry empty — the indirect |
| 9 | +# call silently resolved to `undefined`. |
| 10 | +# |
| 11 | +# Concretely: winston's `class Logger extends Transform` (readable-stream) |
| 12 | +# relies on `require('inherits')(Transform, Duplex)` — an indirect |
| 13 | +# `util.inherits` value-call — to wire the ES5 super-chain so the nested |
| 14 | +# `Readable.call(this)` `if (!(this instanceof Readable))` guard takes the |
| 15 | +# in-place branch and sets `this._readableState`. With the install skipped, |
| 16 | +# the guard saw `false`, returned a discarded `new Readable()`, and |
| 17 | +# `this._readableState.needReadable = true` threw on `null`. |
| 18 | +# |
| 19 | +# This test pins both halves: |
| 20 | +# 1. RUNTIME: the value-read `inherits(Sub, Base)` registers the ES5 parent |
| 21 | +# edge so `new Sub() instanceof Base` is true and base ctor writes persist. |
| 22 | +# 2. CODEGEN: the `PropertyGet { NativeModuleRef("util"), "inherits" }` |
| 23 | +# value-read emits `call void @js_nm_install_util()`. |
| 24 | +set -euo pipefail |
| 25 | + |
| 26 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 27 | +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 28 | +PERRY="${PERRY_BIN:-${PERRY:-$REPO_ROOT/target/release/perry}}" |
| 29 | + |
| 30 | +if [[ ! -x "$PERRY" ]]; then |
| 31 | + PERRY="$REPO_ROOT/target/debug/perry" |
| 32 | +fi |
| 33 | +if [[ ! -x "$PERRY" ]]; then |
| 34 | + echo "SKIP: perry binary not found (build with cargo build -p perry)" |
| 35 | + exit 0 |
| 36 | +fi |
| 37 | + |
| 38 | +TMPDIR="$(mktemp -d)" |
| 39 | +trap 'rm -rf "$TMPDIR"' EXIT |
| 40 | + |
| 41 | +SRC="$TMPDIR/nm_value_read_install.ts" |
| 42 | +BIN="$TMPDIR/nm_value_read_install" |
| 43 | +OBJ="$TMPDIR/nm_value_read_install.o" |
| 44 | + |
| 45 | +cat >"$SRC" <<'TS' |
| 46 | +import * as util from "util"; |
| 47 | +
|
| 48 | +// Read the native-module callable export AS A VALUE, then invoke indirectly. |
| 49 | +const inh: any = (util as any).inherits; |
| 50 | +
|
| 51 | +function Base(this: any) { |
| 52 | + if (!(this instanceof Base)) return new (Base as any)(); |
| 53 | + this._state = { x: 1 }; |
| 54 | +} |
| 55 | +function Sub(this: any) { |
| 56 | + (Base as any).call(this); |
| 57 | + this._state.y = 2; // mirrors readable-stream's post-super `this._x` writes |
| 58 | +} |
| 59 | +inh(Sub, Base); |
| 60 | +
|
| 61 | +const s: any = new (Sub as any)(); |
| 62 | +
|
| 63 | +let failures = 0; |
| 64 | +if (!(s instanceof Base)) { |
| 65 | + console.log("FAIL: value-read util.inherits did not register the ES5 parent edge"); |
| 66 | + failures = failures + 1; |
| 67 | +} |
| 68 | +if (!s._state || s._state.x !== 1 || s._state.y !== 2) { |
| 69 | + console.log("FAIL: base ctor writes did not persist through the super-chain"); |
| 70 | + failures = failures + 1; |
| 71 | +} |
| 72 | +
|
| 73 | +if (failures !== 0) { |
| 74 | + throw new Error("native-module value-read inherits regression failed"); |
| 75 | +} |
| 76 | +console.log("nm value-read inherits ok"); |
| 77 | +TS |
| 78 | + |
| 79 | +"$PERRY" compile --no-cache --no-auto-optimize "$SRC" -o "$BIN" >"$TMPDIR/compile.log" 2>&1 || { |
| 80 | + echo "FAIL: compile failed" |
| 81 | + sed 's/^/ /' "$TMPDIR/compile.log" | tail -80 |
| 82 | + exit 1 |
| 83 | +} |
| 84 | + |
| 85 | +"$BIN" >"$TMPDIR/run.log" 2>&1 || { |
| 86 | + echo "FAIL: program failed" |
| 87 | + sed 's/^/ /' "$TMPDIR/run.log" | tail -80 |
| 88 | + exit 1 |
| 89 | +} |
| 90 | + |
| 91 | +if ! grep -q "nm value-read inherits ok" "$TMPDIR/run.log"; then |
| 92 | + echo "FAIL: expected success marker" |
| 93 | + sed 's/^/ /' "$TMPDIR/run.log" | tail -80 |
| 94 | + exit 1 |
| 95 | +fi |
| 96 | + |
| 97 | +( |
| 98 | + cd "$TMPDIR" |
| 99 | + "$PERRY" compile --no-cache --no-auto-optimize --trace llvm --no-link \ |
| 100 | + "$SRC" -o "$OBJ" >"$TMPDIR/trace-compile.log" 2>&1 |
| 101 | +) || { |
| 102 | + echo "FAIL: trace compile failed" |
| 103 | + sed 's/^/ /' "$TMPDIR/trace-compile.log" | tail -80 |
| 104 | + exit 1 |
| 105 | +} |
| 106 | + |
| 107 | +TRACE_DIR="$TMPDIR/.perry-trace/llvm" |
| 108 | +if [[ ! -d "$TRACE_DIR" ]]; then |
| 109 | + echo "FAIL: LLVM trace directory not found" |
| 110 | + exit 1 |
| 111 | +fi |
| 112 | + |
| 113 | +if ! grep -R "call void @js_nm_install_util()" "$TRACE_DIR" >/dev/null; then |
| 114 | + echo "FAIL: expected js_nm_install_util() call on the native-module value-read path" |
| 115 | + exit 1 |
| 116 | +fi |
| 117 | + |
| 118 | +echo "PASS: native-module value-read install codegen" |
0 commit comments