技术陷阱:客户端潜在的绊脚石

另请参阅:WASM-JS 特性

陷阱

在编程中,陷阱是指系统、程序或编程语言中一个有效的结构,它按照文档说明工作,但违反直觉,几乎会诱发错误,因为它既容易调用,其结果又出乎意料或不合理。

来源:维基百科

尽管我们尽一切努力保护用户免受不幸的陷阱的影响,但有些陷阱是无法避免的……

从Worker加载sqlite3.js时的相对URI解析

两个问题叠加在一起,为通过Worker加载sqlite3创建了一个重大的陷阱

  1. 相对URI的解析取决于脚本的加载方式。
  2. 从JS中,当通过importScripts()加载脚本时,无法确定当前正在加载的脚本的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路径必须在URI中复制到foo.jsimportScripts()调用中,并且消除后者中的重复需要比该重复更多的代码。例如

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堆的4个字节,地址为42。这可能或可能不会导致以后某个不确定的时间出现错误行为。这种损坏,就像C中的堆损坏一样,将产生完全不可预测的影响,并且具有完全不可预测的时间。但是,与C不同的是,我们没有像不可或缺的valgrind这样的堆分析工具来帮助我们在WASM中。

需要明确的是:WASM中的堆损坏仅限于WASM环境沙箱内的内存。除了WASM引擎中存在严重错误之外,不可能从WASM环境内部损坏WASM环境外部的内存。

简而言之,如果JS应用程序开始抛出完全无法解释的错误,例如在此处抛出异常

myDb.exec("SELECT 1");

声称存在SQL语法错误,那么罪魁祸首无疑是内存损坏。(是的,内存损坏的这种特定症状实际上之前发生过。另一个多次出现的症状是来自WASM的异常,声称调用的函数具有无效的签名。)

不幸的是,没有好的公式来跟踪这种损坏,它甚至可能要一个月后才会出现。在查找原因方面,人们能做的最好的事情是回退到不出现问题的应用程序版本,然后"二分查找"(在SCM意义上的术语),或一步一步地进行版本查找,直到找到出现问题的版本,然后查找可能导致该问题的差异。任何写入WASM堆的内容都是潜在的嫌疑对象。

祝你好运!