re2 2.26.2 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/re2/re2.cc CHANGED
@@ -19,6 +19,7 @@
19
19
  #include <re2/set.h>
20
20
  #include <ruby.h>
21
21
  #include <ruby/encoding.h>
22
+ #include <ruby/thread.h>
22
23
 
23
24
  #define BOOL2RUBY(v) (v ? Qtrue : Qfalse)
24
25
 
@@ -41,8 +42,142 @@ typedef struct {
41
42
 
42
43
  typedef struct {
43
44
  RE2::Set *set;
45
+ bool compiled;
44
46
  } re2_set;
45
47
 
48
+ struct nogvl_match_arg {
49
+ const RE2 *pattern;
50
+ re2::StringPiece text;
51
+ size_t startpos;
52
+ size_t endpos;
53
+ RE2::Anchor anchor;
54
+ re2::StringPiece *matches;
55
+ int n;
56
+ bool matched;
57
+ };
58
+
59
+ static void *nogvl_match(void *ptr) {
60
+ auto *arg = static_cast<nogvl_match_arg *>(ptr);
61
+ #ifdef HAVE_ENDPOS_ARGUMENT
62
+ arg->matched = arg->pattern->Match(
63
+ arg->text, arg->startpos, arg->endpos,
64
+ arg->anchor, arg->matches, arg->n);
65
+ #else
66
+ arg->matched = arg->pattern->Match(
67
+ arg->text, arg->startpos,
68
+ arg->anchor, arg->matches, arg->n);
69
+ #endif
70
+ return nullptr;
71
+ }
72
+
73
+ static bool re2_match_without_gvl(
74
+ const RE2 *pattern, VALUE text, size_t startpos, size_t endpos,
75
+ RE2::Anchor anchor, re2::StringPiece *matches, int n) {
76
+ nogvl_match_arg arg;
77
+ arg.pattern = pattern;
78
+ arg.text = re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text));
79
+ arg.startpos = startpos;
80
+ arg.endpos = endpos;
81
+ arg.anchor = anchor;
82
+ arg.matches = matches;
83
+ arg.n = n;
84
+ arg.matched = false;
85
+
86
+ /* Abseil's synchronization primitives (SRWLOCK, SleepConditionVariableSRW)
87
+ * are incompatible with Ruby's Win32 Mutex-based GVL, causing
88
+ * WAIT_ABANDONED crashes when multiple threads match concurrently.
89
+ */
90
+ #ifdef _WIN32
91
+ nogvl_match(&arg);
92
+ #else
93
+ /* No unblocking function is needed: RE2 matching is CPU-bound computation,
94
+ * not a blocking system call, so a signal cannot safely interrupt it.
95
+ */
96
+ rb_thread_call_without_gvl(nogvl_match, &arg, NULL, NULL);
97
+ #endif
98
+
99
+ return arg.matched;
100
+ }
101
+
102
+ struct nogvl_set_match_arg {
103
+ const RE2::Set *set;
104
+ re2::StringPiece text;
105
+ std::vector<int> *v;
106
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
107
+ RE2::Set::ErrorInfo *error_info;
108
+ #endif
109
+ bool matched;
110
+ };
111
+
112
+ static void *nogvl_set_match(void *ptr) {
113
+ auto *arg = static_cast<nogvl_set_match_arg *>(ptr);
114
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
115
+ if (arg->error_info) {
116
+ arg->matched = arg->set->Match(arg->text, arg->v, arg->error_info);
117
+ } else {
118
+ arg->matched = arg->set->Match(arg->text, arg->v);
119
+ }
120
+ #else
121
+ arg->matched = arg->set->Match(arg->text, arg->v);
122
+ #endif
123
+ return nullptr;
124
+ }
125
+
126
+ struct nogvl_replace_arg {
127
+ std::string *str;
128
+ const RE2 *pattern;
129
+ re2::StringPiece string_pattern;
130
+ re2::StringPiece rewrite;
131
+ };
132
+
133
+ static void *nogvl_replace(void *ptr) {
134
+ auto *arg = static_cast<nogvl_replace_arg *>(ptr);
135
+ if (arg->pattern) {
136
+ RE2::Replace(arg->str, *arg->pattern, arg->rewrite);
137
+ } else {
138
+ RE2::Replace(arg->str, arg->string_pattern, arg->rewrite);
139
+ }
140
+ return nullptr;
141
+ }
142
+
143
+ static void *nogvl_global_replace(void *ptr) {
144
+ auto *arg = static_cast<nogvl_replace_arg *>(ptr);
145
+ if (arg->pattern) {
146
+ RE2::GlobalReplace(arg->str, *arg->pattern, arg->rewrite);
147
+ } else {
148
+ RE2::GlobalReplace(arg->str, arg->string_pattern, arg->rewrite);
149
+ }
150
+ return nullptr;
151
+ }
152
+
153
+ struct nogvl_extract_arg {
154
+ re2::StringPiece text;
155
+ const RE2 *pattern;
156
+ re2::StringPiece string_pattern;
157
+ re2::StringPiece rewrite;
158
+ std::string *out;
159
+ bool extracted;
160
+ };
161
+
162
+ static void *nogvl_extract(void *ptr) {
163
+ auto *arg = static_cast<nogvl_extract_arg *>(ptr);
164
+ if (arg->pattern) {
165
+ arg->extracted = RE2::Extract(arg->text, *arg->pattern,
166
+ arg->rewrite, arg->out);
167
+ } else {
168
+ arg->extracted = RE2::Extract(arg->text, RE2(arg->string_pattern),
169
+ arg->rewrite, arg->out);
170
+ }
171
+ return nullptr;
172
+ }
173
+
174
+ static std::string group_name(VALUE key) {
175
+ VALUE str = SYMBOL_P(key) ? rb_sym2str(key) : key;
176
+ str = rb_str_conv_enc(str, rb_enc_get(str), rb_utf8_encoding());
177
+
178
+ return std::string(RSTRING_PTR(str), RSTRING_LEN(str));
179
+ }
180
+
46
181
  VALUE re2_mRE2, re2_cRegexp, re2_cMatchData, re2_cScanner, re2_cSet,
