History

js: fix tiny leaks if js_try throws(!)

As it turns out, js_try can throw if it runs out of try-stack (without/before entering either the try part or the catch part). If it happens, then C code which does allocation -> try will leak. In mpv there were two places which do alloc and then try, one of them as part of the autofree system. On both cases the leak is the smallest possible (zero allocation) - talloc_new(NULL); It's very unlikely to trigger - an autofree mpv API should be called when the try-stack is exactly full, and our next try will throw (and guaranteed to get caught at an outer level, but with a leak). Fix that by doing the allocation inside the try block, so that if try throws before it's entered then nothing got allocated/leaked. Mujs internal code also has/had similar leaks, which are getting fixed around this time (July 2021, post mujs 1.1.3). [1] exhaust the try-stack or call-stack, whichever comes first: function kaboom() { try { kaboom() } catch(e) {} }

Avi Halachmi (:avih) committed 5 years ago

Commit 5ed03381 parent 36b7cff1 file · +41 −16Browse files

Showing the changes to player/javascript.c (1 of 1 file). Show all

Unified · Split

player/javascript.c+41−16
@@ -101,16 +101,25 @@ static uint64_t jsL_checkuint64(js_State *J, int idx); · 100 unmodified lines
101// padded with undefined if called with less, or bigger if called with more.101// padded with undefined if called with less, or bigger if called with more.
102//102//
103// - Almost all vm APIs (js_*) may throw an error - a longjmp to the last103// - Almost all vm APIs (js_*) may throw an error - a longjmp to the last
104// recovery/catch point, which could skip releasing resources. Use protected104// recovery/catch point, which could skip releasing resources. This includes
105// code (e.g. js_pcall) between aquisition and release. Alternatively, use105// js_try itself(!), except at the outer-most [1] js_try which is always
106// the autofree mechanism to manage it more easily. See more details below.106// entering the try part (and the catch part if the try part throws).
107// The assumption should be that anything can throw and needs careful setup.
108// One such automated setup is the autofree mechanism. Details later.
107//109//
108// - Unless named s_foo, all the functions at this file (inc. init) which110// - Unless named s_foo, all the functions at this file (inc. init) which
109// touch the vm may throw, but either cleanup resources regardless (mostly111// touch the vm may throw, but either cleanup resources regardless (mostly
110// autofree) or leave allocated resources on caller-provided talloc context112// autofree) or leave allocated resources on caller-provided talloc context
111// which the caller should release, typically with autofree (e.g. makenode).113// which the caller should release, typically with autofree (e.g. makenode).
112//114//
113// - Functions named s_foo (safe foo) never throw, return 0 on success, else 1.115// - Functions named s_foo (safe foo) never throw if called at the outer-most
116// try-levels, or, inside JS C functions - never throw after allocating.
117// If they didn't throw then they return 0 on success, 1 on js-errors.
118//
119// [1] In practice the N outer-most (nested) tries are guaranteed to enter the
120// try/carch code, where N is the mujs try-stack size (64 with mujs 1.1.3).
121// But because we can't track try-level at (called-back) JS C functions,
122// it's only guaranteed when we know we're near the outer-most try level.
114 123
115/**********************************************************************124/**********************************************************************
116 * mpv scripting API error handling125 * mpv scripting API error handling
@@ -201,13 +210,27 @@ static bool pushed_error(js_State *J, int err, int def) · 84 unmodified lines
201// inserted into the vm using af_newcfunction, but otherwise used normally.210// inserted into the vm using af_newcfunction, but otherwise used normally.
202//211//
203// To wrap an autofree function af_TARGET in C:212// To wrap an autofree function af_TARGET in C:
204// 1. Create a wrapper s_TARGET which runs af_TARGET safely inside js_try.213// 1. Create a wrapper s_TARGET which does this:
205// 2. Use s_TARGET like so (always autofree, and throws if af_TARGET threw):214// if (js_try(J))
206// void *af = talloc_new(NULL);215// return 1;
207// int r = s_TARGET(J, ..., af); // use J, af where the callee expects.216// *af = talloc_new(NULL);
217// af_TARGET(J, ..., *af);
218// js_endtry(J);
219// return 0;
220// 2. Use s_TARGET like so (frees if allocated, throws if {s,af}_TARGET threw):
221// void *af = NULL;
222// int r = s_TARGET(J, ..., &af); // use J, af where the callee expects.
208// talloc_free(af);223// talloc_free(af);
209// if (r)224// if (r)
210// js_throw(J);225// js_throw(J);
226//
227// The reason that the allocation happens inside try/catch is that js_try
228// itself can throw (if it runs out of try-stack) and therefore the code
229// inside the try part is not reached - but neither is the catch part(!),
230// and instead it throws to the next outer catch - but before we've allocated
231// anything, hence no leaks on such case. If js_try did get entered, then the
232// allocation happened, and then if af_TARGET threw then s_TARGET will catch
233// it (and return 1) and we'll free if afterwards.
211 234
212// add_af_file, add_af_dir, add_af_mpv_alloc take a valid FILE*/DIR*/char* value235// add_af_file, add_af_dir, add_af_mpv_alloc take a valid FILE*/DIR*/char* value
213// respectively, and fclose/closedir/mpv_free it when the parent is freed.236// respectively, and fclose/closedir/mpv_free it when the parent is freed.
@@ -266,11 +289,12 @@ static mpv_node *new_af_mpv_node(void *parent) · 52 unmodified lines
266typedef void (*af_CFunction)(js_State*, void*);289typedef void (*af_CFunction)(js_State*, void*);
267 290
268// safely run autofree js c function directly291// safely run autofree js c function directly
269static int s_run_af_jsc(js_State *J, af_CFunction fn, void *af)292static int s_run_af_jsc(js_State *J, af_CFunction fn, void **af)
270{293{
271 if (js_try(J))294 if (js_try(J))
272 return 1;295 return 1;
273 fn(J, af);296 *af = talloc_new(NULL);
297 fn(J, *af);
274 js_endtry(J);298 js_endtry(J);
275 return 0;299 return 0;
276}300}
@@ -285,8 +309,8 @@ static void script__autofree(js_State *J) · 8 unmodified lines
285 af_CFunction fn = (af_CFunction)js_touserdata(J, -1, "af_fn");309 af_CFunction fn = (af_CFunction)js_touserdata(J, -1, "af_fn");
286 js_pop(J, 2);310 js_pop(J, 2);
287 311
288 void *af = talloc_new(NULL);312 void *af = NULL;
289 int r = s_run_af_jsc(J, fn, af);313 int r = s_run_af_jsc(J, fn, &af);
290 talloc_free(af);314 talloc_free(af);
291 if (r)315 if (r)
292 js_throw(J);316 js_throw(J);
@@ -357,11 +381,12 @@ static void af_push_file(js_State *J, const char *fname, int limit, void *af) · 64 unmodified lines
357}381}
358 382
359// Safely run af_push_file.383// Safely run af_push_file.
360static int s_push_file(js_State *J, const char *fname, int limit, void *af)384static int s_push_file(js_State *J, const char *fname, int limit, void **af)
361{385{
362 if (js_try(J))386 if (js_try(J))
363 return 1;387 return 1;
364 af_push_file(J, fname, limit, af);388 *af = talloc_new(NULL);
389 af_push_file(J, fname, limit, *af);
365 js_endtry(J);390 js_endtry(J);
366 return 0;391 return 0;
367}392}
@@ -369,8 +394,8 @@ static int s_push_file(js_State *J, const char *fname, int limit, void *af) · 1 unmodified line
369// Called directly, push up to limit bytes of file fname (from builtin/os).394// Called directly, push up to limit bytes of file fname (from builtin/os).
370static void push_file_content(js_State *J, const char *fname, int limit)395static void push_file_content(js_State *J, const char *fname, int limit)
371{396{
372 void *af = talloc_new(NULL);397 void *af = NULL;
373 int r = s_push_file(J, fname, limit, af);398 int r = s_push_file(J, fname, limit, &af);
374 talloc_free(af);399 talloc_free(af);
375 if (r)400 if (r)
376 js_throw(J);401 js_throw(J);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t