Rustライブラリ「lol_html」のNode.jsラッパーである「html-rewriter-wasm」を使用したところ、以下のエラーが出ました。
recursive use of an object detected which would lead to unsafe aliasing in rust
今までは「JSDOM」を使用していましたが、より高速なパーサーを探してRust系のライブラリに行き着きました。
Rustの開発経験は全くなく、Rust特有の厳格なメモリ管理にも慣れておりません。
recursive useとは、JavaScriptからRustを呼び出して、RustからJSコールバック関数が呼び出されることを指すのでしょうか?
このラッパーがそういう仕様なんですけど…
JavaScript
1const encoder = new TextEncoder(); 2const rewriter = new HTMLRewriter(); 3rewriter.on('a[href]', { 4 element(element) { 5 console.log('[href]', element.getAttribute('href')); 6 }, 7}); 8await rewriter.write(encoder.encode(html)); 9await rewriter.end();
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
自己解決してよかったです。
気づかずに回答を下書きしてしまったので、そのまま貼り付けますね。
結論から言うと、html-rewriter-wasmのREADMEを参考に、HTMLRewriter
をnew
する際に引数を渡すようにしたところ解決しました。試してみてください。
変更前
javascript
1const rewriter = new HTMLRewriter();
変更後
javascript
1const decoder = new TextDecoder(); 2 3let output = ""; 4const rewriter = new HTMLRewriter((outputChunk) => { 5 output += decoder.decode(outputChunk); 6});
詳細
実行時にunreachableエラーが発生
まず、ご質問のコードの足りないところ(以下)を追加してから実行したところ、質問とは異なるエラーになりました。
javascript
1// 先頭に追加 2import { HTMLRewriter } from "html-rewriter-wasm"; 3 4const html = "<a href='https://example.com'>Example</a>";
実行結果
console
1$ node index.js 2[href] https://example.com 3wasm://wasm/0038a716:1 4 5RuntimeError: unreachable 6 at wasm://wasm/0038a716:wasm-function[511]:0xa1fff 7 at wasm://wasm/0038a716:wasm-function[578]:0xa95a1 8 at wasm://wasm/0038a716:wasm-function[709]:0xb28b1 9 at wasm://wasm/0038a716:wasm-function[684]:0xb1376 10 at wasm://wasm/0038a716:wasm-function[710]:0xb2994 11 at wasm://wasm/0038a716:wasm-function[596]:0xaaf72 12 at wasm://wasm/0038a716:wasm-function[530]:0xa4578 13 at wasm://wasm/0038a716:wasm-function[781]:0xb5485 14 at wasm://wasm/0038a716:wasm-function[80]:0x3700b 15 at wasm://wasm/0038a716:wasm-function[277]:0x7e603 16 17Node.js v20.4.0
[href] https://example.com
が表示されているので、console.log()
は実行されていることがわかるunreachable
というのはRustの回復不可能なエラー(panicの一種)で、Rustプログラムを書いた人の想定では実行されないはずのコードが実行されたことを意味する- プログラムに
unreachable!()
と書いた箇所が実行されるとこのエラーが発生する
- プログラムに
unreachable
エラーになる理由として、ライブラリーにバグがあったり、ライブラリーの使い方が想定外だったりすることが考えられます。
free()
を追加したところ、実行時にrecursive use of an objectのエラーが再現できた
READMEを参考にfree()
を追加したところ、質問のエラーが再現できました。
javascript
1// 最後の部分を修正 2try { 3 await rewriter.write(encoder.encode(html)); 4 await rewriter.end(); 5} finally { 6 rewriter.free(); 7}
console
1$ node index.js 2[href] https://example.com 3... /0722-rewriter/node_modules/html-rewriter-wasm/dist/html_rewriter.js:949 4 throw new Error(getStringFromWasm0(arg0, arg1)); 5 ^ 6 7Error: recursive use of an object detected which would lead to unsafe aliasing in rust 8 at module.exports.__wbindgen_throw ( ... /0722-rewriter/node_modules/html-rewriter-wasm/dist/html_rewriter.js:949:11) 9 at wasm://wasm/0038a716:wasm-function[828]:0xb6d3a 10 at wasm://wasm/0038a716:wasm-function[827]:0xb6cae 11 at wasm://wasm/0038a716:wasm-function[351]:0x8bb57 12 at HTMLRewriter.free ( ... /0722-rewriter/node_modules/html-rewriter-wasm/dist/html_rewriter.js:655:14) 13 at file:// ... /0722-rewriter/index.js:17:12 14 15Node.js v20.4.0
エラーの発生箇所がmodule.exports.__wbindgen_throw
となっており、その名前からRustのwasm-bindgenがこのエラーを出しているのだと推測しました。wasm-bindgenはRustでwasmを書くときに便利なライブラリーで、JavaScriptとの相互運用を実現します。html-rewriter-wasmはwasm-bindgenを使っています。
wasm-bindgenのソースコードを調べたところ、エラーを出している箇所が見つかりました。
rust
1 fn borrow_fail() -> ! { 2 super::throw_str( 3 "recursive use of an object detected which would lead to \ 4 unsafe aliasing in rust", 5 ); 6 }
調べたところ、以下のことがわかりました。
- wasm-bindgenでは、Rustの借用ルールが守られていることを実行時に確認している
- Rustだけで書かれたコードでは、借用ルールが守られていることをコンパイラーが検査して、違反があればコンパイルエラーにしてくれる
- しかし、wasmでJavaScriptとの相互運用を行う際は、コンパイラーがJavaScriptに関わる部分が借用ルール満たしているか検査できない。(他言語のコードは検査できない)
- そのため、wasm-bindgenでは、JavaScriptのオブジェクトに対する借用がルールに従っているかを実行時に確認している
- 1つのJavaScriptオブジェクトに対する可変借用は1つだけで、それ以上の可変借用、または、不変借用を行うとエラーになる
Rustの借用ルールについて詳しくは、Rustの所有権システムを参照してください
上のエラーは、HTMLRewriter
のインスタンスに対してfree()
を呼び出したときに発生しています。
console
1 at HTMLRewriter.free ( ... /0722-rewriter/node_modules/html-rewriter-wasm/dist/html_rewriter.js:655:14)
その前にもunreachableエラーになったりしていたので、何か想定外のことが起こっているのだと思います。冒頭に書いた通り、html-rewriter-wasmのREADMEを参考に、HTMLRewriter
をnew
する際に引数を渡すようにしたところ解決しました。恐らく引数が渡されないケースが想定されていないのだと思います。html-rewriter-wasmライブラリーの一種のバグと言えそうです。
修正後のコード
index.js
javascript
1import { HTMLRewriter } from "html-rewriter-wasm"; 2 3const html = "<a href='https://example.com'>Example</a>"; 4 5const encoder = new TextEncoder(); 6const decoder = new TextDecoder(); 7 8let output = ""; 9const rewriter = new HTMLRewriter((outputChunk) => { 10 output += decoder.decode(outputChunk); 11}); 12 13rewriter.on("a[href]", { 14 element(element) { 15 console.log("[href]", element.getAttribute("href")); 16 }, 17}); 18 19try { 20 await rewriter.write(encoder.encode(html)); 21 await rewriter.end(); 22} finally { 23 rewriter.free(); 24}
package.json
json
1{ 2 "name": "rewriter", 3 "version": "1.0.0", 4 "type": "module", 5 "main": "index.js", 6 "license": "MIT", 7 "dependencies": { 8 "html-rewriter-wasm": "^0.4.1" 9 } 10}
投稿2023/07/22 16:15
総合スコア2046
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2023/07/23 02:24
0
自己解決
失礼しました、私の勘違いでした。
NPMのReadmeをちゃんと読んだら、HTMLファイル全体をパースする場合はhtml-rewriter-wasmの代わりに@worker-tools/parsed-html-rewriterを使うようです。
あとはNPMのReadmeのサンプルコードに従い、メソッド名のwrite(u8array)をtransform(response)に、end()をtext()にそれぞれ変えてみました。
コードを次のように修正したら意図通り動きました。
JavaScript
1await new ParsedHTMLRewriter() 2.on('a[href]', { 3 element(element) { 4 console.log('[href]', element.getAttribute('href')); 5 }, 6}) 7.transform(response) 8.text();
そもコールバック関係なかったかも。
JSモジュールなので、呼び出し側がいちいちRustの存在を意識する必要がないようコールバック関数を含む引数はよしなにラップしてくれるようです。
投稿2023/07/22 14:12
編集2023/07/22 14:18総合スコア11
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。