技術陷阱:客戶可能遇到的問題

另請參閱:WASM-JS 特性

陷阱

在程式設計中,陷阱是指系統、程式或程式語言中有效但違反直覺的結構,由於它很容易被呼叫且結果出乎意料或不合理,幾乎很容易導致錯誤。

來源:維基百科

儘管我們盡一切努力保護使用者免受不幸的陷阱,但有些陷阱是無法避免的……

從 Worker 載入 sqlite3.js 時的相對 URI 解析

兩個問題加在一起,造成了透過 Worker 載入 sqlite3 的一個重大陷阱

  1. 相對 URI 的解析方式會因腳本的載入方式而異。
  2. 當腳本透過 importScripts() 載入時,從 JS 無法確定目前載入腳本的 URI。

sqlite3.js 位於與客戶端應用程式不同的目錄中,*且* 客戶端想要使用 Worker 載入它時,這就會成為一個問題。一個完全合理的用法模式如下:

然而,這將會失敗,因為 sqlite3.js 將無法找到 sqlite3.wasm(以及相關檔案),這是因為相對 URI 的解析方式。解決方法是在使用 URI 參數實例化 Worker 時,告知程式庫它的位置

new Worker('foo.js?sqlite3.dir=path/to');

然後,從 foo.js

importScripts('path/to/sqlite3.js');

sqlite3.js 透過 importScripts() 載入時,JS 環境向其公開的唯一 URL 是包含 Worker 載入的 URL,這導致 sqlite3.js 無法解析 sqlite3.wasm

作為一種解決方法,sqlite3.js 會檢查 URL 參數中它唯一可見的 URL(傳遞給 Worker 建構函式的 URL)。如果它找到 sqlite3.dir,它將嘗試從該目錄載入其他與 sqlite3 相關的檔案。如果找不到,它會盡力找出正確的路徑,退回到目前的目錄(只有當它與客戶端應用程式位於同一個目錄中時才有效)。

遺憾的是,sqlite3.dir 路徑必須在 foo.js 的 URI 和 importScripts() 呼叫中重複,而在後者中消除這種重複需要比重複更多的程式碼。例如

let sqlite3Js = 'sqlite3.js';
const urlParams = new URL(self.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
  sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
}
importScripts(sqlite3Js);

同樣遺憾的是,與 importScripts() 參數一起傳遞的 URL 參數會被忽略,因為提供給 importScripts() 的 URI 對以這種方式載入的腳本不可用。它目前的 URL 將解析為載入它的 Worker 腳本(在上面的範例中為 foo.js),因此在載入*該*腳本時需要應用 sqlite3.dir URL 參數。

請注意,當透過 <script> 標籤、作為 ES6 模組或直接透過 Worker 建構函式載入 sqlite3.js 或其補充 JS 檔案之一時,以上情況*不是*問題

const w = new Worker('path/to/sqlite3-worker1.js');

將會執行正確的操作,因為它具有足夠的狀態來確定需要從哪個目錄載入 sqlite3.jssqlite3.wasm

有關如何在不同環境中解析相對 URI 的更多詳細資訊,請參閱

https://zzz.buzz/2017/03/14/relative-uris-in-web-development/

WASM 堆積損壞很容易發生!

WASM 的記憶體觀念是一個簡單的扁平位元組陣列。除了極少數的例外,這種觀念完全缺乏資料型別安全。與 C 編譯器不同,C 編譯器在嘗試將資料型別 X 應用於已宣告為型別 Y 的記憶體時會提供編譯時警告,而 WASM 的記憶體觀念完全沒有型別資訊。

這代表什麼?這表示在無意的情況下損壞 WASM 堆積非常容易。

sqlite3.wasm.poke( 42, 0x1234, 'i32' );

我們剛剛用值 0x1234 覆寫了 WASM 堆積中地址 42 的 4 個位元組。這*可能會*或*可能不會*在稍後的某個不確定時間點導致錯誤行為。這種損壞,就像 C 語言中的堆積損壞一樣,會產生*完全無法預測的影響*,而且*發生時間也完全無法預測*。然而,與 C 語言不同的是,在 WASM 中,我們沒有像不可或缺的 valgrind 這樣的堆積分析工具來幫助我們。

需要說明的是:WASM 中的堆積損壞僅限於 WASM 環境沙盒內的記憶體。除非 WASM 主機引擎存在嚴重錯誤,否則不可能從 WASM 環境內部損壞 WASM 環境外部的記憶體。

簡而言之,如果 JS 應用程式開始拋出完全無法解釋的錯誤,例如在此處拋出例外:

myDb.exec("SELECT 1");

聲稱存在 SQL 語法錯誤,那麼罪魁禍首無疑是記憶體損壞。(是的,記憶體損壞的這個特定症狀實際上以前發生過。另一個多次出現的症狀是來自 WASM 的例外,聲稱被呼叫的函式具有無效的簽章。)

不幸的是,沒有很好的公式可以追蹤此類損壞,而且它甚至可能要到一個月後才會出現。就找出原因而言,最好的辦法是回退到沒有出現問題的應用程式版本,然後(在 SCM 的術語意義上)「二分搜尋」,或一次逐步執行一個版本,直到找到出現問題的版本,然後尋找可能導致問題的差異。任何寫入 WASM 堆積的程式碼都是潛在的嫌疑對象。

祝你好運!