dotnet/runtime · error · Error

Implicit conversion of roots to pointers is no longer suppor

Error message

Implicit conversion of roots to pointers is no longer supported. Use .value or .address as appropriate

What it means

Thrown by WasmJsOwnedRoot.valueOf() to prevent implicit coercion of a scratch root into a primitive. Older code relied on valueOf to treat a root as a raw pointer; this was removed because it silently hid the distinction between a managed pointer (.value) and the address slot (.address). Any operation that triggers JS valueOf (arithmetic, string template, ==) on a scratch root now fails loudly.

Source

Thrown at src/mono/browser/runtime/roots.ts:315

        const destinationAddress = this.address;
        cwraps.mono_wasm_copy_managed_pointer(destinationAddress, source);
    }

    copy_to_address (destination: MonoObjectRef): void {
        const sourceAddress = this.address;
        cwraps.mono_wasm_copy_managed_pointer(destination, sourceAddress);
    }

    get value (): T {
        return this.get();
    }

    set value (value: T) {
        this.set(value);
    }

    valueOf (): T {
        throw new Error("Implicit conversion of roots to pointers is no longer supported. Use .value or .address as appropriate");
    }

    clear (): void {
        // .set performs an expensive write barrier, and that is not necessary in most cases
        //  for clear since clearing a root cannot cause new objects to survive a GC
        const address32 = this.__buffer.get_address_32(this.__index);
        localHeapViewU32()[address32] = 0;
    }

    release (): void {
        if (!this.__buffer)
            throw new Error("No buffer");

        const maxPooledInstances = 128;
        if (_scratch_root_free_instances.length > maxPooledInstances) {
            _mono_wasm_release_scratch_index(this.__index);
            (<any>this).__buffer = null;
            this.__index = 0;

View on GitHub (pinned to 60108ba66e)

Solutions

  1. Replace implicit usage with root.value when you need the managed pointer, or root.address when you need the slot address.
  2. For null-pointer checks use (root.value === 0) or root.get() === 0 instead of root == 0.
  3. In template strings, interpolate root.value explicitly: `${root.value}`.
  4. Run a grep for arithmetic and template usage of root variables to catch every site.

Example fix

// before
if (root == 0) { ... }
const s = `ptr ${root}`;
// after
if (root.value === 0) { ... }
const s = `ptr ${root.value}`;
Defensive patterns

Strategy: validation

Validate before calling

// Lint-time guard: never use a root where a number/string is expected.
const v: number = root.value;       // managed pointer
const a: MonoObjectRef = root.address; // slot address

Type guard

function isWasmRoot<T>(r: unknown): r is WasmRoot<T> {
  return !!r && typeof r === 'object' && 'value' in r && 'address' in r;
}

Prevention

When it happens

Trigger: Writing root + 1, `${root}`, root == 0, or any expression that forces JS to call valueOf() on a WasmJsOwnedRoot instance. Also triggered by passing the root where a number is expected by an untyped API.

Common situations: Legacy interop code from before the valueOf removal; migrating code that treated roots as pointers; debugging statements like console.log(`ptr=${root}`); using == against 0 to test for null pointers.

Related errors


AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10). Data as JSON: /api/errors/752af34e320e5854. Report an issue: GitHub.