Skip to content

fix: #732 Windows WinHTTP linking + #692 'default' undefined symbol - #734

Merged
proggeramlug merged 1 commit into
mainfrom
fix-linker-732-692
May 13, 2026
Merged

fix: #732 Windows WinHTTP linking + #692 'default' undefined symbol#734
proggeramlug merged 1 commit into
mainfrom
fix-linker-732-692

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Two contained linker fixes reported on GitHub. Both are independent and self-contained; #678 (the third linker-tagged issue) is architectural and left for a separate scoped fix.

#732 — Windows: missing winhttp.lib

--target windows failed with lld-link: error: undefined symbol: __declspec(dllimport) WinHttpOpen (and seven sibling WinHttp* symbols), all referenced from perry_ui_windows::widgets::image::fetch_url_blocking.

Root cause: perry-ui-windows enables the windows crate's Win32_Networking_WinHttp feature, which emits #[link(name = "winhttp")] attrs in the rlib. Those attrs do NOT propagate through perry-ui-windows's staticlib crate-type to perry's final link line — perry invokes lld-link with its own hand-rolled Windows system-library list and winhttp.lib wasn't in that list.

Fix: append winhttp.lib to the Windows arm of crates/perry/src/commands/compile/link.rs's system-libs list.

#692undefined reference to 'default'

A literal symbol named default at link time. Reproducer is a single-file TS importing a default export from a module Perry can't resolve as native:

import sanitizeHtml from "sanitize-html";
sanitizeHtml("<b>hi</b>");

Root cause chain:

  1. HIR's register_imported_func (lower.rs:3727) registers default-import bindings under the local name with the original-name sentinel "default".
  2. Identifier lookup (lower.rs:7175) resolves the local to Expr::ExternFuncRef { name: "default" }.
  3. Codegen's call path (lower_call.rs:735+) falls through every special-case (setTimeout/jsx/perry-system/js_* builtin) and the import_function_prefixes map (which only contains NativeCompiled imports, NOT V8-fallback JsModule imports — so sanitize-html has no entry).
  4. The fallthrough at lower_call.rs:848+ emits call double @default(...) and the system linker has no such symbol → undefined reference to 'default'.

Fix: before the native-library fallthrough, detect name == "default" with no import_function_prefixes entry — the only producer of bare-"default" ExternFuncRefs is the default-import-from-unresolved-module path — and route to a new runtime stub js_unresolved_default_call (crates/perry-runtime/src/object.rs) that returns NaN-boxed TAG_UNDEFINED and prints a one-shot diagnostic on first call. Args are still lowered for side effects (string interning, closure collection).

After the fix, the minimal repro links and runs:

perry: called a default-imported binding from an unresolved module (returns undefined). The module's default export was not found in perry-stdlib or perry.compilePackages — run `perry --print-api-manifest` to see what's supported.
result: undefined

Not in this PR — #678

_perry_fn_..._render unresolved symbols for V8-fallback modules referenced from native modules are architectural: codegen either needs to route through js_call_function when the callee module is V8-fallback (requires threading ModuleKind to codegen) or emit weak stub symbols for V8-fallback module exports (requires threading the JsModule export surface). Tracked separately on the issue.

Test plan

  • cargo build --release -p perry-runtime -p perry-codegen -p perry — clean
  • Pre-fix repro (import sanitizeHtml from "sanitize-html") → linker error reproduces locally on Windows
  • Post-fix repro → links, runs, prints diagnostic + result: undefined
  • CI green (lint, cargo-test, parity, compile-smoke, api-docs-drift, security-audit)
  • Manual: external user with --target windows confirms WinHTTP linkage

…v0.5.892)

Two contained linker fixes reported on GitHub:

- #732 (Windows): `--target windows` failed with `lld-link: undefined
  symbol: WinHttpOpen` (and seven sibling WinHttp* symbols) referenced
  from `perry_ui_windows::widgets::image::fetch_url_blocking`. The
  `windows` crate's `Win32_Networking_WinHttp` feature emits `#[link]`
  attrs in the rlib but they don't propagate through perry-ui-windows's
  staticlib crate-type. Added `winhttp.lib` to the Windows arm of
  link.rs's system-libs list.

- #692 (Linux/macOS): `undefined reference to 'default'` at link time
  when a default import resolves to a module Perry can't compile
  natively (no perry-stdlib binding, not in compilePackages). HIR
  registers the binding with sentinel `name = "default"`; codegen's
  ExternFuncRef call path fell through to the native-library extern
  fallback and emitted `call double @default(...)`. Routed bare-name
  `"default"` ExternFuncRef calls (no import_function_prefixes entry)
  to a new runtime stub `js_unresolved_default_call` that returns
  undefined with a one-shot diagnostic. Args still lowered for side
  effects.