47
182
  re2_eSetMatchError, re2_eSetUnsupportedError, re2_eRegexpUnsupportedError;
48
183
 
@@ -126,7 +261,7 @@ static void parse_re2_options(RE2::Options* re2_options, const VALUE options) {
126
261
  }
127
262
 
128
263
  static void re2_matchdata_mark(void *ptr) {
129
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
264
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
130
265
  rb_gc_mark_movable(m->regexp);
131
266
 
132
267
  /* Text must not be movable because StringPiece matches hold pointers into
@@ -136,12 +271,12 @@ static void re2_matchdata_mark(void *ptr) {
136
271
  }
137
272
 
138
273
  static void re2_matchdata_compact(void *ptr) {
139
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
274
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
140
275
  m->regexp = rb_gc_location(m->regexp);
141
276
  }
142
277
 
143
278
  static void re2_matchdata_free(void *ptr) {
144
- re2_matchdata *m = reinterpret_cast<re2_matchdata *>(ptr);
279
+ re2_matchdata *m = static_cast<re2_matchdata *>(ptr);
145
280
  if (m->matches) {
146
281
  delete[] m->matches;
147
282
  }
@@ -149,7 +284,7 @@ static void re2_matchdata_free(void *ptr) {
149
284
  }
150
285
 
151
286
  static size_t re2_matchdata_memsize(const void *ptr) {
152
- const re2_matchdata *m = reinterpret_cast<const re2_matchdata *>(ptr);
287
+ const re2_matchdata *m = static_cast<const re2_matchdata *>(ptr);
153
288
  size_t size = sizeof(*m);
154
289
  if (m->matches) {
155
290
  size += sizeof(*m->matches) * m->number_of_matches;
@@ -174,7 +309,7 @@ static const rb_data_type_t re2_matchdata_data_type = {
174
309
  };
175
310
 
176
311
  static void re2_scanner_mark(void *ptr) {
177
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
312
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
178
313
  rb_gc_mark_movable(s->regexp);
179
314
 
180
315
  /* Text must not be movable because the StringPiece input holds a pointer
@@ -184,12 +319,12 @@ static void re2_scanner_mark(void *ptr) {
184
319
  }
185
320
 
186
321
  static void re2_scanner_compact(void *ptr) {
187
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
322
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
188
323
  s->regexp = rb_gc_location(s->regexp);
189
324
  }
190
325
 
191
326
  static void re2_scanner_free(void *ptr) {
192
- re2_scanner *s = reinterpret_cast<re2_scanner *>(ptr);
327
+ re2_scanner *s = static_cast<re2_scanner *>(ptr);
193
328
  if (s->input) {
194
329
  delete s->input;
195
330
  }
@@ -197,7 +332,7 @@ static void re2_scanner_free(void *ptr) {
197
332
  }
198
333
 
199
334
  static size_t re2_scanner_memsize(const void *ptr) {
200
- const re2_scanner *s = reinterpret_cast<const re2_scanner *>(ptr);
335
+ const re2_scanner *s = static_cast<const re2_scanner *>(ptr);
201
336
  size_t size = sizeof(*s);
202
337
  if (s->input) {
203
338
  size += sizeof(*s->input);
@@ -222,7 +357,7 @@ static const rb_data_type_t re2_scanner_data_type = {
222
357
  };
223
358
 
224
359
  static void re2_regexp_free(void *ptr) {
225
- re2_pattern *p = reinterpret_cast<re2_pattern *>(ptr);
360
+ re2_pattern *p = static_cast<re2_pattern *>(ptr);
226
361
  if (p->pattern) {
227
362
  delete p->pattern;
228
363
  }
@@ -230,7 +365,7 @@ static void re2_regexp_free(void *ptr) {
230
365
  }
231
366
 
232
367
  static size_t re2_regexp_memsize(const void *ptr) {
233
- const re2_pattern *p = reinterpret_cast<const re2_pattern *>(ptr);
368
+ const re2_pattern *p = static_cast<const re2_pattern *>(ptr);
234
369
  size_t size = sizeof(*p);
235
370
  if (p->pattern) {
236
371
  size += sizeof(*p->pattern);
@@ -250,7 +385,7 @@ static const rb_data_type_t re2_regexp_data_type = {
250
385
  0,
251
386
  // IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
252
387
  // macro to update VALUE references, as to trigger write barriers.
253
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
388
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
254
389
  };
255
390
 
256
391
  static re2_pattern *unwrap_re2_regexp(VALUE self) {
@@ -285,9 +420,9 @@ static re2_scanner *unwrap_re2_scanner(VALUE self) {
285
420
  * in alphabetical order rather than definition order, as RE2 stores named
286
421
  * groups internally in a sorted map.
287
422
  *
288
- * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
289
- * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
290
- * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
423
+ * Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
424
+ * sets the `:utf8` option to false so strings will always be returned in
425
+ * UTF-8.
291
426
  *
292
427
  * @return [Array<String>] an array of names of named capturing groups
293
428
  * @example
@@ -300,9 +435,7 @@ static VALUE re2_regexp_names(const VALUE self) {
300
435
  VALUE names = rb_ary_new2(groups.size());
301
436
 
302
437
  for (const auto& group : groups) {
303
- rb_ary_push(names,
304
- encoded_str_new(group.first.data(), group.first.size(),
305
- p->pattern->options().encoding()));
438
+ rb_ary_push(names, rb_utf8_str_new(group.first.data(), group.first.size()));
306
439
  }
307
440
 
308
441
  return names;
@@ -396,6 +529,8 @@ static VALUE re2_scanner_rewind(VALUE self) {
396
529
  }
397
530
 
398
531
  static VALUE re2_scanner_initialize_copy(VALUE self, VALUE other) {
532
+ if (self == other) return self;
533
+
399
534
  re2_scanner *self_c;
400
535
  re2_scanner *other_c = unwrap_re2_scanner(other);
401
536
 
@@ -519,20 +654,13 @@ static re2::StringPiece *re2_matchdata_find_match(VALUE idx, const VALUE self) {
519
654
 
520
655
  if (RB_INTEGER_TYPE_P(idx)) {
521
656
  id = NUM2INT(idx);
522
- } else if (SYMBOL_P(idx)) {
523
- const auto& groups = p->pattern->NamedCapturingGroups();
524
- auto search = groups.find(rb_id2name(SYM2ID(idx)));
525
-
526
- if (search != groups.end()) {
527
- id = search->second;
528
- } else {
529
- return nullptr;
530
- }
531
657
  } else {
532
- StringValue(idx);
658
+ if (!SYMBOL_P(idx)) {
659
+ StringValue(idx);
660
+ }
533
661
 
534
662
  const auto& groups = p->pattern->NamedCapturingGroups();
535
- auto search = groups.find(std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)));
663
+ auto search = groups.find(group_name(idx));
536
664
 
537
665
  if (search != groups.end()) {
538
666
  id = search->second;
@@ -877,11 +1005,8 @@ static VALUE re2_matchdata_aref(int argc, VALUE *argv, const VALUE self) {
877
1005
  VALUE idx, rest;
878
1006
  rb_scan_args(argc, argv, "11", &idx, &rest);
879
1007
 
880
- if (TYPE(idx) == T_STRING) {
881
- return re2_matchdata_named_match(
882
- std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)), self);
883
- } else if (SYMBOL_P(idx)) {
884
- return re2_matchdata_named_match(rb_id2name(SYM2ID(idx)), self);
1008
+ if (TYPE(idx) == T_STRING || SYMBOL_P(idx)) {
1009
+ return re2_matchdata_named_match(group_name(idx), self);
885
1010
  } else if (!NIL_P(rest) || !RB_INTEGER_TYPE_P(idx) || NUM2INT(idx) < 0) {
886
1011
  return rb_ary_aref(argc, argv, re2_matchdata_to_a(self));
887
1012
  } else {
@@ -995,6 +1120,7 @@ static VALUE re2_matchdata_deconstruct(const VALUE self) {
995
1120
  * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
996
1121
  * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
997
1122
  * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1123
+ * Capturing group names will always be returned in UTF-8.
998
1124
  *
999
1125
  * @return [Hash] a hash of capturing group names to submatches
1000
1126
  * @param [Array<Symbol>, nil] keys an array of `Symbol` capturing group names
@@ -1024,7 +1150,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
1024
1150
  if (NIL_P(keys)) {
1025
1151
  for (const auto& group : groups) {
1026
1152
  rb_hash_aset(capturing_groups,
1027
- ID2SYM(rb_intern2(group.first.data(), group.first.size())),
1153
+ rb_str_intern(rb_utf8_str_new(group.first.data(), group.first.size())),
1028
1154
  re2_matchdata_nth_match(group.second, self));
1029
1155
  }
1030
1156
  } else {
@@ -1034,8 +1160,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
1034
1160
  for (int i = 0; i < RARRAY_LEN(keys); ++i) {
1035
1161
  VALUE key = rb_ary_entry(keys, i);
1036
1162
  Check_Type(key, T_SYMBOL);
1037
- const char *name = rb_id2name(SYM2ID(key));
1038
- auto search = groups.find(name);
1163
+ auto search = groups.find(group_name(key));
1039
1164
 
1040
1165
  if (search != groups.end()) {
1041
1166
  rb_hash_aset(capturing_groups, key, re2_matchdata_nth_match(search->second, self));
@@ -1055,6 +1180,7 @@ static VALUE re2_matchdata_deconstruct_keys(const VALUE self, const VALUE keys)
1055
1180
  * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1056
1181
  * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1057
1182
  * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1183
+ * Capturing group names will always be returned in UTF-8.
1058
1184
  *
1059
1185
  * @overload named_captures
1060
1186
  * Returns a hash with string keys.
@@ -1092,12 +1218,9 @@ static VALUE re2_matchdata_named_captures(int argc, VALUE *argv, const VALUE sel
1092
1218
  VALUE result = rb_hash_new();
1093
1219
 
1094
1220
  for (const auto& group : groups) {
1095
- VALUE key;
1221
+ VALUE key = rb_utf8_str_new(group.first.data(), group.first.size());
1096
1222
  if (symbolize) {
1097
- key = ID2SYM(rb_intern2(group.first.data(), group.first.size()));
1098
- } else {
1099
- key = encoded_str_new(group.first.data(), group.first.size(),
1100
- p->pattern->options().encoding());
1223
+ key = rb_str_intern(key);
1101
1224
  }
1102
1225
  rb_hash_aset(result, key, re2_matchdata_nth_match(group.second, self));
1103
1226
  }
@@ -1110,9 +1233,9 @@ static VALUE re2_matchdata_named_captures(int argc, VALUE *argv, const VALUE sel
1110
1233
  * alphabetical order rather than definition order, as RE2 stores named groups
1111
1234
  * internally in a sorted map.
1112
1235
  *
1113
- * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1114
- * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1115
- * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1236
+ * Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
1237
+ * sets the `:utf8` option to false so strings will always be returned in
1238
+ * UTF-8.
1116
1239
  *
1117
1240
  * @return [Array<String>] an array of names of named capturing groups
1118
1241
  * @example
@@ -1149,12 +1272,8 @@ static VALUE re2_matchdata_values_at(int argc, VALUE *argv, const VALUE self) {
1149
1272
  for (int i = 0; i < argc; ++i) {
1150
1273
  VALUE idx = argv[i];
1151
1274
 
1152
- if (TYPE(idx) == T_STRING) {
1153
- rb_ary_push(result, re2_matchdata_named_match(
1154
- std::string(RSTRING_PTR(idx), RSTRING_LEN(idx)), self));
1155
- } else if (SYMBOL_P(idx)) {
1156
- rb_ary_push(result, re2_matchdata_named_match(
1157
- rb_id2name(SYM2ID(idx)), self));
1275
+ if (TYPE(idx) == T_STRING || SYMBOL_P(idx)) {
1276
+ rb_ary_push(result, re2_matchdata_named_match(group_name(idx), self));
1158
1277
  } else {
1159
1278
  rb_ary_push(result, re2_matchdata_nth_match(NUM2INT(idx), self));
1160
1279
  }
@@ -1164,6 +1283,8 @@ static VALUE re2_matchdata_values_at(int argc, VALUE *argv, const VALUE self) {
1164
1283
  }
1165
1284
 
1166
1285
  static VALUE re2_matchdata_initialize_copy(VALUE self, VALUE other) {
1286
+ if (self == other) return self;
1287
+
1167
1288
  re2_matchdata *self_m;
1168
1289
  re2_matchdata *other_m = unwrap_re2_matchdata(other);
1169
1290
 
@@ -1249,26 +1370,23 @@ static VALUE re2_regexp_initialize(int argc, VALUE *argv, VALUE self) {
1249
1370
 
1250
1371
  TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, p);
1251
1372
 
1252
- if (p->pattern) {
1253
- delete p->pattern;
1254
- p->pattern = nullptr;
1255
- }
1373
+ RE2::Options re2_options;
1256
1374
 
1257
1375
  if (RTEST(options)) {
1258
- RE2::Options re2_options;
1259
1376
  parse_re2_options(&re2_options, options);
1260
-
1261
- p->pattern = new(std::nothrow) RE2(
1262
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)), re2_options);
1263
- } else {
1264
- p->pattern = new(std::nothrow) RE2(
1265
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)));
1266
1377
  }
1267
1378
 
1379
+ rb_check_frozen(self);
1380
+
1381
+ p->pattern = new(std::nothrow) RE2(
1382
+ re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)), re2_options);
1383
+
1268
1384
  if (p->pattern == nullptr) {
1269
1385
  rb_raise(rb_eNoMemError, "not enough memory to allocate RE2 object");
1270
1386
  }
1271
1387
 
1388
+ rb_obj_freeze(self);
1389
+
1272
1390
  return self;
1273
1391
  }
1274
1392
 
@@ -1278,6 +1396,8 @@ static VALUE re2_regexp_initialize_copy(VALUE self, VALUE other) {
1278
1396
 
1279
1397
  TypedData_Get_Struct(self, re2_pattern, &re2_regexp_data_type, self_p);
1280
1398
 
1399
+ rb_check_frozen(self);
1400
+
1281
1401
  if (self_p->pattern) {
1282
1402
  delete self_p->pattern;
1283
1403
  self_p->pattern = nullptr;
@@ -1289,6 +1409,8 @@ static VALUE re2_regexp_initialize_copy(VALUE self, VALUE other) {
1289
1409
  rb_raise(rb_eNoMemError, "not enough memory to allocate RE2 object");
1290
1410
  }
1291
1411
 
1412
+ rb_obj_freeze(self);
1413
+
1292
1414
  return self;
1293
1415
  }
1294
1416
 
@@ -1643,9 +1765,9 @@ static VALUE re2_regexp_number_of_capturing_groups(const VALUE self) {
1643
1765
  /*
1644
1766
  * Returns a hash of names to capturing indices of groups.
1645
1767
  *
1646
- * Note RE2 only supports UTF-8 and ISO-8859-1 encoding so strings will be
1647
- * returned in UTF-8 by default or ISO-8859-1 if the `:utf8` option for the
1648
- * {RE2::Regexp} is set to `false` (any other encoding's behaviour is undefined).
1768
+ * Note RE2 stores capturing group names as UTF-8, even if the {RE2::Regexp}
1769
+ * sets the `:utf8` option to false so strings will always be returned in
1770
+ * UTF-8.
1649
1771
  *
1650
1772
  * @return [Hash] a hash of names to capturing indices
1651
1773
  */
@@ -1656,8 +1778,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
1656
1778
 
1657
1779
  for (const auto& group : groups) {
1658
1780
  rb_hash_aset(capturing_groups,
1659
- encoded_str_new(group.first.data(), group.first.size(),
1660
- p->pattern->options().encoding()),
1781
+ rb_utf8_str_new(group.first.data(), group.first.size()),
1661
1782
  INT2FIX(group.second));
1662
1783
  }
1663
1784
 
@@ -1753,8 +1874,9 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1753
1874
 
1754
1875
  rb_scan_args(argc, argv, "11", &text, &options);
1755
1876
 
1756
- /* Ensure text is a string. */
1877
+ /* Coerce and freeze text to prevent mutation. */
1757
1878
  StringValue(text);
1879
+ text = rb_str_new_frozen(text);
1758
1880
 
1759
1881
  p = unwrap_re2_regexp(self);
1760
1882
 
@@ -1852,15 +1974,10 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1852
1974
  #endif
1853
1975
 
1854
1976
  if (n == 0) {
1855
- #ifdef HAVE_ENDPOS_ARGUMENT
1856
- bool matched = p->pattern->Match(
1857
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1858
- startpos, endpos, anchor, 0, 0);
1859
- #else
1860
- bool matched = p->pattern->Match(
1861
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1862
- startpos, anchor, 0, 0);
1863
- #endif
1977
+ bool matched = re2_match_without_gvl(
1978
+ p->pattern, text, startpos, endpos, anchor, 0, 0);
1979
+ RB_GC_GUARD(text);
1980
+
1864
1981
  return BOOL2RUBY(matched);
1865
1982
  } else {
1866
1983
  if (n == INT_MAX) {
@@ -1876,17 +1993,10 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1876
1993
  "not enough memory to allocate StringPieces for matches");
1877
1994
  }
1878
1995
 
1879
- text = rb_str_new_frozen(text);
1996
+ bool matched = re2_match_without_gvl(
1997
+ p->pattern, text, startpos, endpos, anchor, matches, n);
1998
+ RB_GC_GUARD(text);
1880
1999
 
1881
- #ifdef HAVE_ENDPOS_ARGUMENT
1882
- bool matched = p->pattern->Match(
1883
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1884
- startpos, endpos, anchor, matches, n);
1885
- #else
1886
- bool matched = p->pattern->Match(
1887
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
1888
- startpos, anchor, matches, n);
1889
- #endif
1890
2000
  if (matched) {
1891
2001
  VALUE matchdata = rb_class_new_instance(0, 0, re2_cMatchData);
1892
2002
  TypedData_Get_Struct(matchdata, re2_matchdata, &re2_matchdata_data_type, m);
@@ -1915,13 +2025,15 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1915
2025
  * @raise [TypeError] if text cannot be coerced to a `String`
1916
2026
  */
1917
2027
  static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
1918
- /* Ensure text is a string. */
1919
2028
  StringValue(text);
2029
+ text = rb_str_new_frozen(text);
1920
2030
 
1921
2031
  re2_pattern *p = unwrap_re2_regexp(self);
2032
+ bool matched = re2_match_without_gvl(
2033
+ p->pattern, text, 0, RSTRING_LEN(text), RE2::UNANCHORED, 0, 0);
2034
+ RB_GC_GUARD(text);
1922
2035
 
1923
- return BOOL2RUBY(RE2::PartialMatch(
1924
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)), *p->pattern));
2036
+ return BOOL2RUBY(matched);
1925
2037
  }
