Skip to content

Commit 668ef75

Browse files
Refactor WASM memory management to prevent leaks
- Wrap all malloc/_free and _wasm_* usage in try/finally blocks - Ensure queryPtr, dataPtr, and resultPtr are always freed - Prevent memory leaks when errors occur in UTF8ToString, JSON.parse, or _wasm_* functions - Apply consistent memory management pattern across all async and sync functions - Refactor stringToPtr helper for better error handling - All 18 tests passing after refactoring Co-Authored-By: Dan Lynch <[email protected]>
1 parent f3ee929 commit 668ef75

File tree

2 files changed

+205
-126
lines changed

2 files changed

+205
-126
lines changed

wasm/index.cjs

Lines changed: 137 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ function awaitInit(fn) {
1717
function stringToPtr(str) {
1818
const len = wasmModule.lengthBytesUTF8(str) + 1;
1919
const ptr = wasmModule._malloc(len);
20-
wasmModule.stringToUTF8(str, ptr, len);
21-
return ptr;
20+
try {
21+
wasmModule.stringToUTF8(str, ptr, len);
22+
return ptr;
23+
} catch (error) {
24+
wasmModule._free(ptr);
25+
throw error;
26+
}
2227
}
2328

2429
function ptrToString(ptr) {
@@ -27,67 +32,90 @@ function ptrToString(ptr) {
2732

2833
const parseQuery = awaitInit(async (query) => {
2934
const queryPtr = stringToPtr(query);
30-
const resultPtr = wasmModule._wasm_parse_query(queryPtr);
31-
wasmModule._free(queryPtr);
32-
33-
const resultStr = ptrToString(resultPtr);
34-
wasmModule._wasm_free_string(resultPtr);
35-
36-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
37-
throw new Error(resultStr);
35+
let resultPtr;
36+
37+
try {
38+
resultPtr = wasmModule._wasm_parse_query(queryPtr);
39+
const resultStr = ptrToString(resultPtr);
40+
41+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
42+
throw new Error(resultStr);
43+
}
44+
45+
return JSON.parse(resultStr);
46+
} finally {
47+
wasmModule._free(queryPtr);
48+
if (resultPtr) {
49+
wasmModule._wasm_free_string(resultPtr);
50+
}
3851
}
39-
40-
return JSON.parse(resultStr);
4152
});
4253

4354
const deparse = awaitInit(async (parseTree) => {
4455
const msg = pg_query.ParseResult.fromObject(parseTree);
4556
const data = pg_query.ParseResult.encode(msg).finish();
4657

4758
const dataPtr = wasmModule._malloc(data.length);
48-
wasmModule.HEAPU8.set(data, dataPtr);
49-
50-
const resultPtr = wasmModule._wasm_deparse_protobuf(dataPtr, data.length);
51-
wasmModule._free(dataPtr);
52-
53-
const resultStr = ptrToString(resultPtr);
54-
wasmModule._wasm_free_string(resultPtr);
55-
56-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
57-
throw new Error(resultStr);
59+
let resultPtr;
60+
61+
try {
62+
wasmModule.HEAPU8.set(data, dataPtr);
63+
resultPtr = wasmModule._wasm_deparse_protobuf(dataPtr, data.length);
64+
const resultStr = ptrToString(resultPtr);
65+
66+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
67+
throw new Error(resultStr);
68+
}
69+
70+
return resultStr;
71+
} finally {
72+
wasmModule._free(dataPtr);
73+
if (resultPtr) {
74+
wasmModule._wasm_free_string(resultPtr);
75+
}
5876
}
59-
60-
return resultStr;
6177
});
6278

6379
const parsePlPgSQL = awaitInit(async (query) => {
6480
const queryPtr = stringToPtr(query);
65-
const resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
66-
wasmModule._free(queryPtr);
67-
68-
const resultStr = ptrToString(resultPtr);
69-
wasmModule._wasm_free_string(resultPtr);
70-
71-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
72-
throw new Error(resultStr);
81+
let resultPtr;
82+
83+
try {
84+
resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
85+
const resultStr = ptrToString(resultPtr);
86+
87+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
88+
throw new Error(resultStr);
89+
}
90+
91+
return JSON.parse(resultStr);
92+
} finally {
93+
wasmModule._free(queryPtr);
94+
if (resultPtr) {
95+
wasmModule._wasm_free_string(resultPtr);
96+
}
7397
}
74-
75-
return JSON.parse(resultStr);
7698
});
7799

78100
const fingerprint = awaitInit(async (query) => {
79101
const queryPtr = stringToPtr(query);
80-
const resultPtr = wasmModule._wasm_fingerprint(queryPtr);
81-
wasmModule._free(queryPtr);
82-
83-
const resultStr = ptrToString(resultPtr);
84-
wasmModule._wasm_free_string(resultPtr);
85-
86-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
87-
throw new Error(resultStr);
102+
let resultPtr;
103+
104+
try {
105+
resultPtr = wasmModule._wasm_fingerprint(queryPtr);
106+
const resultStr = ptrToString(resultPtr);
107+
108+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
109+
throw new Error(resultStr);
110+
}
111+
112+
return resultStr;
113+
} finally {
114+
wasmModule._free(queryPtr);
115+
if (resultPtr) {
116+
wasmModule._wasm_free_string(resultPtr);
117+
}
88118
}
89-
90-
return resultStr;
91119
});
92120

