summaryrefslogtreecommitdiff
path: root/transcode.c
diff options
context:
space:
mode:
authorPeter Zhu <peter@peterzhu.ca>2025-10-22 19:47:20 -0400
committerPeter Zhu <peter@peterzhu.ca>2025-10-24 18:34:13 -0400
commitcb302881629f997f403e705425f69e5f6b0741ac (patch)
tree2ad53ea8e1a5f12ee116de5d6fbc32a41a1b7bbc /transcode.c
parentfcae206232d923af0de7b0df3917e28f36e116f1 (diff)
Fix memory leak of transcoding when fallback raises
When the fallback function in transcode_loop raises, it will leak the memory in rb_econv_t. The following script reproduces the leak: 10.times do 100_000.times do "\ufffd".encode(Encoding::US_ASCII, fallback: proc { raise }) rescue end puts `ps -o rss= -p #{$$}` end Before: 451196 889980 1328508 1767676 2206460 2645372 3083900 3522428 3960956 4399484 After: 12508 12636 12892 12892 13148 13404 13532 13788 13916 13916
Diffstat (limited to 'transcode.c')
-rw-r--r--transcode.c30
1 files changed, 29 insertions, 1 deletions
diff --git a/transcode.c b/transcode.c
index 072e1942b1..09ecb1c651 100644
--- a/transcode.c
+++ b/transcode.c
@@ -2349,6 +2349,20 @@ aref_fallback(VALUE fallback, VALUE c)
return rb_funcallv_public(fallback, idAREF, 1, &c);
}
+struct transcode_loop_fallback_args {
+ VALUE (*fallback_func)(VALUE, VALUE);
+ VALUE fallback;
+ VALUE rep;
+};
+
+static VALUE
+transcode_loop_fallback_try(VALUE a)
+{
+ struct transcode_loop_fallback_args *args = (struct transcode_loop_fallback_args *)a;
+
+ return args->fallback_func(args->fallback, args->rep);
+}
+
static void
transcode_loop(const unsigned char **in_pos, unsigned char **out_pos,
const unsigned char *in_stop, unsigned char *out_stop,
@@ -2398,7 +2412,21 @@ transcode_loop(const unsigned char **in_pos, unsigned char **out_pos,
(const char *)ec->last_error.error_bytes_start,
ec->last_error.error_bytes_len,
rb_enc_find(ec->last_error.source_encoding));
- rep = (*fallback_func)(fallback, rep);
+
+
+ struct transcode_loop_fallback_args args = {
+ .fallback_func = fallback_func,
+ .fallback = fallback,
+ .rep = rep,
+ };
+
+ int state;
+ rep = rb_protect(transcode_loop_fallback_try, (VALUE)&args, &state);
+ if (state) {
+ rb_econv_close(ec);
+ rb_jump_tag(state);
+ }
+
if (!UNDEF_P(rep) && !NIL_P(rep)) {
StringValue(rep);
ret = rb_econv_insert_output(ec, (const unsigned char *)RSTRING_PTR(rep),