1926
2038
 
1927
2039
  /*
@@ -1934,13 +2046,15 @@ static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
1934
2046
  * @raise [TypeError] if text cannot be coerced to a `String`
1935
2047
  */
1936
2048
  static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
1937
- /* Ensure text is a string. */
1938
2049
  StringValue(text);
2050
+ text = rb_str_new_frozen(text);
1939
2051
 
1940
2052
  re2_pattern *p = unwrap_re2_regexp(self);
2053
+ bool matched = re2_match_without_gvl(
2054
+ p->pattern, text, 0, RSTRING_LEN(text), RE2::ANCHOR_BOTH, 0, 0);
2055
+ RB_GC_GUARD(text);
1941
2056
 
1942
- return BOOL2RUBY(RE2::FullMatch(
1943
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)), *p->pattern));
2057
+ return BOOL2RUBY(matched);
1944
2058
  }
1945
2059
 
1946
2060
  /*
@@ -1956,8 +2070,8 @@ static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
1956
2070
  * #=> #<RE2::Scanner:0x0000000000000001>
1957
2071
  */
1958
2072
  static VALUE re2_regexp_scan(const VALUE self, VALUE text) {
1959
- /* Ensure text is a string. */
1960
2073
  StringValue(text);
2074
+ text = rb_str_new_frozen(text);
1961
2075
 
1962
2076
  re2_pattern *p = unwrap_re2_regexp(self);
1963
2077
  re2_scanner *c;
@@ -1965,7 +2079,7 @@ static VALUE re2_regexp_scan(const VALUE self, VALUE text) {
1965
2079
  TypedData_Get_Struct(scanner, re2_scanner, &re2_scanner_data_type, c);
1966
2080
 
1967
2081
  RB_OBJ_WRITE(scanner, &c->regexp, self);
1968
- RB_OBJ_WRITE(scanner, &c->text, rb_str_new_frozen(text));
2082
+ RB_OBJ_WRITE(scanner, &c->text, text);
1969
2083
  c->input = new(std::nothrow) re2::StringPiece(
1970
2084
  RSTRING_PTR(c->text), RSTRING_LEN(c->text));
1971
2085
  if (c->input == nullptr) {
@@ -2028,35 +2142,51 @@ static VALUE re2_replace(VALUE, VALUE str, VALUE pattern,
2028
2142
  VALUE rewrite) {
2029
2143
  re2_pattern *p = nullptr;
2030
2144
 
2031
- /* Coerce all arguments before any C++ allocations so that any Ruby
2032
- * exceptions (via longjmp) cannot bypass C++ destructors and leak memory.
2145
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2146
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2147
+ * memory, and later coercions cannot mutate earlier strings.
2033
2148
  */
2034
2149
  StringValue(str);
2150
+ str = rb_str_new_frozen(str);
2035
2151
  if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2036
2152
  p = unwrap_re2_regexp(pattern);
2037
2153
  } else {
2038
2154
  StringValue(pattern);
2155
+ pattern = rb_str_new_frozen(pattern);
2039
2156
  }
2040
2157
  StringValue(rewrite);
2158
+ rewrite = rb_str_new_frozen(rewrite);
2041
2159
 
2042
- /* Take a copy of str so it can be modified in-place by
2043
- * RE2::Replace.
2044
- */
2160
+ /* Take a copy of str so it can be modified in-place by RE2::Replace. */
2045
2161
  std::string str_as_string(RSTRING_PTR(str), RSTRING_LEN(str));
2046
2162
 
2047
- /* Do the replacement. */
2163
+ nogvl_replace_arg arg;
2164
+ arg.str = &str_as_string;
2048
2165
  if (p) {
2049
- RE2::Replace(&str_as_string, *p->pattern,
2050
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2166
+ arg.pattern = p->pattern;
2167
+ } else {
2168
+ arg.pattern = nullptr;
2169
+ arg.string_pattern = re2::StringPiece(
2170
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2171
+ }
2172
+ arg.rewrite = re2::StringPiece(
2173
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2174
+
2175
+ #ifdef _WIN32
2176
+ nogvl_replace(&arg);
2177
+ #else
2178
+ rb_thread_call_without_gvl(nogvl_replace, &arg, NULL, NULL);
2179
+ #endif
2180
+
2181
+ RB_GC_GUARD(rewrite);
2182
+ RB_GC_GUARD(pattern);
2051
2183
 
2184
+ if (p) {
2052
2185
  return encoded_str_new(str_as_string.data(), str_as_string.size(),
2053
2186
  p->pattern->options().encoding());
2054
2187
  } else {
2055
- RE2::Replace(&str_as_string,
2056
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)),
2057
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2058
-
2059
- return encoded_str_new(str_as_string.data(), str_as_string.size(), RE2::Options::EncodingUTF8);
2188
+ return encoded_str_new(str_as_string.data(), str_as_string.size(),
2189
+ RE2::Options::EncodingUTF8);
2060
2190
  }
2061
2191
  }
2062
2192
 
@@ -2084,35 +2214,53 @@ static VALUE re2_global_replace(VALUE, VALUE str, VALUE pattern,
2084
2214
  VALUE rewrite) {
2085
2215
  re2_pattern *p = nullptr;
2086
2216
 
2087
- /* Coerce all arguments before any C++ allocations so that any Ruby
2088
- * exceptions (via longjmp) cannot bypass C++ destructors and leak memory.
2217
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2218
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2219
+ * memory, and later coercions cannot mutate earlier strings.
2089
2220
  */
2090
2221
  StringValue(str);
2222
+ str = rb_str_new_frozen(str);
2091
2223
  if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2092
2224
  p = unwrap_re2_regexp(pattern);
2093
2225
  } else {
2094
2226
  StringValue(pattern);
2227
+ pattern = rb_str_new_frozen(pattern);
2095
2228
  }
2096
2229
  StringValue(rewrite);
2230
+ rewrite = rb_str_new_frozen(rewrite);
2097
2231
 
2098
2232
  /* Take a copy of str so it can be modified in-place by
2099
2233
  * RE2::GlobalReplace.
2100
2234
  */
2101
2235
  std::string str_as_string(RSTRING_PTR(str), RSTRING_LEN(str));
2102
2236
 
2103
- /* Do the replacement. */
2237
+ nogvl_replace_arg arg;
2238
+ arg.str = &str_as_string;
2104
2239
  if (p) {
2105
- RE2::GlobalReplace(&str_as_string, *p->pattern,
2106
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2240
+ arg.pattern = p->pattern;
2241
+ } else {
2242
+ arg.pattern = nullptr;
2243
+ arg.string_pattern = re2::StringPiece(
2244
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2245
+ }
2246
+ arg.rewrite = re2::StringPiece(
2247
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2107
2248
 
2249
+ #ifdef _WIN32
2250
+ nogvl_global_replace(&arg);
2251
+ #else
2252
+ rb_thread_call_without_gvl(nogvl_global_replace, &arg, NULL, NULL);
2253
+ #endif
2254
+
2255
+ RB_GC_GUARD(rewrite);
2256
+ RB_GC_GUARD(pattern);
2257
+
2258
+ if (p) {
2108
2259
  return encoded_str_new(str_as_string.data(), str_as_string.size(),
2109
2260
  p->pattern->options().encoding());
2110
2261
  } else {
2111
- RE2::GlobalReplace(&str_as_string,
2112
- re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern)),
2113
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)));
2114
-
2115
- return encoded_str_new(str_as_string.data(), str_as_string.size(), RE2::Options::EncodingUTF8);
2262
+ return encoded_str_new(str_as_string.data(), str_as_string.size(),
2263
+ RE2::Options::EncodingUTF8);
2116
2264
  }
2117
2265
  }
2118
2266
 
@@ -2142,46 +2290,53 @@ static VALUE re2_extract(VALUE, VALUE text, VALUE pattern,
2142
2290
  VALUE rewrite) {
2143
2291
  re2_pattern *p = nullptr;
2144
2292
 
2145
- /* Coerce all arguments before any C++ allocations so that any Ruby
2146
- * exceptions (via longjmp) cannot bypass C++ destructors and leak memory.
2293
+ /* Coerce and freeze all arguments before any C++ allocations so that any
2294
+ * Ruby exceptions (via longjmp) cannot bypass C++ destructors and leak
2295
+ * memory, and later coercions cannot mutate earlier strings.
2147
2296
  */
2148
2297
  StringValue(text);
2298
+ text = rb_str_new_frozen(text);
2149
2299
  if (rb_obj_is_kind_of(pattern, re2_cRegexp)) {
2150
2300
  p = unwrap_re2_regexp(pattern);
2151
2301
  } else {
2152
2302
  StringValue(pattern);
2303
+ pattern = rb_str_new_frozen(pattern);
2153
2304
  }
2154
2305
  StringValue(rewrite);
2306
+ rewrite = rb_str_new_frozen(rewrite);
2155
2307
 
2156
2308
  std::string out;
2157
- bool extracted;
2158
2309
 
2310
+ nogvl_extract_arg arg;
2311
+ arg.text = re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text));
2159
2312
  if (p) {
2160
- extracted = RE2::Extract(
2161
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
2162
- *p->pattern,
2163
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)),
2164
- &out);
2165
-
2166
- if (extracted) {
2167
- return encoded_str_new(out.data(), out.size(),
2168
- p->pattern->options().encoding());
2169
- } else {
2170
- return Qnil;
2171
- }
2313
+ arg.pattern = p->pattern;
2172
2314
  } else {
2173
- extracted = RE2::Extract(
2174
- re2::StringPiece(RSTRING_PTR(text), RSTRING_LEN(text)),
2175
- RE2(re2::StringPiece(RSTRING_PTR(pattern), RSTRING_LEN(pattern))),
2176
- re2::StringPiece(RSTRING_PTR(rewrite), RSTRING_LEN(rewrite)),
2177
- &out);
2178
-
2179
- if (extracted) {
2180
- return encoded_str_new(out.data(), out.size(),
2181
- RE2::Options::EncodingUTF8);
2182
- } else {
2183
- return Qnil;
2184
- }
2315
+ arg.pattern = nullptr;
2316
+ arg.string_pattern = re2::StringPiece(
2317
+ RSTRING_PTR(pattern), RSTRING_LEN(pattern));
2318
+ }
2319
+ arg.rewrite = re2::StringPiece(
2320
+ RSTRING_PTR(rewrite), RSTRING_LEN(rewrite));
2321
+ arg.out = &out;
2322
+ arg.extracted = false;
2323
+
2324
+ #ifdef _WIN32
2325
+ nogvl_extract(&arg);
2326
+ #else
2327
+ rb_thread_call_without_gvl(nogvl_extract, &arg, NULL, NULL);
2328
+ #endif
2329
+
2330
+ RB_GC_GUARD(text);
2331
+ RB_GC_GUARD(rewrite);
2332
+ RB_GC_GUARD(pattern);
2333
+
2334
+ if (arg.extracted) {
2335
+ return encoded_str_new(out.data(), out.size(),
2336
+ p ? p->pattern->options().encoding()
2337
+ : RE2::Options::EncodingUTF8);
2338
+ } else {
2339
+ return Qnil;
2185
2340
  }
