Component Model の borrow 契約を破ってみる

山本悠滋

2026-11-12 Fukuoka.wasm

はじめまして!👋😄

🙇宣伝 wa(_ _)sm

🙇おことわり wa(_ _)sm

以降のスライドは、Claude Code (Opus 5.5) と相談しつつ実験し、生成したものを加筆・修正して使っています。

🎯やりたかったこと

🙋‍♂️先に進む前に…

⚙️構成 1

😥試み1: 失敗(エラーが出ない)

Python 側の Blob.append で self をグローバル変数 escape に保存

def append(self, value: int):
    global escape
    escape = self
    self.contents.append(value)

😥試み2: 失敗(エラーが出ない)

次の呼び出しで escape を使う、

def append_global(self, value: int):
    global escape
    escape.contents.append(value)

😥試み3: 失敗(エラーが出ない)

gc.collect() を呼ぶ

🤔なぜエラーにならないのか

💁仕様上の根拠: lower_borrow

component-model/design/mvp/canonical-abi/definitions.py の lower_borrow:

def lower_borrow(cx, rep, t):
  if cx.inst is t.rt.impl:
    return rep          # 実装者には rep をそのまま渡す
  h = ResourceHandle(
    t.rt, rep, own = False, borrow_scope = cx.borrow_scope
  )
  h.borrow_scope.num_borrows += 1
  return cx.inst.handles.add(h)

💁仕様上の根拠: lower_borrow (続き)

💁componentize-py の実装も同じ(らしい)

componentize-py/runtime/src/lib.rs の push_borrow

👿今度こそ契約を破る

コンポーネント自身で実装していないリソースを borrow<blob> で受け取るコンポーネントが必要

interface processor {
  use blobs.{blob};

  process: func(b: borrow<blob>);   // b をグローバル変数に保存
  later: func() -> list<u8>;        // 保存した b を使う
}

world processor-world {
  import blobs;
  export processor;
}

⚙️構成 2

こちらのコミットより:

⚙️構成 2 (続き)

processor.js:

let escape = null;

export const processor = {
  process(b) {
    escape = b;
    b.append(100);
  },

  later() {
    escape.append(101);
    return escape.getContents();
  },
};

⚙️構成 2 (続き)

processor-host.js:

import { blobs } from "./out-python/python.js";
import { processor } from "./out-processor/processor.js";

const blob = new blobs.Blob([1, 2, 3]);
processor.process(blob);
console.log(blob.getContents());
console.log("----");
console.log(processor.later()); // ← ここでエラー!

💥結果: 契約違反を検出できた

Uint8Array(4) [ 1, 2, 3, 100 ]      ← process() 中は正常
----
TypeError: Resource error: Not a valid "Blob" resource.
    at Blob.append (out-python/python.js)
    ...
    at _trampoline0 (out-processor/processor.js)

🚫「borrow が呼び出しより長生き」のトラップは出ない

📰まとめ