另请参阅:WASM-JS 特性
陷阱
在编程中,陷阱是指系统、程序或编程语言中一个有效的结构,它按照文档说明工作,但违反直觉,几乎会诱发错误,因为它既容易调用,其结果又出乎意料或不合理。
来源:维基百科
尽管我们尽一切努力保护用户免受不幸的陷阱的影响,但有些陷阱是无法避免的……
从Worker加载sqlite3.js
时的相对URI解析
两个问题叠加在一起,为通过Worker加载sqlite3创建了一个重大的陷阱
- 相对URI的解析取决于脚本的加载方式。
- 从JS中,当通过
importScripts()
加载脚本时,无法确定当前正在加载的脚本的URI。
当sqlite3.js
位于客户端应用程序所在的目录之外并且客户端希望使用Worker加载它时,这就会成为问题。一个非常合理的用法模式是这样的
- 客户端代码加载
new Worker('foo.js')
foo.js
使用importScripts('path/to/sqlite3.js)
但是,这将失败,因为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
路径必须在URI中复制到foo.js
和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.js
和sqlite3.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堆的4个字节,地址为42。这可能或可能不会导致以后某个不确定的时间出现错误行为。这种损坏,就像C中的堆损坏一样,将产生完全不可预测的影响,并且具有完全不可预测的时间。但是,与C不同的是,我们没有像不可或缺的valgrind这样的堆分析工具来帮助我们在WASM中。
需要明确的是:WASM中的堆损坏仅限于WASM环境沙箱内的内存。除了WASM引擎中存在严重错误之外,不可能从WASM环境内部损坏WASM环境外部的内存。
简而言之,如果JS应用程序开始抛出完全无法解释的错误,例如在此处抛出异常
myDb.exec("SELECT 1");
声称存在SQL语法错误,那么罪魁祸首无疑是内存损坏。(是的,内存损坏的这种特定症状实际上之前发生过。另一个多次出现的症状是来自WASM的异常,声称调用的函数具有无效的签名。)
不幸的是,没有好的公式来跟踪这种损坏,它甚至可能要一个月后才会出现。在查找原因方面,人们能做的最好的事情是回退到不出现问题的应用程序版本,然后"二分查找"(在SCM意义上的术语),或一步一步地进行版本查找,直到找到出现问题的版本,然后查找可能导致该问题的差异。任何写入WASM堆的内容都是潜在的嫌疑对象。
祝你好运!