2186
2341
  }
2187
2342
 
@@ -2211,7 +2366,7 @@ static VALUE re2_escape(VALUE, VALUE unquoted) {
2211
2366
  }
2212
2367
 
2213
2368
  static void re2_set_free(void *ptr) {
2214
- re2_set *s = reinterpret_cast<re2_set *>(ptr);
2369
+ re2_set *s = static_cast<re2_set *>(ptr);
2215
2370
  if (s->set) {
2216
2371
  delete s->set;
2217
2372
  }
@@ -2219,7 +2374,7 @@ static void re2_set_free(void *ptr) {
2219
2374
  }
2220
2375
 
2221
2376
  static size_t re2_set_memsize(const void *ptr) {
2222
- const re2_set *s = reinterpret_cast<const re2_set *>(ptr);
2377
+ const re2_set *s = static_cast<const re2_set *>(ptr);
2223
2378
  size_t size = sizeof(*s);
2224
2379
  if (s->set) {
2225
2380
  size += sizeof(*s->set);
@@ -2239,7 +2394,7 @@ static const rb_data_type_t re2_set_data_type = {
2239
2394
  0,
2240
2395
  // IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
2241
2396
  // macro to update VALUE references, as to trigger write barriers.
2242
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
2397
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
2243
2398
  };
2244
2399
 
2245
2400
  static re2_set *unwrap_re2_set(VALUE self) {
@@ -2332,9 +2487,14 @@ static VALUE re2_set_initialize(int argc, VALUE *argv, VALUE self) {
2332
2487
  parse_re2_options(&re2_options, options);
2333
2488
  }
2334
2489
 
2490
+ rb_check_frozen(self);
2491
+
2492
+ /* Prevent re-initialisation: #match releases the GVL while holding a
2493
+ * pointer to s->set, so replacing s->set from another thread would free
2494
+ * the object out from under a concurrent match.
2495
+ */
2335
2496
  if (s->set) {
2336
- delete s->set;
2337
- s->set = nullptr;
2497
+ rb_raise(rb_eTypeError, "already initialized RE2::Set");
2338
2498
  }
2339
2499
 
2340
2500
  s->set = new(std::nothrow) RE2::Set(re2_options, re2_anchor);
@@ -2362,6 +2522,7 @@ static VALUE re2_set_add(VALUE self, VALUE pattern) {
2362
2522
  StringValue(pattern);
2363
2523
 
2364
2524
  re2_set *s = unwrap_re2_set(self);
2525
+ rb_check_frozen(self);
2365
2526
 
2366
2527
  int index;
2367
2528
  VALUE msg;
@@ -2393,8 +2554,16 @@ static VALUE re2_set_add(VALUE self, VALUE pattern) {
2393
2554
  */
2394
2555
  static VALUE re2_set_compile(VALUE self) {
2395
2556
  re2_set *s = unwrap_re2_set(self);
2557
+ rb_check_frozen(self);
2558
+
2559
+ bool compiled = s->set->Compile();
2396
2560
 
2397
- return BOOL2RUBY(s->set->Compile());
2561
+ if (compiled) {
2562
+ s->compiled = true;
2563
+ rb_obj_freeze(self);
2564
+ }
2565
+
2566
+ return BOOL2RUBY(compiled);
2398
2567
  }
2399
2568
 
2400
2569
  /*
@@ -2491,6 +2660,8 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2491
2660
  rb_scan_args(argc, argv, "11", &str, &options);
2492
2661
 
2493
2662
  StringValue(str);
2663
+ str = rb_str_new_frozen(str);
2664
+
2494
2665
  re2_set *s = unwrap_re2_set(self);
2495
2666
 
2496
2667
  if (RTEST(options)) {
@@ -2506,9 +2677,27 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2506
2677
 
2507
2678
  if (raise_exception) {
2508
2679
  #ifdef HAVE_ERROR_INFO_ARGUMENT
2680
+ /* RE2::Set::Match dereferences a null program after a failed Compile. */
2681
+ if (!s->compiled) {
2682
+ rb_raise(re2_eSetMatchError, "#match must not be called before #compile");
2683
+ }
2684
+
2509
2685
  RE2::Set::ErrorInfo e;
2510
- bool match_failed = !s->set->Match(
2511
- re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str)), &v, &e);
2686
+ nogvl_set_match_arg arg;
2687
+ arg.set = s->set;
2688
+ arg.text = re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str));
2689
+ arg.v = &v;
2690
+ arg.error_info = &e;
2691
+ arg.matched = false;
2692
+
2693
+ #ifdef _WIN32
2694
+ nogvl_set_match(&arg);
2695
+ #else
2696
+ rb_thread_call_without_gvl(nogvl_set_match, &arg, NULL, NULL);
2697
+ #endif
2698
+ RB_GC_GUARD(str);
2699
+
2700
+ bool match_failed = !arg.matched;
2512
2701
  VALUE result = rb_ary_new2(v.size());
2513
2702
 
2514
2703
  if (match_failed) {
@@ -2535,11 +2724,29 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2535
2724
  rb_raise(re2_eSetUnsupportedError, "current version of RE2::Set::Match() does not output error information, :exception option can only be set to false");
2536
2725
  #endif
2537
2726
  } else {
2538
- bool matched = s->set->Match(
2539
- re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str)), &v);
2727
+ if (!s->compiled) {
2728
+ return rb_ary_new();
2729
+ }
2730
+
2731
+ nogvl_set_match_arg arg;
2732
+ arg.set = s->set;
2733
+ arg.text = re2::StringPiece(RSTRING_PTR(str), RSTRING_LEN(str));
2734
+ arg.v = &v;
2735
+ #ifdef HAVE_ERROR_INFO_ARGUMENT
2736
+ arg.error_info = nullptr;
2737
+ #endif
2738
+ arg.matched = false;
2739
+
2740
+ #ifdef _WIN32
2741
+ nogvl_set_match(&arg);
2742
+ #else
2743
+ rb_thread_call_without_gvl(nogvl_set_match, &arg, NULL, NULL);
2744
+ #endif
2745
+ RB_GC_GUARD(str);
2746
+
2540
2747
  VALUE result = rb_ary_new2(v.size());
2541
2748
 
2542
- if (matched) {
2749
+ if (arg.matched) {
2543
2750
  for (int index : v) {
2544
2751
  rb_ary_push(result, INT2FIX(index));
2545
2752
  }
@@ -2550,6 +2757,8 @@ static VALUE re2_set_match(int argc, VALUE *argv, const VALUE self) {
2550
2757
  }
2551
2758
 
2552
2759
  extern "C" void Init_re2(void) {
2760
+ rb_ext_ractor_safe(true);
2761
+
2553
2762
  re2_mRE2 = rb_define_module("RE2");
2554
2763
  re2_cRegexp = rb_define_class_under(re2_mRE2, "Regexp", rb_cObject);
2555
2764
  re2_eRegexpUnsupportedError = rb_define_class_under(re2_cRegexp,