1use std::borrow::Cow;
13use std::cmp::Ordering;
14use std::convert::Infallible;
15use std::fmt;
16use std::hash::{Hash, Hasher};
17use std::ops::Deref;
18
19use regex::{Error, RegexBuilder};
20use regex_syntax::ast::{self, Ast, ClassSetItem, Visitor};
21use serde::de::Error as DeError;
22use serde::ser::SerializeStruct;
23use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
24
25const MAX_REGEX_SIZE_AFTER_COMPILATION: usize = 10 * 1024 * 1024;
31
32const MAX_REGEX_SIZE_BEFORE_COMPILATION: usize = 1 * 1024 * 1024;
45
46const MAX_REGEX_CHARACTER_CLASSES: usize = 2000;
69
70struct CharacterClassCounter {
80 count: usize,
81}
82
83impl Visitor for CharacterClassCounter {
84 type Output = usize;
85 type Err = Infallible;
86
87 fn finish(self) -> Result<usize, Infallible> {
88 Ok(self.count)
89 }
90
91 fn visit_pre(&mut self, ast: &Ast) -> Result<(), Infallible> {
92 self.count += match ast {
93 Ast::ClassUnicode(_) | Ast::ClassPerl(_) => 1,
94 Ast::Empty(_)
96 | Ast::Flags(_)
97 | Ast::Literal(_)
98 | Ast::Dot(_)
99 | Ast::Assertion(_)
100 | Ast::ClassBracketed(_)
101 | Ast::Repetition(_)
102 | Ast::Group(_)
103 | Ast::Alternation(_)
104 | Ast::Concat(_) => 0,
105 };
106 Ok(())
107 }
108
109 fn visit_class_set_item_pre(&mut self, item: &ClassSetItem) -> Result<(), Infallible> {
110 self.count += match item {
113 ClassSetItem::Unicode(_)
114 | ClassSetItem::Perl(_)
115 | ClassSetItem::Ascii(_)
116 | ClassSetItem::Range(_) => 1,
117 ClassSetItem::Empty(_)
118 | ClassSetItem::Literal(_)
119 | ClassSetItem::Bracketed(_)
120 | ClassSetItem::Union(_) => 0,
121 };
122 Ok(())
123 }
124}
125
126fn count_character_classes(pattern: &str) -> Option<usize> {
132 let ast = ast::parse::Parser::new().parse(pattern).ok()?;
133 match ast::visit(&ast, CharacterClassCounter { count: 0 }) {
134 Ok(count) => Some(count),
135 Err(infallible) => match infallible {},
136 }
137}
138
139#[derive(Debug, Clone)]
166pub struct Regex {
167 pub case_insensitive: bool,
168 pub dot_matches_new_line: bool,
169 pub regex: regex::Regex,
170}
171
172impl Regex {
173 pub fn new(pattern: &str, case_insensitive: bool) -> Result<Regex, RegexCompilationError> {
177 Self::new_dot_matches_new_line(pattern, case_insensitive, true)
178 }
179
180 pub fn new_dot_matches_new_line(
182 pattern: &str,
183 case_insensitive: bool,
184 dot_matches_new_line: bool,
185 ) -> Result<Regex, RegexCompilationError> {
186 if pattern.len() > MAX_REGEX_SIZE_BEFORE_COMPILATION {
187 return Err(RegexCompilationError::PatternTooLarge {
188 pattern_size: pattern.len(),
189 });
190 }
191 if let Some(classes) = count_character_classes(pattern) {
192 if classes > MAX_REGEX_CHARACTER_CLASSES {
193 return Err(RegexCompilationError::TooManyCharacterClasses { classes });
194 }
195 }
196 let mut regex_builder = RegexBuilder::new(pattern);
197 regex_builder.case_insensitive(case_insensitive);
198 regex_builder.dot_matches_new_line(dot_matches_new_line);
199 regex_builder.size_limit(MAX_REGEX_SIZE_AFTER_COMPILATION);
200 Ok(Regex {
201 case_insensitive,
202 dot_matches_new_line,
203 regex: regex_builder.build()?,
204 })
205 }
206
207 pub fn pattern(&self) -> &str {
209 self.regex.as_str()
212 }
213}
214
215#[derive(Debug, Clone)]
217pub enum RegexCompilationError {
218 RegexError(Error),
220 PatternTooLarge { pattern_size: usize },
222 TooManyCharacterClasses { classes: usize },
224}
225
226impl fmt::Display for RegexCompilationError {
227 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 match self {
229 RegexCompilationError::RegexError(e) => write!(f, "{}", e),
230 RegexCompilationError::PatternTooLarge {
231 pattern_size: patter_size,
232 } => write!(
233 f,
234 "regex pattern too large ({} bytes, max {} bytes)",
235 patter_size, MAX_REGEX_SIZE_BEFORE_COMPILATION
236 ),
237 RegexCompilationError::TooManyCharacterClasses { classes } => write!(
238 f,
239 "regex pattern has too many character classes ({}, max {}). \
240 A character class is a Unicode, Perl or POSIX class such as `\\p{{L}}`, `\\d` \
241 or `[[:alpha:]]`, or a range such as `a-z`",
242 classes, MAX_REGEX_CHARACTER_CLASSES
243 ),
244 }
245 }
246}
247
248impl From<Error> for RegexCompilationError {
249 fn from(e: Error) -> Self {
250 RegexCompilationError::RegexError(e)
251 }
252}
253
254impl PartialEq<Regex> for Regex {
255 fn eq(&self, other: &Regex) -> bool {
256 self.pattern() == other.pattern()
257 && self.case_insensitive == other.case_insensitive
258 && self.dot_matches_new_line == other.dot_matches_new_line
259 }
260}
261
262impl Eq for Regex {}
263
264impl PartialOrd for Regex {
265 fn partial_cmp(&self, other: &Regex) -> Option<Ordering> {
266 Some(self.cmp(other))
267 }
268}
269
270impl Ord for Regex {
271 fn cmp(&self, other: &Regex) -> Ordering {
272 (
273 self.pattern(),
274 self.case_insensitive,
275 self.dot_matches_new_line,
276 )
277 .cmp(&(
278 other.pattern(),
279 other.case_insensitive,
280 other.dot_matches_new_line,
281 ))
282 }
283}
284
285impl Hash for Regex {
286 fn hash<H: Hasher>(&self, hasher: &mut H) {
287 self.pattern().hash(hasher);
288 self.case_insensitive.hash(hasher);
289 self.dot_matches_new_line.hash(hasher);
290 }
291}
292
293impl Deref for Regex {
294 type Target = regex::Regex;
295
296 fn deref(&self) -> ®ex::Regex {
297 &self.regex
298 }
299}
300
301impl Serialize for Regex {
302 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
303 where
304 S: Serializer,
305 {
306 let mut state = serializer.serialize_struct("Regex", 3)?;
307 state.serialize_field("pattern", &self.pattern())?;
308 state.serialize_field("case_insensitive", &self.case_insensitive)?;
309 state.serialize_field("dot_matches_new_line", &self.dot_matches_new_line)?;
310 state.end()
311 }
312}
313
314impl<'de> Deserialize<'de> for Regex {
315 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
316 where
317 D: Deserializer<'de>,
318 {
319 enum Field {
320 Pattern,
321 CaseInsensitive,
322 DotMatchesNewLine,
323 }
324
325 impl<'de> Deserialize<'de> for Field {
326 fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
327 where
328 D: Deserializer<'de>,
329 {
330 struct FieldVisitor;
331
332 impl<'de> de::Visitor<'de> for FieldVisitor {
333 type Value = Field;
334
335 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
336 formatter.write_str(
337 "pattern string or case_insensitive bool or dot_matches_new_line bool",
338 )
339 }
340
341 fn visit_str<E>(self, value: &str) -> Result<Field, E>
342 where
343 E: de::Error,
344 {
345 match value {
346 "pattern" => Ok(Field::Pattern),
347 "case_insensitive" => Ok(Field::CaseInsensitive),
348 "dot_matches_new_line" => Ok(Field::DotMatchesNewLine),
349 _ => Err(de::Error::unknown_field(value, FIELDS)),
350 }
351 }
352 }
353
354 deserializer.deserialize_identifier(FieldVisitor)
355 }
356 }
357
358 struct RegexVisitor;
359
360 impl<'de> de::Visitor<'de> for RegexVisitor {
361 type Value = Regex;
362
363 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
364 formatter.write_str("Regex serialized by the manual Serialize impl from above")
365 }
366
367 fn visit_seq<V>(self, mut seq: V) -> Result<Regex, V::Error>
368 where
369 V: de::SeqAccess<'de>,
370 {
371 let pattern = seq
372 .next_element::<Cow<str>>()?
373 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
374 let case_insensitive = seq
375 .next_element()?
376 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
377 let dot_matches_new_line = seq
378 .next_element()?
379 .ok_or_else(|| de::Error::invalid_length(2, &self))?;
380 Regex::new_dot_matches_new_line(&pattern, case_insensitive, dot_matches_new_line)
381 .map_err(|err| {
382 V::Error::custom(format!(
383 "Unable to recreate regex during deserialization: {}",
384 err
385 ))
386 })
387 }
388
389 fn visit_map<V>(self, mut map: V) -> Result<Regex, V::Error>
390 where
391 V: de::MapAccess<'de>,
392 {
393 let mut pattern: Option<Cow<str>> = None;
394 let mut case_insensitive: Option<bool> = None;
395 let mut dot_matches_new_line: Option<bool> = None;
396 while let Some(key) = map.next_key()? {
397 match key {
398 Field::Pattern => {
399 if pattern.is_some() {
400 return Err(de::Error::duplicate_field("pattern"));
401 }
402 pattern = Some(map.next_value()?);
403 }
404 Field::CaseInsensitive => {
405 if case_insensitive.is_some() {
406 return Err(de::Error::duplicate_field("case_insensitive"));
407 }
408 case_insensitive = Some(map.next_value()?);
409 }
410 Field::DotMatchesNewLine => {
411 if dot_matches_new_line.is_some() {
412 return Err(de::Error::duplicate_field("dot_matches_new_line"));
413 }
414 dot_matches_new_line = Some(map.next_value()?);
415 }
416 }
417 }
418 let pattern = pattern.ok_or_else(|| de::Error::missing_field("pattern"))?;
419 let case_insensitive =
420 case_insensitive.ok_or_else(|| de::Error::missing_field("case_insensitive"))?;
421 let dot_matches_new_line = dot_matches_new_line
422 .ok_or_else(|| de::Error::missing_field("dot_matches_new_line"))?;
423 Regex::new_dot_matches_new_line(&pattern, case_insensitive, dot_matches_new_line)
424 .map_err(|err| {
425 V::Error::custom(format!(
426 "Unable to recreate regex during deserialization: {}",
427 err
428 ))
429 })
430 }
431 }
432
433 const FIELDS: &[&str] = &["pattern", "case_insensitive", "dot_matches_new_line"];
434 deserializer.deserialize_struct("Regex", FIELDS, RegexVisitor)
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use std::alloc::{GlobalAlloc, Layout, System};
441 use std::cell::Cell;
442
443 use regex_syntax::hir::translate::TranslatorBuilder;
444
445 use super::*;
446
447 struct TrackingAllocator;
453
454 thread_local! {
455 static LIVE_BYTES: Cell<usize> = const { Cell::new(0) };
457 static PEAK_BYTES: Cell<usize> = const { Cell::new(0) };
459 }
460
461 fn record_alloc(size: usize) {
465 let _ = LIVE_BYTES.try_with(|live| {
466 let now = live.get().saturating_add(size);
467 live.set(now);
468 let _ = PEAK_BYTES.try_with(|peak| {
469 if now > peak.get() {
470 peak.set(now);
471 }
472 });
473 });
474 }
475
476 fn record_dealloc(size: usize) {
477 let _ = LIVE_BYTES.try_with(|live| live.set(live.get().saturating_sub(size)));
478 }
479
480 unsafe impl GlobalAlloc for TrackingAllocator {
484 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
485 let ptr = unsafe { System.alloc(layout) };
486 if !ptr.is_null() {
487 record_alloc(layout.size());
488 }
489 ptr
490 }
491
492 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
493 let ptr = unsafe { System.alloc_zeroed(layout) };
494 if !ptr.is_null() {
495 record_alloc(layout.size());
496 }
497 ptr
498 }
499
500 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
501 record_dealloc(layout.size());
502 unsafe { System.dealloc(ptr, layout) }
503 }
504
505 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
506 record_alloc(new_size);
509 let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
510 record_dealloc(if new_ptr.is_null() {
511 new_size
512 } else {
513 layout.size()
514 });
515 new_ptr
516 }
517 }
518
519 #[global_allocator]
520 static ALLOC: TrackingAllocator = TrackingAllocator;
521
522 fn peak_translate_bytes(pattern: &str, case_insensitive: bool) -> usize {
530 let base = LIVE_BYTES.with(|live| live.get());
531 PEAK_BYTES.with(|peak| peak.set(base));
532 {
533 let ast = ast::parse::Parser::new()
534 .parse(pattern)
535 .expect("pattern parses");
536 let mut translator = TranslatorBuilder::new();
537 translator.case_insensitive(case_insensitive);
538 translator.dot_matches_new_line(true);
539 let hir = translator
540 .build()
541 .translate(pattern, &ast)
542 .expect("pattern translates");
543 std::hint::black_box((&ast, &hir));
545 }
546 PEAK_BYTES.with(|peak| peak.get()).saturating_sub(base)
547 }
548
549 #[mz_ore::test]
553 #[cfg_attr(miri, ignore)] fn regex_class_heavy_pattern_rejected_before_compiling() {
555 let pattern = r"\p{L}".repeat(MAX_REGEX_SIZE_BEFORE_COMPILATION / r"\p{L}".len());
556 assert!(pattern.len() <= MAX_REGEX_SIZE_BEFORE_COMPILATION);
557 for case_insensitive in [true, false] {
559 let err = Regex::new(&pattern, case_insensitive).expect_err("must be rejected");
560 assert!(
561 matches!(err, RegexCompilationError::TooManyCharacterClasses { .. }),
562 "expected TooManyCharacterClasses, got {err:?}"
563 );
564 }
565 }
566
567 #[mz_ore::test]
571 #[cfg_attr(miri, ignore)] fn regex_wide_bracketed_range_counts_as_a_class() {
573 let unit = r"[a-\x{2FFF}]";
574 let pattern = unit.repeat(MAX_REGEX_SIZE_BEFORE_COMPILATION / unit.len());
575 for case_insensitive in [true, false] {
578 let err = Regex::new(&pattern, case_insensitive).expect_err("must be rejected");
579 assert!(
580 matches!(err, RegexCompilationError::TooManyCharacterClasses { .. }),
581 "expected TooManyCharacterClasses, got {err:?}"
582 );
583 }
584 }
585
586 #[mz_ore::test]
589 #[cfg_attr(miri, ignore)] fn regex_bracketed_class_is_counted() {
591 let unit = r"[\p{L}]";
592 let pattern = unit.repeat(MAX_REGEX_SIZE_BEFORE_COMPILATION / unit.len());
593 let err = Regex::new(&pattern, true).expect_err("must be rejected");
594 assert!(
595 matches!(err, RegexCompilationError::TooManyCharacterClasses { .. }),
596 "expected TooManyCharacterClasses, got {err:?}"
597 );
598 }
599
600 #[mz_ore::test]
603 #[cfg_attr(miri, ignore)] fn regex_class_limit_boundary_is_where_the_constant_puts_it() {
605 let unit = r"\p{L}";
606 assert!(
607 unit.len() * (MAX_REGEX_CHARACTER_CLASSES + 1) <= MAX_REGEX_SIZE_BEFORE_COMPILATION
608 );
609
610 let at_limit = Regex::new(&unit.repeat(MAX_REGEX_CHARACTER_CLASSES), false);
614 assert!(
615 !matches!(
616 at_limit,
617 Err(RegexCompilationError::TooManyCharacterClasses { .. })
618 ),
619 "a pattern at the limit must reach the compiler, got {at_limit:?}"
620 );
621
622 let err = Regex::new(&unit.repeat(MAX_REGEX_CHARACTER_CLASSES + 1), false)
623 .expect_err("must be rejected");
624 assert!(
625 matches!(err, RegexCompilationError::TooManyCharacterClasses { .. }),
626 "one class over the limit must be turned away, got {err:?}"
627 );
628 }
629
630 #[mz_ore::test]
633 #[cfg_attr(miri, ignore)] fn regex_long_literal_pattern_still_compiles() {
635 let pattern = vec!["abcdefgh"; 12_000].join("|");
638 assert!(pattern.len() > 100 * 1024);
639 assert_eq!(count_character_classes(&pattern), Some(0));
640 assert!(Regex::new(&pattern, true).is_ok());
641 }
642
643 #[mz_ore::test]
651 #[cfg_attr(miri, ignore)] fn regex_two_limits_bound_what_a_compile_spends() {
653 const MAX_MEMORY_PER_CHARACTER_CLASS: usize = 96 * 1024;
656 const MAX_MEMORY_PER_AST_NODE: usize = 768;
659
660 const CLASS_UNITS: usize = 150;
663 const CHEAP_UNITS: usize = 10_000;
666
667 let cases: &[(&str, bool, usize, usize)] = &[
669 (r"\p{Grapheme_Base}", true, CLASS_UNITS, 1),
672 (r"\p{XID_Continue}", true, CLASS_UNITS, 1),
673 (r"\p{Alphabetic}", true, CLASS_UNITS, 1),
674 (r"\p{L}", true, CLASS_UNITS, 1),
675 (r"\p{L}", false, CLASS_UNITS, 1),
676 (r"\w", true, CLASS_UNITS, 1),
677 (r"\W", true, CLASS_UNITS, 1),
678 (r"\d", true, CLASS_UNITS, 1),
679 (r"\s", true, CLASS_UNITS, 1),
680 (r"[\p{L}]", true, CLASS_UNITS, 1),
682 (r"[a\p{L}\d]", true, CLASS_UNITS, 2),
683 (r"[a-\x{10FFFF}]", true, CLASS_UNITS, 1),
688 (r"[^a-\x{10FFFF}]", true, CLASS_UNITS, 1),
689 (r"[a-\x{2FFF}]", true, CLASS_UNITS, 1),
690 (r"[\x{100}-\x{250}]", true, CLASS_UNITS, 1),
691 (r"[\x{100}-\x{17F}]", true, CLASS_UNITS, 1),
692 (r"[a-\x{FF}]", true, CLASS_UNITS, 1),
693 (r"[a-z]", true, CLASS_UNITS, 1),
694 (r"[[:alpha:]]", true, CLASS_UNITS, 1),
695 ("a", true, CHEAP_UNITS, 0),
699 ("a", false, CHEAP_UNITS, 0),
700 (".", true, CHEAP_UNITS, 0),
701 ("[abc]", true, CHEAP_UNITS, 0),
702 ("(a)", false, CHEAP_UNITS, 0),
703 ("a|", false, CHEAP_UNITS, 0),
704 ("a*", false, CHEAP_UNITS, 0),
705 ("a{2}", false, CHEAP_UNITS, 0),
706 ("^", false, CHEAP_UNITS, 0),
707 ];
708
709 for (unit, case_insensitive, repeats, classes_per_unit) in cases {
710 let pattern = unit.repeat(*repeats);
711 let expected_classes = repeats * classes_per_unit;
712 let classes = count_character_classes(&pattern).expect("pattern parses");
713 assert_eq!(
714 classes, expected_classes,
715 "`{unit}` x{repeats} counts as {classes} character classes, expected \
716 {expected_classes}"
717 );
718
719 let bound =
722 classes * MAX_MEMORY_PER_CHARACTER_CLASS + pattern.len() * MAX_MEMORY_PER_AST_NODE;
723 let measured = peak_translate_bytes(&pattern, *case_insensitive);
724 assert!(
725 measured <= bound,
726 "`{unit}` x{repeats} (case_insensitive: {case_insensitive}) allocated {measured} \
727 bytes, above the {bound} bytes the limits allow it, so they no longer bound what \
728 a compile spends"
729 );
730 }
731 }
732
733 #[mz_ore::test]
737 #[cfg_attr(miri, ignore)] fn regex_counted_repetition_rejected_by_size_limit() {
739 let err = Regex::new(r"\p{L}{200000}", true).expect_err("must be rejected");
740 assert!(
741 matches!(err, RegexCompilationError::RegexError(_)),
742 "expected the regex crate's own error, got {err:?}"
743 );
744 }
745
746 #[mz_ore::test]
748 #[cfg_attr(miri, ignore)] fn regex_ordinary_patterns_unaffected() {
750 for pattern in [
751 r"a+b",
752 r"^\d{3}-\d{4}$",
753 r"\p{L}+",
754 r"[a-zA-Z0-9_]*",
755 r"(?i)foo|bar",
756 ] {
757 assert!(
758 Regex::new(pattern, false).is_ok(),
759 "{pattern} should compile"
760 );
761 }
762 }
763
764 #[mz_ore::test]
767 #[cfg_attr(miri, ignore)] fn regex_unparseable_pattern_reports_regex_error() {
769 let err = Regex::new("(", false).expect_err("must be rejected");
770 assert!(
771 matches!(err, RegexCompilationError::RegexError(_)),
772 "expected the regex crate's own error, got {err:?}"
773 );
774 }
775
776 #[mz_ore::test]
780 fn regex_serde_case_insensitive() {
781 let pattern = "AAA";
782 let orig_regex = Regex::new(pattern, true).unwrap();
783 let serialized: String = serde_json::to_string(&orig_regex).unwrap();
784 let roundtrip_result: Regex = serde_json::from_str(&serialized).unwrap();
785 assert_eq!(orig_regex.regex.is_match("aaa"), true);
789 assert_eq!(roundtrip_result.regex.is_match("aaa"), true);
790 assert_eq!(pattern, roundtrip_result.pattern());
791 }
792
793 #[mz_ore::test]
796 fn regex_serde_dot_matches_new_line() {
797 {
798 let pattern = "A.*B";
800 let orig_regex = Regex::new_dot_matches_new_line(pattern, true, true).unwrap();
801 let serialized: String = serde_json::to_string(&orig_regex).unwrap();
802 let roundtrip_result: Regex = serde_json::from_str(&serialized).unwrap();
803 assert_eq!(orig_regex.regex.is_match("axxx\nxxxb"), true);
804 assert_eq!(roundtrip_result.regex.is_match("axxx\nxxxb"), true);
805 assert_eq!(pattern, roundtrip_result.pattern());
806 }
807 {
808 let pattern = "A.*B";
810 let orig_regex = Regex::new_dot_matches_new_line(pattern, true, false).unwrap();
811 let serialized: String = serde_json::to_string(&orig_regex).unwrap();
812 let roundtrip_result: Regex = serde_json::from_str(&serialized).unwrap();
813 assert_eq!(orig_regex.regex.is_match("axxx\nxxxb"), false);
814 assert_eq!(roundtrip_result.regex.is_match("axxx\nxxxb"), false);
815 assert_eq!(pattern, roundtrip_result.pattern());
816 }
817 {
818 let pattern = "A.*B";
820 let orig_regex = Regex::new(pattern, true).unwrap();
821 let serialized: String = serde_json::to_string(&orig_regex).unwrap();
822 let roundtrip_result: Regex = serde_json::from_str(&serialized).unwrap();
823 assert_eq!(orig_regex.regex.is_match("axxx\nxxxb"), true);
824 assert_eq!(roundtrip_result.regex.is_match("axxx\nxxxb"), true);
825 assert_eq!(pattern, roundtrip_result.pattern());
826 }
827 }
828
829 #[mz_ore::test]
830 fn regex_serde_from_reader() {
831 let pattern = "A.*B";
832 let orig_regex = Regex::new_dot_matches_new_line(pattern, true, true).unwrap();
833
834 let serialized: String = serde_json::to_string(&orig_regex).unwrap();
835 let roundtrip_result: Regex = serde_json::from_reader(serialized.as_bytes()).unwrap();
836
837 assert_eq!(orig_regex.regex.is_match("axxx\nxxxb"), true);
838 assert_eq!(roundtrip_result.regex.is_match("axxx\nxxxb"), true);
839 assert_eq!(pattern, roundtrip_result.pattern());
840
841 let serialized = bincode::serialize(&orig_regex).unwrap();
842 let roundtrip_result: Regex = bincode::deserialize_from(&*serialized).unwrap();
843
844 assert_eq!(orig_regex.regex.is_match("axxx\nxxxb"), true);
845 assert_eq!(roundtrip_result.regex.is_match("axxx\nxxxb"), true);
846 assert_eq!(pattern, roundtrip_result.pattern());
847 }
848}