diff --git a/mrbgems/mruby-regexp/include/re_internal.h b/mrbgems/mruby-regexp/include/re_internal.h index 2c66eda1f..07e022677 100644 --- a/mrbgems/mruby-regexp/include/re_internal.h +++ b/mrbgems/mruby-regexp/include/re_internal.h @@ -67,6 +67,7 @@ typedef struct mrb_regexp_pattern { uint16_t num_captures; /* number of capture groups (including group 0) */ uint32_t flags; re_named_capture *named_captures; + char *named_arena; /* owned storage for named_captures[i].name; NULL if num_named == 0 */ uint16_t num_named; mrb_bool has_backref; /* true if pattern uses \1-\9 */ mrb_bool needs_backtrack; /* true if pattern needs backtracking engine */ diff --git a/mrbgems/mruby-regexp/src/re_compile.c b/mrbgems/mruby-regexp/src/re_compile.c index c65e6c223..9ffcab203 100644 --- a/mrbgems/mruby-regexp/src/re_compile.c +++ b/mrbgems/mruby-regexp/src/re_compile.c @@ -884,7 +884,26 @@ re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags) pat->num_captures = c.num_captures; pat->flags = flags; pat->named_captures = c.named_captures; + pat->named_arena = NULL; pat->num_named = c.num_named; + + /* Copy capture names into an owned arena. Until this point the names + point into the pattern source (or into c.stripped, which gets freed + below in /x mode). After this loop the regexp owns its names. */ + if (c.num_named > 0) { + size_t total = 0; + for (uint16_t i = 0; i < c.num_named; i++) total += c.named_captures[i].name_len; + if (total > 0) { + pat->named_arena = (char*)mrb_malloc(mrb, total); + size_t off = 0; + for (uint16_t i = 0; i < c.num_named; i++) { + uint16_t n = c.named_captures[i].name_len; + memcpy(pat->named_arena + off, c.named_captures[i].name, n); + pat->named_captures[i].name = pat->named_arena + off; + off += n; + } + } + } pat->has_backref = c.has_backref; pat->needs_backtrack = c.needs_backtrack; @@ -959,6 +978,7 @@ re_free(mrb_state *mrb, mrb_regexp_pattern *pat) mrb_free(mrb, pat->code); mrb_free(mrb, pat->classes); mrb_free(mrb, pat->named_captures); + mrb_free(mrb, pat->named_arena); mrb_free(mrb, pat->prefix); mrb_free(mrb, pat->cached_visited); mrb_free(mrb, pat->cached_threads[0]); diff --git a/mrbgems/mruby-regexp/test/regexp.rb b/mrbgems/mruby-regexp/test/regexp.rb index 9b48ccc1f..8f1499c27 100644 --- a/mrbgems/mruby-regexp/test/regexp.rb +++ b/mrbgems/mruby-regexp/test/regexp.rb @@ -361,6 +361,25 @@ assert("MatchData#named_captures") do assert_equal "host", nc["b"] end +assert("Regexp - named captures survive /x preprocessing") do + # Regression: with /x, re_compile freed the stripped buffer that + # named_captures[i].name pointed into. + re = /(?\d+) # comment + \s* (?\w+) /x + m = re.match("42 px") + assert_equal "42", m[:n] + assert_equal "px", m[:u] +end + +assert("Regexp - named captures survive source string mutation") do + # Regression: name pointer used to alias RSTRING_PTR of the source. + s = String.new("(?\\d+)") + re = Regexp.new(s) + s.replace("X" * 10000) # force buffer reallocation + m = re.match("abc 123 def") + assert_equal "123", m[:key] +end + assert("Regexp - positive lookahead (?=...)") do md = /\w+(?=@)/.match("user@host") assert_equal "user", md[0]