質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.39%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

722閲覧

JavaScript←wasm→Rustのエラー

YukiMoriNRT

総合スコア11

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2023/07/22 04:26

編集2023/07/22 04:42

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2023/07/22 09:18

エラーメッセージにはエラーが見つかったファイルの名前と行番号も表示されているはずですので、それがご提示のコードのどの行にあたるのかを示すとよい (別の箇所ならそちらのコードも示すとよい) と思います。
guest

回答2

0

自己解決してよかったです。

気づかずに回答を下書きしてしまったので、そのまま貼り付けますね。


結論から言うと、html-rewriter-wasmのREADMEを参考に、HTMLRewriternewする際に引数を渡すようにしたところ解決しました。試してみてください。

変更前

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のソースコードを調べたところ、エラーを出している箇所が見つかりました。

wasm-bindgen/src/lib.rs#L1562

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を参考に、HTMLRewriternewする際に引数を渡すようにしたところ解決しました。恐らく引数が渡されないケースが想定されていないのだと思います。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

tatsuya6502

総合スコア2046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YukiMoriNRT

2023/07/23 02:24

丁寧なご説明をいただきありがとうございます。 結局、HTMLRewriterの代わりに、ParsedHTMLRewriterを使用することになりました。 開発元が同じなのに引数やメソッド名が異なるのは大変紛らわしいものです。 引数を書き忘れてもPure JSでは自動的にundefinedが渡されるため大きな問題にはならないのですが、厳格な静的型付け言語であるRustではプログラムの実行自体が停止してしまうほど大きな問題なのですね。 恥ずかしながら、私は今までJavaScriptやPythonなどの柔軟性の高い動的型付け言語ばかりやってきたため (TypeScriptすら敬遠していた) でも、これが静的型付け言語なのですね。 変数に所有権があり、いちいち借用しなければ外部で使えないという発想も新鮮でした。 これからはRustも勉強させていただきます。 実は別案件のブラウザアプリでAPIのCORSがネックになっていたのですが、これもWebAssemblyで突破できそうです。 Pure JSしか知らなかった私の世界をRustとwasmが広げてくれました。 ありがとうございました。
guest

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
YukiMoriNRT

総合スコア11

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.39%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問