93121
// Sync versions that assume WASM module is already initialized
@@ -96,17 +124,23 @@ function parseQuerySync(query) {
96124
throw new Error('WASM module not initialized. Call an async method first to initialize.');
97125
}
98126
const queryPtr = stringToPtr(query);
99-
const resultPtr = wasmModule._wasm_parse_query(queryPtr);
100-
wasmModule._free(queryPtr);
101-
102-
const resultStr = ptrToString(resultPtr);
103-
wasmModule._wasm_free_string(resultPtr);
104-
105-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
106-
throw new Error(resultStr);
127+
let resultPtr;
128+
129+
try {
130+
resultPtr = wasmModule._wasm_parse_query(queryPtr);
131+
const resultStr = ptrToString(resultPtr);
132+
133+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
134+
throw new Error(resultStr);
135+
}
136+
137+
return JSON.parse(resultStr);
138+
} finally {
139+
wasmModule._free(queryPtr);
140+
if (resultPtr) {
141+
wasmModule._wasm_free_string(resultPtr);
142+
}
107143
}
108-
109-
return JSON.parse(resultStr);
110144
}
111145

112146
function deparseSync(parseTree) {
@@ -117,55 +151,72 @@ function deparseSync(parseTree) {
117151
const data = pg_query.ParseResult.encode(msg).finish();
118152

119153
const dataPtr = wasmModule._malloc(data.length);
120-
wasmModule.HEAPU8.set(data, dataPtr);
121-
122-
const resultPtr = wasmModule._wasm_deparse_protobuf(dataPtr, data.length);
123-
wasmModule._free(dataPtr);
124-
125-
const resultStr = ptrToString(resultPtr);
126-
wasmModule._wasm_free_string(resultPtr);
127-
128-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
129-
throw new Error(resultStr);
154+
let resultPtr;
155+
156+
try {
157+
wasmModule.HEAPU8.set(data, dataPtr);
158+
resultPtr = wasmModule._wasm_deparse_protobuf(dataPtr, data.length);
159+
const resultStr = ptrToString(resultPtr);
160+
161+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
162+
throw new Error(resultStr);
163+
}
164+
165+
return resultStr;
166+
} finally {
167+
wasmModule._free(dataPtr);
168+
if (resultPtr) {
169+
wasmModule._wasm_free_string(resultPtr);
170+
}
130171
}
131-
132-
return resultStr;
133172
}
134173

135174
function parsePlPgSQLSync(query) {
136175
if (!wasmModule) {
137176
throw new Error('WASM module not initialized. Call an async method first to initialize.');
138177
}
139178
const queryPtr = stringToPtr(query);
140-
const resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
141-
wasmModule._free(queryPtr);
142-
143-
const resultStr = ptrToString(resultPtr);
144-
wasmModule._wasm_free_string(resultPtr);
145-
146-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
147-
throw new Error(resultStr);
179+
let resultPtr;
180+
181+
try {
182+
resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
183+
const resultStr = ptrToString(resultPtr);
184+
185+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
186+
throw new Error(resultStr);
187+
}
188+
189+
return JSON.parse(resultStr);
190+
} finally {
191+
wasmModule._free(queryPtr);
192+
if (resultPtr) {
193+
wasmModule._wasm_free_string(resultPtr);
194+
}
148195
}
149-
150-
return JSON.parse(resultStr);
151196
}
152197

153198
function fingerprintSync(query) {
154199
if (!wasmModule) {
155200
throw new Error('WASM module not initialized. Call an async method first to initialize.');
156201
}
157202
const queryPtr = stringToPtr(query);
158-
const resultPtr = wasmModule._wasm_fingerprint(queryPtr);
159-
wasmModule._free(queryPtr);
160-
161-
const resultStr = ptrToString(resultPtr);
162-
wasmModule._wasm_free_string(resultPtr);
163-
164-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
165-
throw new Error(resultStr);
203+
let resultPtr;
204+
205+
try {
206+
resultPtr = wasmModule._wasm_fingerprint(queryPtr);
207+
const resultStr = ptrToString(resultPtr);
208+
209+
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.includes('ERROR')) {
210+
throw new Error(resultStr);
211+
}
212+
213+
return resultStr;
214+
} finally {
215+
wasmModule._free(queryPtr);
216+
if (resultPtr) {
217+
wasmModule._wasm_free_string(resultPtr);
218+
}
166219
}
167-
168-
return resultStr;
169220
}
170221

171222
module.exports = {

0 commit comments

Comments
 (0)