Khắc phục lỗi rò rỉ bộ nhớ trong WebAssembly bằng Emscripten

Mặc dù JavaScript khá dễ dàng dọn dẹp, nhưng các ngôn ngữ tĩnh chắc chắn không...

Squoosh.app là một PWA minh hoạ số lượng bộ mã hoá và giải mã hình ảnh khác nhau và cài đặt có thể cải thiện kích thước tệp hình ảnh mà không ảnh hưởng đáng kể đến chất lượng. Tuy nhiên, bản minh hoạ kỹ thuật cho thấy cách bạn có thể sử dụng các thư viện được viết bằng C++ hoặc Rust rồi đưa chúng vào web.

Khả năng chuyển đổi mã từ các hệ sinh thái hiện có là vô cùng quan trọng, nhưng có một số sự khác biệt giữa các ngôn ngữ tĩnh và JavaScript đó. Một trong số đó là theo các cách phương pháp quản lý bộ nhớ.

Mặc dù JavaScript khá dễ dàng dọn dẹp, nhưng những ngôn ngữ tĩnh như vậy chắc chắn là không. Bạn cần yêu cầu rõ ràng về bộ nhớ được phân bổ mới và bạn thực sự cần phải đảm bảo bạn sẽ cung cấp lại sau và không bao giờ sử dụng lại. Nếu điều đó không xảy ra, bạn sẽ bị rò rỉ thông tin... và nó thực sự diễn ra khá thường xuyên. Hãy cùng xem cách bạn có thể gỡ lỗi rò rỉ bộ nhớ và tốt hơn nữa, cách bạn có thể thiết kế mã để tránh chúng lần sau.

Mẫu đáng ngờ

Gần đây, khi bắt đầu làm việc trên Squoosh, tôi không chú ý thấy một hình mẫu thú vị trong Trình bao bọc bộ mã hoá và giải mã C++. Hãy xem trình bao bọc ImageQuant ví dụ (giảm để chỉ hiển thị các phần tạo đối tượng và giải phóng):

liq_attr* attr;
liq_image* image;
liq_result* res;
uint8_t* result;

RawImage quantize(std::string rawimage,
                  int image_width,
                  int image_height,
                  int num_colors,
                  float dithering) {
  const uint8_t* image_buffer = (uint8_t*)rawimage.c_str();
  int size = image_width * image_height;

  attr = liq_attr_create();
  image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
  liq_set_max_colors(attr, num_colors);
  liq_image_quantize(image, attr, &res);
  liq_set_dithering_level(res, dithering);
  uint8_t* image8bit = (uint8_t*)malloc(size);
  result = (uint8_t*)malloc(size * 4);

  // …

  free(image8bit);
  liq_result_destroy(res);
  liq_image_destroy(image);
  liq_attr_destroy(attr);

  return {
    val(typed_memory_view(image_width * image_height * 4, result)),
    image_width,
    image_height
  };
}

void free_result() {
  free(result);
}

JavaScript (TypeScript):

export async function process(data: ImageData, opts: QuantizeOptions) {
  if (!emscriptenModule) {
    emscriptenModule = initEmscriptenModule(imagequant, wasmUrl);
  }
  const module = await emscriptenModule;

  const result = module.quantize(/* … */);

  module.free_result();

  return new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );
}

Bạn có phát hiện thấy vấn đề không? Gợi ý: đó là sử dụng sau khi miễn phí, nhưng trong JavaScript!

Trong Emscripten, typed_memory_view trả về một JavaScript Uint8Array được WebAssembly (Wasm) hỗ trợ vùng đệm bộ nhớ, với byteOffsetbyteLength được đặt thành con trỏ và độ dài đã cho. Chính đó là một khung hiển thị TypedArray được đưa vào vùng đệm bộ nhớ WebAssembly, thay vì Bản sao dữ liệu do JavaScript sở hữu.

Khi chúng ta gọi free_result từ JavaScript, hàm này sẽ gọi một hàm C chuẩn free để đánh dấu bộ nhớ này sẵn có cho mọi lượt phân bổ trong tương lai, tức là dữ liệu mà Uint8Array hiển thị điểm đến, có thể bị ghi đè bằng dữ liệu tuỳ ý bằng bất kỳ lệnh gọi nào trong tương lai vào Wasm.

Hoặc, một số phương thức triển khai free thậm chí có thể quyết định lấp đầy bộ nhớ đã giải phóng ngay lập tức bằng 0. Chiến lược phát hành đĩa đơn free mà Emscripten sử dụng không làm như vậy, nhưng chúng tôi đang dựa vào chi tiết triển khai tại đây mà chúng tôi không thể đảm bảo.

Hoặc ngay cả khi bộ nhớ phía sau con trỏ được giữ nguyên, thì quá trình phân bổ mới có thể cần tăng Bộ nhớ WebAssembly. Khi WebAssembly.Memory được phát triển thông qua API JavaScript hoặc memory.grow, phương thức này sẽ vô hiệu hoá ArrayBuffer hiện tại và mọi khung hiển thị theo cách bắc cầu dựa trên nội dung đó.

Hãy để tôi sử dụng bảng điều khiển Công cụ cho nhà phát triển (hoặc Node.js) để minh hoạ hành vi này:

> memory = new WebAssembly.Memory({ initial: 1 })
Memory {}

> view = new Uint8Array(memory.buffer, 42, 10)
Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// ^ all good, we got a 10 bytes long view at address 42

> view.buffer
ArrayBuffer(65536) {}
// ^ its buffer is the same as the one used for WebAssembly memory
//   (the size of the buffer is 1 WebAssembly "page" == 64KB)

> memory.grow(1)
1
// ^ let's say we grow Wasm memory by +1 page to fit some new data

> view
Uint8Array []
// ^ our original view is no longer valid and looks empty!

> view.buffer
ArrayBuffer(0) {}
// ^ its buffer got invalidated as well and turned into an empty one

Cuối cùng, ngay cả khi chúng ta không gọi lại Wasm một cách rõ ràng từ free_result đến new Uint8ClampedArray, tại một thời điểm nào đó, chúng ta vẫn có thể thêm tính năng hỗ trợ đa luồng vào bộ mã hoá và giải mã của mình. Trong trường hợp đó, có thể là một luồng hoàn toàn khác ghi đè dữ liệu ngay trước khi chúng tôi sao chép được.

Đang tìm lỗi bộ nhớ

Trong trường hợp này, tôi đã quyết định đi sâu hơn và kiểm tra xem mã này có cho thấy bất kỳ vấn đề nào trong thực tế không. Đây có vẻ là một cơ hội hoàn hảo để dùng thử sản phẩm khử trùng mô phỏng mới dịch vụ hỗ trợ được thêm vào năm ngoái và được trình bày trong bài nói chuyện trên WebAssembly tại Hội nghị Nhà phát triển Chrome:

Trong trường hợp này, chúng ta quan tâm đến việc AddressSanitizer, có thể phát hiện nhiều vấn đề liên quan đến con trỏ và bộ nhớ. Để dùng mã này, chúng tôi cần biên dịch lại bộ mã hoá và giải mã với -fsanitize=address:

emcc \
  --bind \
  ${OPTIMIZE} \
  --closure 1 \
  -s ALLOW_MEMORY_GROWTH=1 \
  -s MODULARIZE=1 \
  -s 'EXPORT_NAME="imagequant"' \
  -I node_modules/libimagequant \
  -o ./imagequant.js \
  --std=c++11 \
  imagequant.cpp \
  -fsanitize=address \
  node_modules/libimagequant/libimagequant.a

Thao tác này sẽ tự động bật tính năng kiểm tra an toàn cho con trỏ, nhưng chúng ta cũng muốn tìm bộ nhớ tiềm ẩn rò rỉ thông tin. Vì chúng tôi đang sử dụng ImageQuant làm thư viện thay vì chương trình, nên sẽ không có "điểm thoát" vào lúc mà Emscripten có thể tự động xác thực rằng tất cả bộ nhớ đã được giải phóng.

Thay vào đó, đối với những trường hợp như vậy, LeakSanitizer (có trong AddressSanitizer) sẽ cung cấp các hàm __lsan_do_leak_check__lsan_do_recoverable_leak_check, có thể được gọi theo cách thủ công bất cứ khi nào chúng ta mong muốn tất cả bộ nhớ đã được giải phóng và muốn xác thực rằng giả định. __lsan_do_leak_check được dùng ở cuối ứng dụng đang chạy, khi bạn muốn huỷ bỏ quá trình này phòng khi phát hiện thấy bất kỳ sự cố rò rỉ nào, trong khi __lsan_do_recoverable_leak_check phù hợp hơn với các trường hợp sử dụng thư viện như trường hợp của chúng tôi, khi bạn muốn in rò rỉ đến bảng điều khiển, nhưng duy trì hoạt động của ứng dụng.

Hãy hiển thị trình trợ giúp thứ hai qua Embind để chúng ta có thể gọi trình trợ giúp từ JavaScript bất cứ lúc nào:

#include <sanitizer/lsan_interface.h>

// …

void free_result() {
  free(result);
}

EMSCRIPTEN_BINDINGS(my_module) {
  function("zx_quantize", &zx_quantize);
  function("version", &version);
  function("free_result", &free_result);
  function("doLeakCheck", &__lsan_do_recoverable_leak_check);
}

Và gọi nó từ phía JavaScript sau khi chúng ta hoàn tất với hình ảnh. Thực hiện việc này từ Phía JavaScript, thay vì phía C++, giúp đảm bảo rằng tất cả các phạm vi đều được bị thoát và tất cả các đối tượng C++ tạm thời đã được giải phóng vào thời điểm chúng tôi chạy các bước kiểm tra đó:

  // 

  const result = opts.zx
    ? module.zx_quantize(data.data, data.width, data.height, opts.dither)
    : module.quantize(data.data, data.width, data.height, opts.maxNumColors, opts.dither);

  module.free_result();
  module.doLeakCheck();

  return new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );
}

Thao tác này sẽ cung cấp cho chúng ta một báo cáo như sau trong bảng điều khiển:

Ảnh chụp màn hình về một thông báo

Ồ, có một số rò rỉ nhỏ, nhưng dấu vết ngăn xếp không hữu ích lắm vì tất cả các tên hàm bị hỏng. Hãy biên dịch lại bằng thông tin gỡ lỗi cơ bản để giữ nguyên các thông tin đó:

emcc \
  --bind \
  ${OPTIMIZE} \
  --closure 1 \
  -s ALLOW_MEMORY_GROWTH=1 \
  -s MODULARIZE=1 \
  -s 'EXPORT_NAME="imagequant"' \
  -I node_modules/libimagequant \
  -o ./imagequant.js \
  --std=c++11 \
  imagequant.cpp \
  -fsanitize=address \
  -g2 \
  node_modules/libimagequant/libimagequant.a

Giao diện này trông đẹp hơn nhiều:

Ảnh chụp màn hình thông báo cho biết &quot;Trực tiếp rò rỉ 12 byte&quot; đến từ hàm GenericBindingType RawImage ::toWireType

Một số phần của dấu vết ngăn xếp vẫn trông mờ ám khi trỏ đến phần bên trong Emscripten, nhưng chúng ta có thể cho biết rằng sự cố rò rỉ bắt nguồn từ lượt chuyển đổi RawImage thành "loại dây" (thành giá trị JavaScript) bằng cách Đã liên kết. Thật vậy, khi xem mã, chúng ta có thể thấy rằng chúng ta trả về các phiên bản C++ RawImage cho JavaScript, nhưng chúng tôi không bao giờ giải phóng chúng ở cả hai bên.

Xin lưu ý rằng hiện tại, chưa tích hợp tính năng thu thập rác giữa JavaScript và WebAssembly, mặc dù một công cụ đang được phát triển. Thay vào đó, bạn có để giải phóng bất kỳ bộ nhớ nào theo cách thủ công và gọi các hàm huỷ từ phía JavaScript sau khi bạn đã hoàn tất . Riêng đối với Embind, cụm từ chính thức tài liệu đề xuất gọi một phương thức .delete() trên các lớp C++ bị lộ:

Mã JavaScript phải xoá rõ ràng mọi đối tượng C++ xử lý nó đã nhận được hoặc mã Emscripten Vùng nhớ khối xếp sẽ phát triển vô thời hạn.

var x = new Module.MyClass;
x.method();
x.delete();

Thật vậy, khi chúng ta thực hiện việc đó trong JavaScript cho lớp:

  // 

  const result = opts.zx
    ? module.zx_quantize(data.data, data.width, data.height, opts.dither)
    : module.quantize(data.data, data.width, data.height, opts.maxNumColors, opts.dither);

  module.free_result();
  result.delete();
  module.doLeakCheck();

  return new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );
}

Sự rò rỉ này biến mất như dự kiến.

Phát hiện các vấn đề khác về chất khử trùng