#678 (V8-fallback `_perry_fn_..._render` unresolved symbols) is
architectural and tracked separately — it needs either V8-bridge
routing or weak stub emission for JsModule exports.
@proggeramlug
proggeramlug merged commit 53c17c1 into main May 13, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the fix-linker-732-692 branch May 13, 2026 04:17
proggeramlug added a commit that referenced this pull request May 13, 2026
Bundles seven green dependabot bumps into a single commit instead of
seven round-trips through the PR queue.

Renumbered from v0.5.892 to v0.5.893 after rebasing on main — #734
landed v0.5.892 while this batch was in review.

Cargo crates:
- scraper 0.19.1 → 0.27.0 (#731) — perry-ext-cheerio, perry-stdlib
- gstreamer 0.23.7 → 0.25.2 (#730) — perry-ui-gtk4
- bcrypt 0.15 → 0.17 (#728) — perry-stdlib (aligns with perry-ext-bcrypt
  which already pinned 0.17, so the workspace now resolves a single
  bcrypt version)

GitHub Actions:
- actions/download-artifact v4 → v8 (#724)
- actions/setup-node v4 → v6 (#725)
- actions/cache v4 → v5 (#726)
  (test.yml line 110's comment referencing v4 is left as-is — it is
  documenting historical state, not configuring a step.)

NPM test fixture:
- drizzle-orm ^0.36.0 → ^0.45.2 (#723) in
  tests/release/packages/drizzle-sqlite/

PR #723 had a stale security-audit FAILURE from a transient advisory-db
hit on May 12; main's subsequent run against the unchanged Cargo.lock
passed on May 13. The PR only touches npm test fixtures so the audit
result was unrelated to its diff.

Skipped (real breakage, not safe — being addressed separately):
- toml 0.8.23 → 1.1.2+spec-1.1.0 (#727) — major version breaks
  well_known_bindings.toml parsing; 7 cargo tests + 31 compile-smoke
  failures.
- deno_core 0.311.0 → 0.400.0 (#729) — 89-patch jump, parity +
  compile-smoke failures from API-breaking changes.
proggeramlug added a commit that referenced this pull request May 13, 2026
Bundles seven green dependabot bumps into a single commit instead of
seven round-trips through the PR queue.

Cargo crates:
- scraper 0.19.1 → 0.27.0 (#731) — perry-ext-cheerio, perry-stdlib
- gstreamer 0.23.7 → 0.25.2 (#730) — perry-ui-gtk4
- bcrypt 0.15 → 0.17 (#728) — perry-stdlib (aligns with perry-ext-bcrypt
  which already pinned 0.17, so the workspace now resolves a single
  bcrypt version)

GitHub Actions:
- actions/download-artifact v4 → v8 (#724)
- actions/setup-node v4 → v6 (#725)
- actions/cache v4 → v5 (#726)
  (test.yml line 110's comment referencing v4 is left as-is — it is
  documenting historical state, not configuring a step.)

NPM test fixture:
- drizzle-orm ^0.36.0 → ^0.45.2 (#723) in
  tests/release/packages/drizzle-sqlite/

PR #723 had a stale security-audit FAILURE from a transient advisory-db
hit on May 12; main's subsequent run against the unchanged Cargo.lock
passed on May 13. The PR only touches npm test fixtures so the audit
result was unrelated to its diff.

Skipped (real breakage, not safe):
- toml 0.8.23 → 1.1.2+spec-1.1.0 (#727) — major version breaks
  well_known_bindings.toml parsing; 7 cargo tests + 31 compile-smoke
  failures.
- deno_core 0.311.0 → 0.400.0 (#729) — 89-patch jump, parity +
  compile-smoke failures from API-breaking changes.

Rebased onto main at e8c4a95 (HarmonyOS Chart + TreeView shipped as
v0.5.893 after #734 shipped as v0.5.892); this commit moves the batch
roll-up to v0.5.894 to avoid the patch-version collision.
proggeramlug added a commit that referenced this pull request May 13, 2026
Bundles seven green dependabot bumps into a single commit instead of
seven round-trips through the PR queue.

Cargo crates:
- scraper 0.19.1 → 0.27.0 (#731) — perry-ext-cheerio, perry-stdlib
- gstreamer 0.23.7 → 0.25.2 (#730) — perry-ui-gtk4
- bcrypt 0.15 → 0.17 (#728) — perry-stdlib (aligns with perry-ext-bcrypt
  which already pinned 0.17, so the workspace now resolves a single
  bcrypt version)

GitHub Actions:
- actions/download-artifact v4 → v8 (#724)
- actions/setup-node v4 → v6 (#725)
- actions/cache v4 → v5 (#726)
  (test.yml line 110's comment referencing v4 is left as-is — it is
  documenting historical state, not configuring a step.)

NPM test fixture:
- drizzle-orm ^0.36.0 → ^0.45.2 (#723) in
  tests/release/packages/drizzle-sqlite/

PR #723 had a stale security-audit FAILURE from a transient advisory-db
hit on May 12; main's subsequent run against the unchanged Cargo.lock
passed on May 13. The PR only touches npm test fixtures so the audit
result was unrelated to its diff.

Skipped (real breakage, not safe):
- toml 0.8.23 → 1.1.2+spec-1.1.0 (#727) — major version breaks
  well_known_bindings.toml parsing; 7 cargo tests + 31 compile-smoke
  failures.
- deno_core 0.311.0 → 0.400.0 (#729) — 89-patch jump, parity +
  compile-smoke failures from API-breaking changes.

Rebased onto main at e8c4a95 (HarmonyOS Chart + TreeView shipped as
v0.5.893 after #734 shipped as v0.5.892); this commit moves the batch
roll-up to v0.5.894 to avoid the patch-version collision.
proggeramlug added a commit that referenced this pull request May 13, 2026
…g (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.
proggeramlug added a commit that referenced this pull request May 13, 2026
…g (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.
proggeramlug added a commit that referenced this pull request May 13, 2026
…er-flexible work end-to-end (#735)

* feat(resolve): #665 — compilePackages overrides NATIVE_MODULES routing (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.

* fix(codegen): #665 — default-import collision no longer mints phantom cross-class method symbols (v0.5.896)

When two different classes are both default-imported in the same file,
the `class_ids` / `imported_class_prefix` / `imported_class_source_name`
side maps in `compile_module` overwrote each other (last-writer-wins)
while `class_table` kept the first stub (first-writer-wins). The
mismatch produced method symbols mangled with FIRST stub's method list
+ LAST writer's class name + LAST writer's source prefix — names the
linker can't resolve since Phase F only declares each ic's REAL
methods. Flip the three side-map `.insert()` calls to `.entry().or_insert()`
so all four maps agree on first-writer-wins.

Fixes the `@perry_method_..._RateLimiterRes_js__RateLimiterRes__delete`
/ `__get` / `__set` / `__block` undefined-value errors that surfaced
in rate-limiter-flexible's 5 leaf files once v0.5.895's routing opt-in
let them reach codegen.

Refs #665. See CHANGELOG.md for full root-cause walkthrough.

* fix(codegen,runtime): #665 — setTimeout(fn, delay, ...args) forwards trailing args (v0.5.897)

Existing `"setTimeout" if args.len() == 2` arm only handled the 2-arg
shape; 3+ arg call sites (e.g. `setTimeout(resolve, delay, res)` inside
Promise executors per ECMA-262 §27.5.4.1) fell through to the generic
ExternFuncRef fallthrough and emitted `call @settimeout(...)` against a
symbol nothing in stdlib defines. Discovering call site:
`RateLimiterMemory.consume()`'s Promise executor in rate-limiter-flexible.

Adds:
- `CallbackTimer::args: Vec<f64>` (empty for 2-arg shape — back-compat).
- `js_set_timeout_callback_args(callback, delay_ms, args_ptr, n_args)`
  runtime entry that copies the buffer in.
- `js_callback_timer_tick` dispatch via `match args.len()` to
  `js_closure_call0..js_closure_call9`, clamping at 9 trailing args.
- Codegen `"setTimeout" if args.len() >= 3` arm: alloca a stack buffer
  of doubles for the trailing args, GEP for the base, call the new
  runtime entry.

Refs #665. See CHANGELOG.md for full root-cause + scope notes.

* fix(hir): #665 — compilePackages-opted-in packages skip native-instance lowering (v0.5.898)

HIR's `is_native_module` only consulted the NATIVE_MODULES manifest, so
`rate-limiter-flexible` was lowered as a native import regardless of the
user's `compilePackages` opt-in. That cascaded:

  register_native_module(local, "rate-limiter-flexible", ...)
    → new RateLimiterMemory(...) triggers
  register_native_instance(limiter, "rate-limiter-flexible", "RateLimiterMemory")
    → limiter.consume in expr_member::lower_member emits
  NativeMethodCall { module, object: Some(limiter), method: "consume", args: [] }
    → codegen routes to js_native_call_method → no FFI entry → 0.0

So `typeof limiter.consume === "number"`. For genuine native bindings
(req.method on http.IncomingMessage) this zero-arg-getter lowering is
correct; for compile-package-overridden classes it's wrong because
`consume` is a real method, not a getter.

Fix: add a thread-local `COMPILE_PACKAGES_OVERRIDE: HashSet<String>` in
perry-hir, set by the compiler driver via
`set_compile_packages_override(ctx.compile_packages.clone())` before
each `lower_module_full` invocation and cleared after.
`is_native_module` parses the package name (supports `node:` prefix +
`@scope/pkg` scoped form) and returns false when the package is in the
override even if it appears in NATIVE_MODULES.

Validation: rate-limiter-flexible probe now reports `typeof
limiter.consume: function` (was "number"). Workspace tests green.

Refs #665. See CHANGELOG.md for full root-cause walkthrough.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant