Emscripten का इस्तेमाल करके, WebAssembly में मेमोरी लीक होने की जानकारी को डीबग करना

हालांकि JavaScript अपने आप को साफ़ करने के मामले को नज़रअंदाज़ करता है, लेकिन स्टैटिक भाषाएं ऐसी नहीं हैं...

Squoosh.app एक PWA है, जो यह दिखाता है कि कितने अलग-अलग इमेज कोडेक हैं और सेटिंग, क्वालिटी पर ज़्यादा असर डाले बिना इमेज फ़ाइल के साइज़ को बेहतर बना सकती हैं. हालांकि, यह भी एक तकनीकी डेमो, जिसमें यह बताया गया है कि C++ या Rust में लिखी गई लाइब्रेरी को कैसे हासिल किया जा सकता है और वेब.

मौजूदा नेटवर्क से कोड को पोर्ट कर पाने की क्षमता बहुत अहम है, लेकिन अभी तक कई ज़रूरी के बीच फ़र्क़ दिखाया गया है. उनमें से एक अपने अलग और ज़्यादा मेमोरी उपलब्ध कराने की कोशिश कर रहे हैं.

हालांकि, JavaScript अपने-आप काम को पूरा करने के मामले को नज़रअंदाज़ करता है, लेकिन ऐसी स्टैटिक भाषाएं बिलकुल नहीं. आपको साफ़ तौर पर, नई यादों के लिए अनुरोध करना होगा. साथ ही, यह भी ज़रूरी है कि उसे बाद में वापस दे दें और उसका कभी भी इस्तेमाल न करें. अगर ऐसा नहीं होता है, तो आपकी जानकारी लीक हो जाती है... और यह काफ़ी नियमित रूप से होता है. चलिए देखते हैं कि मेमोरी में होने वाली उन गड़बड़ियों को कैसे डीबग किया जा सकता है और और भी बेहतर होगा कि आप अपने कोड को कैसे डिज़ाइन कर सकते हैं, ताकि अगली बार उनसे बचा जा सके.

संदिग्ध पैटर्न

हाल ही में, Squoosh पर काम शुरू करते समय, मुझे C++ कोडेक रैपर. आइए, ImageQuant रैपर पर एक नज़र डालते हैं, उदाहरण (सिर्फ़ ऑब्जेक्ट बनाने और हटाने की जगह वाले हिस्से दिखाने के लिए कम किया गया):

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
  );
}

क्या आपको कोई गड़बड़ी दिख रही है? संकेत: यह इस्तेमाल के बाद मुफ़्त में, लेकिन JavaScript!

Emscripten में, typed_memory_view WebAssembly (Wasm) के साथ काम करने वाली JavaScript Uint8Array दिखाता है मेमोरी बफ़र, जिसमें byteOffset और byteLength दिए गए पॉइंटर और लंबाई पर सेट किए गए हैं. मुख्य पॉइंट का मतलब है कि यह WebAssembly मेमोरी बफ़र में TypedArray व्यू है, न कि JavaScript के मालिकाना हक वाली डेटा की कॉपी.

जब हम JavaScript से free_result को कॉल करते हैं, तो यह निशान लगाने के लिए स्टैंडर्ड C फ़ंक्शन free को कॉल करता है यह मेमोरी आने वाले समय में किए जाने वाले किसी भी आवंटन के लिए उपलब्ध है. इसका मतलब है कि हमारे Uint8Array को दिखने वाला डेटा Wasm के लिए किए जाने वाले कॉल को आर्बिट्रेरी डेटा से बदला जा सकता है.

इसके अलावा, free को लागू करने पर, खाली की गई मेमोरी को तुरंत शून्य भी किया जा सकता है. कॉन्टेंट बनाने Emscripten का इस्तेमाल करने वाला free ऐसा नहीं करता है. हालांकि, हम यहां लागू करने के बारे में जानकारी पर भरोसा कर रहे हैं जिसकी गारंटी नहीं दी जा सकती.

इसके अलावा, अगर पॉइंटर के पीछे की मेमोरी सुरक्षित रहती है, तब भी नए आवंटन को बढ़ाने की ज़रूरत पड़ सकती है WebAssembly की मेमोरी. जब WebAssembly.Memory को JavaScript API या इससे जुड़े memory.grow निर्देश के मुताबिक, यह मौजूदा ArrayBuffer को अमान्य कर देता है. साथ ही, यह किसी भी व्यू को अमान्य कर देता है सुरक्षित नहीं रहा.

इस व्यवहार को दिखाने के लिए, मुझे DevTools (या Node.js) कंसोल का इस्तेमाल करने दें:

> 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

अगर हम साफ़ तौर पर free_result और new Uint8ClampedArray के बीच Wasm का इस्तेमाल नहीं करते हैं, तो भी हम अपने कोडेक में मल्टीथ्रेडिंग की सुविधा जोड़ सकते हैं. इस मामले में यह यह एक पूरी तरह से अलग थ्रेड हो सकता है जो डेटा को क्लोन करने से ठीक पहले ओवरराइट करता है.

मेमोरी से जुड़ी गड़बड़ियों को ढूंढा जा रहा है

मैंने आगे बढ़ने और जांच करने का फ़ैसला किया है कि क्या इस कोड में कोई ऐसी समस्या है जो दिख रही है. यह नए(इश) एम्स्क्रिप्टन सैनिटाइज़र्स को आज़माने का एक सही अवसर लगता है सहायता टीम से संपर्क करने की सुविधा मिलेगी, जिसे पिछले साल जोड़ा गया था और हमने Chrome Dev सम्मेलन में हुई WebAssembly बातचीत में इन्हें पेश किया:

इस मामले में, हमें दिलचस्पी है AddressSanitizer, जो पॉइंटर और मेमोरी से जुड़ी अलग-अलग समस्याओं का पता लगा सकती है. इसका इस्तेमाल करने के लिए, हमें अपने कोडेक को फिर से कंपाइल करना होगा -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

इससे पॉइंटर की सुरक्षा जांच अपने-आप चालू हो जाएगी. हालांकि, हम संभावित मेमोरी भी ढूंढना चाहते हैं लीक हो जाता है. हम ImageQuant का उपयोग प्रोग्राम के बजाय लाइब्रेरी के रूप में कर रहे हैं, इसलिए इसमें कोई "एग्ज़िट पॉइंट" नहीं है पर कौनसा Emscripten अपने-आप इस बात की पुष्टि कर सकता है कि सभी मेमोरी खाली कर दी गई हैं.

इसके बजाय, ऐसे मामलों के लिए LeakSanitizer (AddressSanitizer में शामिल) फ़ंक्शन उपलब्ध कराता है __lsan_do_leak_check और __lsan_do_recoverable_leak_check, जिसे मैन्युअल रूप से तब शुरू किया जा सकता है, जब हम उम्मीद करते हैं कि सभी मेमोरी ख़ाली हो जाएँ और हम माना जाता है. __lsan_do_leak_check का इस्तेमाल किसी चल रहे ऐप्लिकेशन के आखिर में किया जाना चाहिए, जब इस प्रोसेस को रद्द करना चाहते हैं, ताकि किसी तरह की जानकारी लीक होने का पता चलने पर आप उसे रद्द कर सकें. __lsan_do_recoverable_leak_check जब कंसोल में लीक को प्रिंट करना हो, तो लाइब्रेरी में होने वाले इस्तेमाल के मामलों में यह तरीका ज़्यादा सही होता है, फिर चाहे ऐप्लिकेशन को चालू रखें.

चलिए, उस दूसरे हेल्पर के बारे में Embind के ज़रिए जानकारी देते हैं, ताकि हम किसी भी समय JavaScript से इसे कॉल कर सकें:

#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);
}

और इमेज पर काम पूरा हो जाने के बाद, JavaScript की ओर से इसे शुरू करें. ऐसा C++ के बजाय JavaScript का इस्तेमाल करने पर, यह पक्का करने में मदद मिलती है कि सभी स्कोप बाहर निकल गया और हमारे जांच करने से पहले, सभी अस्थायी C++ ऑब्जेक्ट खाली हो गए:

  // 

  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
  );
}

इससे हमें कंसोल में इस तरह की रिपोर्ट मिलती है:

मैसेज का स्क्रीनशॉट

ओह, थोड़ी-बहुत जानकारी लीक हुई है, लेकिन सभी फ़ंक्शन के नामों की तरह स्टैकट्रेस बहुत मददगार नहीं है घायल किए गए हैं. उन्हें सुरक्षित रखने के लिए, उन्हें डीबग करने की बुनियादी जानकारी के साथ फिर से कंपाइल करते हैं:

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

यह काफ़ी बेहतर दिखता है:

&#39;सीधे तौर पर 12 बाइट लीक हुआ&#39; मैसेज का स्क्रीनशॉट जेनेरिकBindingTypeRAWImage ::toWireType फ़ंक्शन से आ रहा है

स्टैकट्रेस के कुछ हिस्से अब भी अस्पष्ट दिख रहे हैं, क्योंकि वे Emscripten इंटर्नल की ओर इशारा करते हैं, लेकिन हम बताएं कि लीक RawImage कन्वर्ज़न से "वायर टाइप" में हो रहा है (किसी JavaScript वैल्यू में) एम्बाइंड. कोड को देखने पर, हम पाते हैं कि RawImage C++ इंस्टेंस, JavaScript लागू करते हैं, लेकिन हम उन्हें कभी भी किसी भी तरफ़ खाली नहीं करते.

आपको याद दिला दें कि फ़िलहाल JavaScript और WebAssembly. हालांकि, एक को बनाया जा रहा है. इसके बजाय, आपके पास का काम पूरा हो जाने के बाद JavaScript साइड से किसी भी मेमोरी और डिस्ट्रक्टर को कॉल करने के लिए मैन्युअल रूप से ऑब्जेक्ट है. खास तौर पर, एम्बाइंड के लिए, आधिकारिक दस्तावेज़ सार्वजनिक C++ क्लास में किसी .delete() तरीके को कॉल करने का सुझाव दें:

JavaScript कोड को हीप अनिश्चित समय तक बढ़ता रहेगा.

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

वाकई, जब हम अपनी क्लास के लिए JavaScript में ऐसा करते हैं:

  // 

  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
  );
}

लीक होने की समस्या ठीक हो गई है.

सैनिटाइज़र से जुड़ी समस्याओं के बारे में जानना

सैनिटाइज़र की मदद से, दूसरे Squoosh कोडेक बनाने पर, एक-दूसरे से मिलते-जुलते और कुछ नई समस्याओं का पता चलता है. इसके लिए उदाहरण के लिए, मुझे MozJPEG फ़ॉर्मैट में यह गड़बड़ी मिली है:

मैसेज का स्क्रीनशॉट

यह कोई लीक नहीं है, बल्कि हम एक ऐसी याद में लिख रहे हैं जो तय सीमाओं से परे है ➘

MozJPEG के कोड को देखने पर, हमें पता चला है कि यहाँ समस्या यह है कि jpeg_mem_dest— फ़ंक्शन है जिसका इस्तेमाल हम JPEG के लिए मेमोरी डेस्टिनेशन असाइन करने के लिए करते हैं— outbuffer और outsize शून्य के अलावा:

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;
}

हालांकि, हम इनमें से किसी भी वैरिएबल को शुरू किए बिना इसे शुरू करते हैं. इसका मतलब है कि MozJPEG नतीजे के तौर पर, किसी भी रैंडम मेमोरी पते में बदल जाता है, जो कि कॉल का समय है!

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

शुरू करने से पहले, दोनों वैरिएबल को शून्य से शुरू करने से यह समस्या हल हो जाती है. साथ ही, अब कोड मेमोरी लीक जाँच करेगा. अच्छी बात यह है कि जांच पूरी हो गई. इससे पता चलता है कि हमारे पास कोई इस कोडेक में लीक हो गया है.

शेयर की गई स्थिति से जुड़ी समस्याएं

...या हम करें?

हम जानते हैं कि हमारी कोडेक बाइंडिंग, कुछ राज्यों को सेव करती हैं. साथ ही, नतीजे ग्लोबल स्टैटिक में भी होते हैं और MozJPEG फ़ॉर्मैट में कुछ खास तरह के स्ट्रक्चर होते हैं.

uint8_t* last_result;
struct jpeg_compress_struct cinfo;

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

क्या होगा अगर उनमें से कुछ को पहली बार में ही लेज़ी शुरू किया जाए और फिर भविष्य में उनका गलत तरीके से दोबारा इस्तेमाल किया जाए दौड़ता है? फिर सैनिटाइज़र से एक बार बात करने पर भी, उन्हें कोई समस्या नहीं मिलेगी.

आइए, अलग-अलग क्वालिटी लेवल पर बिना किसी क्रम के क्लिक करके, इमेज को कई बार प्रोसेस करने की कोशिश करते हैं डालें. दरअसल, अब हमें ये रिपोर्ट मिलती हैं:

मैसेज का स्क्रीनशॉट

262,144 बाइट—ऐसा लगता है कि पूरी सैंपल इमेज, jpeg_finish_compress से लीक हो गई है!

दस्तावेज़ों और आधिकारिक उदाहरणों को देखने के बाद, पता चला है कि jpeg_finish_compress हमारे पिछले jpeg_mem_dest कॉल में आबंटित की गई मेमोरी को खाली नहीं करता है—यह सिर्फ़ संपीड़न संरचना, भले ही संपीड़न संरचना पहले से ही हमारी स्मृति के बारे में गंतव्य... आह.

हम 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);
}

मैं उन मेमोरी की गड़बड़ियों का एक-एक करके पता लगा सकता था, लेकिन मुझे लगता है कि अब तक यह बात साफ़ हो चुकी है कि मेमोरी प्रबंधन के मौजूदा तरीक़े की वजह से कुछ गंभीर शातिर समस्याएं पैदा हो जाती हैं.

इनमें से कुछ को सैनिटाइज़र तुरंत पकड़ सकता है. वहीं कुछ को पकड़ने के लिए, पेचीदा तरकीबों की ज़रूरत होती है. आखिर में, पोस्ट की शुरुआत में कुछ समस्याएं आती हैं. जैसा कि हमें लॉग में दिखता है, सैनिटाइज़र के हाथों में नहीं होता. इसका कारण यह है कि वास्तविक दुरुपयोग JavaScript का हिस्सा नहीं होना चाहिए, जिसमें सैनिटाइज़र नहीं दिखता. ये समस्याएं अपने-आप सामने आ जाएंगी ऐसा सिर्फ़ प्रोडक्शन में या आने वाले समय में कोड में ऐसे बदलाव होने पर किया जाए जो किसी बाहरी सोर्स से मेल न खाते हों.

सुरक्षित रैपर बनाना

चलिए कुछ कदम पीछे चलते हैं और इसके बजाय कोड को फिर से स्ट्रक्चर करके इन सभी समस्याओं को ठीक करते हैं को सुरक्षित रखा जाता है. मैं उदाहरण के तौर पर फिर से ImageQuant रैपर का इस्तेमाल करूंगा, लेकिन इस तरह के रीफ़ैक्टरिंग के नियम लागू होंगे कोड बेस में और दूसरे मिलते-जुलते कोड बेस में भी दिखेगा.

सबसे पहले, आइए पोस्ट की शुरुआत से ही इस तरह की समस्या को हल करते हैं: इसके लिए, हमें JavaScript साइड पर 'मुफ़्त' के तौर पर मार्क करने से पहले, WebAssembly-बैक्ड व्यू से डेटा का क्लोन बनाने के लिए:

  // 

  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;
}

अब यह पक्का कर लें कि हम शुरू करने के बीच ग्लोबल वैरिएबल में कोई भी स्थिति शेयर न करें. यह हम उन समस्याओं को ठीक कर लेंगे जिन्हें हम पहले देख चुके हैं. साथ ही, हम इन समस्याओं को हल कर सकेंगे. आने वाले समय में मल्टीथ्रेड एनवायरमेंट में कोडेक का इस्तेमाल करें.

ऐसा करने के लिए, हम C++ रैपर को रीफ़ैक्टर करते हैं, ताकि यह पक्का किया जा सके कि फ़ंक्शन को किया जाने वाला हर कॉल अपने-आप मैनेज हो स्थानीय वैरिएबल का इस्तेमाल करके डेटा इकट्ठा करना. इसके बाद, हम अपने free_result फ़ंक्शन के हस्ताक्षर को बदलकर यह कर सकते हैं पॉइंटर को वापस स्वीकार करें:

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);
}

हालांकि, हम JavaScript के साथ इंटरैक्ट करने के लिए पहले से ही Embind का इस्तेमाल कर रहे हैं. इसलिए, हम भी ऐसा कर सकते हैं C++ मेमोरी मैनेजमेंट की जानकारी छिपाकर, एपीआई को और भी सुरक्षित बनाएं!

इसके लिए, चलिए new Uint8ClampedArray(…) वाले हिस्से को JavaScript से C++ साइड में ले जाते हैं. एम्बाइंड. इसके बाद, हम इसका इस्तेमाल साइट पर वापस आने से पहले भी डेटा को JavaScript मेमोरी में क्लोन करने के लिए कर सकते हैं से:

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;
}

ध्यान दें कि कैसे एक ही बदलाव करके, हम दोनों यह पक्का करते हैं कि नतीजे के तौर पर मिलने वाले बाइट अरे का मालिकाना हक JavaScript के पास हो और WebAssembly मेमोरी के साथ काम नहीं करता साथ ही, लीक हो चुके RawImage रैपर को हटा देता है भी.

अब JavaScript को डेटा खाली करने की चिंता करने की ज़रूरत नहीं है. साथ ही, वह अब इस तरह के नतीजे इस्तेमाल कर सकता है कचरा इकट्ठा करने वाला कोई अन्य ऑब्जेक्ट:

  // 

  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);
}

इसका मतलब यह भी है कि अब हमें C++ साइड पर कस्टम free_result बाइंडिंग की ज़रूरत नहीं है:

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());
}

कुल मिलाकर, हमारा रैपर कोड एक ही समय में ज़्यादा साफ़ और सुरक्षित, दोनों हो गया.

इसके बाद, मैंने ImageQuant रैपर के कोड में कुछ और छोटे सुधार किए और अन्य कोडेक के लिए, फिर से बनाए गए मेमोरी मैनेजमेंट से जुड़े सुधार. अगर आपको और जानकारी चाहिए, आप प्राप्त होने वाले PR को यहां देख सकते हैं: C++ के लिए मेमोरी सुधार कोडेक.

सीखने वाली अहम बातें

इस रीफ़ैक्टरिंग से हम क्या सीख सकते हैं और क्या शेयर कर सकते हैं? जिसे दूसरे कोडबेस पर लागू किया जा सकता है?

  • WebAssembly के बैक अप वाले मेमोरी व्यू का इस्तेमाल न करें. इससे कोई फ़र्क़ नहीं पड़ता कि इसे किस भाषा से बनाया गया है सिर्फ़ शुरू करना. यह खत्म होने के बाद भी उन पर भरोसा नहीं किया जा सकता और आपको पारंपरिक तरीकों से इन गड़बड़ियों का पता लगाया जा सकता है. इसलिए, अगर आपको बाद में डेटा सेव करने की ज़रूरत है, तो और उसे वहां सेव कर देता है.
  • अगर हो सके, तो मेमोरी मैनेज करने वाली किसी सुरक्षित भाषा का इस्तेमाल करें. इसके अलावा, कम से कम सुरक्षित टाइप के रैपर का इस्तेमाल करें रॉ पॉइंटर पर काम करता है. इससे, JavaScript VideoObject WebAssembly में आने वाली गड़बड़ियों से आपका बचाव नहीं होगा सीमा, लेकिन कम से कम यह स्टैटिक लैंग्वेज कोड से जुड़ी गड़बड़ियों की सरफ़ेस को कम कर देगा.
  • चाहे आप किसी भी भाषा का इस्तेमाल कर रहे हों, डेवलपमेंट के दौरान सैनिटाइज़र के साथ कोड चलाएं—वे इन कामों में आपकी मदद कर सकते हैं इसमें, सिर्फ़ स्टैटिक लैंग्वेज कोड की ही नहीं, बल्कि JavaScript की कुछ समस्याओं की जानकारी भी मिलती है 📚 WebAssembly सीमा, जैसे कि .delete() को कॉल करना भूलना या इससे अमान्य पॉइंटर पास करना में दी गई जानकारी है.
  • अगर हो सके, तो WebAssembly के मैनेज नहीं किए गए डेटा और ऑब्जेक्ट को JavaScript में पूरी तरह दिखाने से बचें. JavaScript एक ऐसी भाषा है जिसमें ग़ैर-ज़रूरी चीज़ें इकट्ठा होती हैं और इसमें मैन्युअल तरीके से मेमोरी मैनेज नहीं की जाती. इसे आपके WebAssembly में इस्तेमाल की गई भाषा के मेमोरी मॉडल का ऐब्स्ट्रैक्ट लीक माना जा सकता है को बनाया गया है और JavaScript कोड बेस में गलत मैनेजमेंट को आसानी से देखा जा सकता है.
  • यह स्पष्ट हो सकता है, लेकिन किसी भी अन्य कोडबेस की तरह, ग्लोबल में परिवर्तनशील स्थिति संग्रहित करने से बचें वैरिएबल. अगर आपको बार-बार इस्तेमाल किए जाने की वजह से आने वाली समस्याओं को डीबग नहीं करना है, तो हमारा लक्ष्य है कि आप इसमें ज़्यादा से ज़्यादा लोगों को शामिल करें.