Việc tạo các bộ mã hoá và giải mã Squoosh khác bằng trình dọn dẹp cho thấy vừa tương tự vừa phát hiện một số vấn đề mới. Cho Ví dụ: tôi gặp lỗi này trong các liên kết MozJPEG:

Ảnh chụp màn hình về một thông báo

Ở đây, đó không phải là sự rò rỉ dữ liệu, mà là chúng ta ghi vào một bộ nhớ nằm ngoài ranh giới được phân bổ 🤔

Đào sâu vào mã MozJPEG, chúng ta nhận thấy có vấn đề ở đây là jpeg_mem_dest — mà chúng ta sử dụng để phân bổ đích bộ nhớ cho JPEG—sử dụng lại các giá trị hiện có của outbufferoutsize khi chúng lên khác 0:

if (*outbuffer == NULL || *outsize == 0) {
  /* Allocate initial buffer */
  dest->newbuffer = *outbuffer = (unsigned char *) malloc(OUTPUT_BUF_SIZE);
  if (dest->newbuffer == NULL)
    ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
  *outsize = OUTPUT_BUF_SIZE;
}

Tuy nhiên, chúng ta gọi nó mà không cần khởi chạy một trong hai biến đó, nghĩa là MozJPEG ghi dẫn đến một địa chỉ bộ nhớ ngẫu nhiên có thể được lưu trữ trong các biến đó tại thời gian của cuộc gọi!

uint8_t* output;
unsigned long size;
// …
jpeg_mem_dest(&cinfo, &output, &size);

Việc không khởi chạy cả hai biến trước khi lệnh gọi sẽ giải quyết vấn đề này và giờ đây mã sẽ đạt đến thay vào đó, kiểm tra rò rỉ bộ nhớ. May mắn thay, việc kiểm tra thành công, cho thấy rằng chúng tôi không có rò rỉ trong bộ mã hoá và giải mã này.

Vấn đề về trạng thái được chia sẻ

...hay chúng ta làm gì?

Chúng ta biết rằng các liên kết bộ mã hoá và giải mã của mình lưu trữ một số trạng thái cũng như dẫn đến trạng thái tĩnh toàn cầu nhiều biến và MozJPEG có một số cấu trúc đặc biệt phức tạp.

uint8_t* last_result;
struct jpeg_compress_struct cinfo;

val encode(std::string image_in, int image_width, int image_height, MozJpegOptions opts) {
  // …
}

Điều gì sẽ xảy ra nếu một số ứng dụng khởi động từng phần trong lần chạy đầu tiên, sau đó sử dụng lại không đúng cách trong tương lai chạy không? Sau đó, một cuộc gọi riêng lẻ có trình dọn dẹp sẽ không báo cáo các cuộc gọi đó là có vấn đề.

Hãy thử và xử lý hình ảnh một vài lần bằng cách nhấp ngẫu nhiên ở các mức chất lượng khác nhau trong giao diện người dùng. Thực vậy, bây giờ chúng tôi nhận được báo cáo sau đây:

Ảnh chụp màn hình về một thông báo

262.144 byte – có vẻ như toàn bộ hình ảnh mẫu đã bị rò rỉ từ jpeg_finish_compress!

Sau khi xem các tài liệu và ví dụ chính thức, hoá ra jpeg_finish_compress không giải phóng bộ nhớ được phân bổ bởi lệnh gọi jpeg_mem_dest trước đó mà chỉ giải phóng cấu trúc nén đó, mặc dù cấu trúc nén đó đã biết về bộ nhớ điểm đến... Hai lần đầu tiên.

Chúng ta có thể khắc phục vấn đề này bằng cách giải phóng dữ liệu theo cách thủ công trong hàm free_result:

void free_result() {
  /* This is an important step since it will release a good deal of memory. */
  free(last_result);
  jpeg_destroy_compress(&cinfo);
}

Tôi có thể tiếp tục săn từng lỗi bộ nhớ, nhưng đến giờ thì rõ ràng là phương pháp quản lý bộ nhớ hiện tại dẫn đến một số vấn đề hệ thống khó chịu.

Máy vệ sinh có thể tóm được một vài trong số đó ngay lập tức. Một số trường hợp khác lại đòi hỏi những thủ thuật tinh vi thì mới bị bắt được. Cuối cùng, có một số vấn đề như trong phần đầu của bài đăng, như chúng ta có thể thấy trong nhật ký, công cụ dọn dẹp không hề bị bắt. Lý do là việc sử dụng sai mục đích thực sự xảy ra trên Phía JavaScript mà trình dọn dẹp không có chế độ hiển thị. Những vấn đề đó sẽ tự phát hiện chỉ trong quá trình sản xuất hoặc sau khi những thay đổi có vẻ không liên quan đến mã trong tương lai.

Xây dựng một trình bao bọc an toàn

Hãy lùi lại một vài bước và thay vào đó, hãy khắc phục tất cả các vấn đề này bằng cách tái cấu trúc mã theo cách an toàn hơn. Tôi sẽ sử dụng lại trình bao bọc ImageQuant làm ví dụ, nhưng áp dụng quy tắc tái cấu trúc tương tự vào tất cả các bộ mã hoá và giải mã cũng như các cơ sở mã tương tự khác.

Trước hết, hãy khắc phục vấn đề "Use-after-free" (không dùng nữa) ở đầu bài đăng này. Để làm được điều đó, chúng tôi cần để sao chép dữ liệu từ chế độ xem được WebAssembly hỗ trợ trước khi đánh dấu dữ liệu đó là miễn phí ở phía JavaScript:

  // 

  const result = /*  */;

  const imgData = new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );

  module.free_result();
  result.delete();
  module.doLeakCheck();

  return new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );
  return imgData;
}

Bây giờ, hãy đảm bảo chúng ta không chia sẻ trạng thái nào trong các biến toàn cục giữa các lệnh gọi. Chiến dịch này sẽ khắc phục một số vấn đề chúng ta đã gặp phải, đồng thời giúp người dùng dễ dàng sử dụng trong môi trường đa luồng trong tương lai.

Để làm được điều đó, chúng ta sẽ tái cấu trúc trình bao bọc C++ để đảm bảo rằng mỗi lệnh gọi đến hàm tự quản lý lệnh gọi đó bằng cách sử dụng các biến cục bộ. Sau đó, chúng ta có thể thay đổi chữ ký của hàm free_result thành chấp nhận con trỏ trở lại:

liq_attr* attr;
liq_image* image;
liq_result* res;
uint8_t* result;

RawImage quantize(std::string rawimage,
                  int image_width,
                  int image_height,
                  int num_colors,
                  float dithering) {
  const uint8_t* image_buffer = (uint8_t*)rawimage.c_str();
  int size = image_width * image_height;

  attr = liq_attr_create();
  image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
  liq_attr* attr = liq_attr_create();
  liq_image* image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
  liq_set_max_colors(attr, num_colors);
  liq_result* res = nullptr;
  liq_image_quantize(image, attr, &res);
  liq_set_dithering_level(res, dithering);
  uint8_t* image8bit = (uint8_t*)malloc(size);
  result = (uint8_t*)malloc(size * 4);
  uint8_t* result = (uint8_t*)malloc(size * 4);

  // 
}

void free_result() {
void free_result(uint8_t *result) {
  free(result);
}

Tuy nhiên, vì chúng tôi đã sử dụng Embind trong Emscripten để tương tác với JavaScript, chúng tôi cũng có thể giúp API này an toàn hơn nữa bằng cách ẩn hoàn toàn chi tiết quản lý bộ nhớ C++!

Để làm được việc đó, hãy di chuyển phần new Uint8ClampedArray(…) từ JavaScript sang phía C++ bằng Đã liên kết. Sau đó, chúng ta có thể sử dụng tập dữ liệu này để sao chép dữ liệu vào bộ nhớ JavaScript, thậm chí trước khi trả về từ hàm:

class RawImage {
 public:
  val buffer;
  int width;
  int height;

  RawImage(val b, int w, int h) : buffer(b), width(w), height(h) {}
};
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");

RawImage quantize(/*  */) {
val quantize(/*  */) {
  // 
  return {
    val(typed_memory_view(image_width * image_height * 4, result)),
    image_width,
    image_height
  };
  val js_result = Uint8ClampedArray.new_(typed_memory_view(
    image_width * image_height * 4,
    result
  ));
  free(result);
  return js_result;
}

Lưu ý: chỉ với một thay đổi duy nhất, chúng ta đều đảm bảo rằng mảng byte kết quả thuộc sở hữu của JavaScript và không được bộ nhớ WebAssembly hỗ trợ, đồng thời loại bỏ trình bao bọc RawImage bị rò rỉ trước đó của Google.

Bây giờ, JavaScript không phải lo lắng về việc giải phóng dữ liệu nữa và có thể sử dụng kết quả như bất kỳ đối tượng được thu thập rác nào khác:

  // 

  const result = /*  */;

  const imgData = new ImageData(
    new Uint8ClampedArray(result.view),
    result.width,
    result.height
  );

  module.free_result();
  result.delete();
  // module.doLeakCheck();

  return imgData;
  return new ImageData(result, result.width, result.height);
}

Điều này cũng có nghĩa là chúng ta không cần liên kết free_result tuỳ chỉnh ở phía C++ nữa:

void free_result(uint8_t* result) {
  free(result);
}

EMSCRIPTEN_BINDINGS(my_module) {
  class_<RawImage>("RawImage")
      .property("buffer", &RawImage::buffer)
      .property("width", &RawImage::width)
      .property("height", &RawImage::height);

  function("quantize", &quantize);
  function("zx_quantize", &zx_quantize);
  function("version", &version);
  function("free_result", &free_result, allow_raw_pointers());
}

Về tổng thể, mã bao bọc của chúng tôi nay vừa gọn gàng hơn vừa an toàn hơn.

Sau đó, tôi đã trải qua một số cải tiến nhỏ thêm cho mã của trình bao bọc ImageQuant và sao chép các bản sửa lỗi quản lý bộ nhớ tương tự cho các bộ mã hoá và giải mã khác. Nếu muốn biết thêm thông tin, bạn có thể xem PR kết quả tại đây: Sửa lỗi bộ nhớ cho C++ bộ mã hoá và giải mã.

Cướp lại bóng

Chúng ta có thể học hỏi và chia sẻ những bài học nào từ việc tái cấu trúc này và có thể áp dụng cho các cơ sở mã khác?

  • Không sử dụng các thành phần hiển thị bộ nhớ được WebAssembly hỗ trợ (bất kể giao diện được tạo bằng ngôn ngữ nào) bên ngoài một lời gọi duy nhất. Bạn không thể dựa vào chúng để tồn tại lâu hơn, và bạn sẽ không thể để phát hiện các lỗi này bằng các phương pháp thông thường, vì vậy, nếu bạn cần lưu trữ dữ liệu cho sau này, hãy sao chép dữ liệu vào phía JavaScript và lưu trữ mã đó ở đó.
  • Nếu có thể, hãy sử dụng ngôn ngữ quản lý bộ nhớ an toàn hoặc ít nhất là trình bao bọc loại an toàn, thay vì trực tiếp hoạt động trên con trỏ thô. Việc này sẽ không giúp bạn tránh các lỗi trên JavaScript 📲 WebAssembly ranh giới, nhưng ít nhất nó sẽ làm giảm bề mặt cho các lỗi độc lập bởi mã ngôn ngữ tĩnh.
  • Bất kể bạn dùng ngôn ngữ nào, hãy chạy mã bằng công cụ dọn dẹp trong quá trình phát triển vì chúng có thể giúp không chỉ phát hiện các vấn đề trong mã ngôn ngữ tĩnh, mà còn phát hiện một số vấn đề về JavaScript tránh Ranh giới WebAssembly, chẳng hạn như quên gọi .delete() hoặc truyền con trỏ không hợp lệ từ phía JavaScript.
  • Nếu có thể, hãy tránh để lộ hoàn toàn dữ liệu và đối tượng không được quản lý từ WebAssembly cho JavaScript. JavaScript là ngôn ngữ được thu thập rác và việc quản lý bộ nhớ thủ công không phổ biến trong ngôn ngữ này. Đây có thể được coi là một sự rò rỉ trừu tượng của mô hình bộ nhớ cho ngôn ngữ mà WebAssembly của bạn sử dụng được tạo và hoạt động quản lý không chính xác rất dễ bị bỏ qua trong cơ sở mã JavaScript.
  • Điều này có thể hiển nhiên, nhưng cũng giống như trong bất kỳ cơ sở mã nào khác, hãy tránh lưu trữ trạng thái có thể thay đổi trong biến. Bạn không muốn gỡ lỗi khi sử dụng lại các lệnh gọi khác nhau hoặc thậm chí luồng, vì vậy, tốt nhất bạn nên giữ luồng độc lập nhất có thể.