aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2019-08-03 12:45:19 -0700
committerPaul Eggert2019-08-03 13:00:27 -0700
commit13fe8a27042b1539d43727e6df97c386c61c3053 (patch)
treee176e0687bfd1c7a427b03c170a1463e0a4b515a /src
parentb60b6ffb35f4ffbeecb73381e58712ff5cdd7e40 (diff)
downloademacs-13fe8a27042b1539d43727e6df97c386c61c3053.tar.gz
emacs-13fe8a27042b1539d43727e6df97c386c61c3053.zip
Fix rare undefined behaviors in replace-match
* src/search.c (Freplace_match): Simplify by caching search_regs components. Fix sanity check for out-of-range subscripts; it incorrectly allowed negative subscripts, subscripts equal to search_regs.num_regs, and it had undefined behavior for subscripts outside ptrdiff_t range. Improve wording of newly-introduced replace-match diagnostic. Rework use of opoint, to avoid setting point to an out-of-range value in rare cases involving modification hooks.
Diffstat (limited to 'src')
-rw-r--r--src/search.c107
1 files changed, 38 insertions, 69 deletions
diff --git a/src/search.c b/src/search.c
index 0e2ae059e81..9b674a58102 100644
--- a/src/search.c
+++ b/src/search.c
@@ -2389,44 +2389,32 @@ since only regular expressions have distinguished subexpressions. */)
2389 case_action = nochange; /* We tried an initialization */ 2389 case_action = nochange; /* We tried an initialization */
2390 /* but some C compilers blew it */ 2390 /* but some C compilers blew it */
2391 2391
2392 if (search_regs.num_regs <= 0) 2392 ptrdiff_t num_regs = search_regs.num_regs;
2393 if (num_regs <= 0)
2393 error ("`replace-match' called before any match found"); 2394 error ("`replace-match' called before any match found");
2394 2395
2395 if (NILP (subexp)) 2396 if (NILP (subexp))
2396 sub = 0; 2397 sub = 0;
2397 else 2398 else
2398 { 2399 {
2399 CHECK_FIXNUM (subexp); 2400 CHECK_RANGED_INTEGER (subexp, 0, num_regs - 1);
2400 sub = XFIXNUM (subexp); 2401 sub = XFIXNUM (subexp);
2401 /* Sanity check to see whether the subexp is larger than the
2402 allowed number of sub-regexps. */
2403 if (sub >= 0 && sub > search_regs.num_regs)
2404 args_out_of_range (subexp, make_fixnum (search_regs.num_regs));
2405 } 2402 }
2406 2403
2407 /* Check whether the subexpression to replace is greater than the 2404 ptrdiff_t sub_start = search_regs.start[sub];
2408 number of subexpressions in the regexp. */ 2405 ptrdiff_t sub_end = search_regs.end[sub];
2409 if (sub > 0 && search_regs.start[sub] == -1) 2406 eassert (sub_start <= sub_end);
2410 args_out_of_range (build_string ("Attempt to replace regexp subexpression that doesn't exist"),
2411 subexp);
2412 2407
2413 /* Sanity check to see whether the text to replace is present in the 2408 /* Check whether the text to replace is present in the buffer/string. */
2414 buffer/string. */ 2409 if (! (NILP (string)
2415 if (NILP (string)) 2410 ? BEGV <= sub_start && sub_end <= ZV
2411 : 0 <= sub_start && sub_end <= SCHARS (string)))
2416 { 2412 {
2417 if (search_regs.start[sub] < BEGV 2413 if (sub_start < 0)
2418 || search_regs.start[sub] > search_regs.end[sub] 2414 xsignal2 (Qerror,
2419 || search_regs.end[sub] > ZV) 2415 build_string ("replace-match subexpression does not exist"),
2420 args_out_of_range (make_fixnum (search_regs.start[sub]), 2416 subexp);
2421 make_fixnum (search_regs.end[sub])); 2417 args_out_of_range (make_fixnum (sub_start), make_fixnum (sub_end));
2422 }
2423 else
2424 {
2425 if (search_regs.start[sub] < 0
2426 || search_regs.start[sub] > search_regs.end[sub]
2427 || search_regs.end[sub] > SCHARS (string))
2428 args_out_of_range (make_fixnum (search_regs.start[sub]),
2429 make_fixnum (search_regs.end[sub]));
2430 } 2418 }
2431 2419
2432 if (NILP (fixedcase)) 2420 if (NILP (fixedcase))
@@ -2434,8 +2422,8 @@ since only regular expressions have distinguished subexpressions. */)
2434 /* Decide how to casify by examining the matched text. */ 2422 /* Decide how to casify by examining the matched text. */
2435 ptrdiff_t last; 2423 ptrdiff_t last;
2436 2424
2437 pos = search_regs.start[sub]; 2425 pos = sub_start;
2438 last = search_regs.end[sub]; 2426 last = sub_end;
2439 2427
2440 if (NILP (string)) 2428 if (NILP (string))
2441 pos_byte = CHAR_TO_BYTE (pos); 2429 pos_byte = CHAR_TO_BYTE (pos);
@@ -2511,9 +2499,8 @@ since only regular expressions have distinguished subexpressions. */)
2511 { 2499 {
2512 Lisp_Object before, after; 2500 Lisp_Object before, after;
2513 2501
2514 before = Fsubstring (string, make_fixnum (0), 2502 before = Fsubstring (string, make_fixnum (0), make_fixnum (sub_start));
2515 make_fixnum (search_regs.start[sub])); 2503 after = Fsubstring (string, make_fixnum (sub_end), Qnil);
2516 after = Fsubstring (string, make_fixnum (search_regs.end[sub]), Qnil);
2517 2504
2518 /* Substitute parts of the match into NEWTEXT 2505 /* Substitute parts of the match into NEWTEXT
2519 if desired. */ 2506 if desired. */
@@ -2542,12 +2529,12 @@ since only regular expressions have distinguished subexpressions. */)
2542 2529
2543 if (c == '&') 2530 if (c == '&')
2544 { 2531 {
2545 substart = search_regs.start[sub]; 2532 substart = sub_start;
2546 subend = search_regs.end[sub]; 2533 subend = sub_end;
2547 } 2534 }
2548 else if (c >= '1' && c <= '9') 2535 else if (c >= '1' && c <= '9')
2549 { 2536 {
2550 if (c - '0' < search_regs.num_regs 2537 if (c - '0' < num_regs
2551 && search_regs.start[c - '0'] >= 0) 2538 && search_regs.start[c - '0'] >= 0)
2552 { 2539 {
2553 substart = search_regs.start[c - '0']; 2540 substart = search_regs.start[c - '0'];
@@ -2612,13 +2599,8 @@ since only regular expressions have distinguished subexpressions. */)
2612 return concat3 (before, newtext, after); 2599 return concat3 (before, newtext, after);
2613 } 2600 }
2614 2601
2615 /* Record point, then move (quietly) to the start of the match. */ 2602 /* Record point. A nonpositive OPOINT is actually an offset from ZV. */
2616 if (PT >= search_regs.end[sub]) 2603 opoint = PT <= sub_start ? PT : max (PT, sub_end) - ZV;
2617 opoint = PT - ZV;
2618 else if (PT > search_regs.start[sub])
2619 opoint = search_regs.end[sub] - ZV;
2620 else
2621 opoint = PT;
2622 2604
2623 /* If we want non-literal replacement, 2605 /* If we want non-literal replacement,
2624 perform substitution on the replacement string. */ 2606 perform substitution on the replacement string. */
@@ -2687,7 +2669,7 @@ since only regular expressions have distinguished subexpressions. */)
2687 2669
2688 if (c == '&') 2670 if (c == '&')
2689 idx = sub; 2671 idx = sub;
2690 else if (c >= '1' && c <= '9' && c - '0' < search_regs.num_regs) 2672 else if ('1' <= c && c <= '9' && c - '0' < num_regs)
2691 { 2673 {
2692 if (search_regs.start[c - '0'] >= 1) 2674 if (search_regs.start[c - '0'] >= 1)
2693 idx = c - '0'; 2675 idx = c - '0';
@@ -2745,25 +2727,11 @@ since only regular expressions have distinguished subexpressions. */)
2745 xfree (substed); 2727 xfree (substed);
2746 } 2728 }
2747 2729
2748 /* The functions below modify the buffer, so they could trigger 2730 newpoint = sub_start + SCHARS (newtext);
2749 various modification hooks (see signal_before_change and 2731 ptrdiff_t newstart = sub_start == sub_end ? newpoint : sub_start;
2750 signal_after_change). If these hooks clobber the match data we
2751 error out since otherwise this will result in confusing bugs. */
2752 ptrdiff_t sub_start = search_regs.start[sub];
2753 ptrdiff_t sub_end = search_regs.end[sub];
2754 ptrdiff_t num_regs = search_regs.num_regs;
2755 newpoint = search_regs.start[sub] + SCHARS (newtext);
2756 2732
2757 /* Replace the old text with the new in the cleanest possible way. */ 2733 /* Replace the old text with the new in the cleanest possible way. */
2758 replace_range (search_regs.start[sub], search_regs.end[sub], 2734 replace_range (sub_start, sub_end, newtext, 1, 0, 1, true);
2759 newtext, 1, 0, 1, 1);
2760 /* Update saved data to match adjustment made by replace_range. */
2761 {
2762 ptrdiff_t change = newpoint - sub_end;
2763 if (sub_start >= sub_end)
2764 sub_start += change;
2765 sub_end += change;
2766 }
2767 2735
2768 if (case_action == all_caps) 2736 if (case_action == all_caps)
2769 Fupcase_region (make_fixnum (search_regs.start[sub]), 2737 Fupcase_region (make_fixnum (search_regs.start[sub]),
@@ -2773,17 +2741,18 @@ since only regular expressions have distinguished subexpressions. */)
2773 Fupcase_initials_region (make_fixnum (search_regs.start[sub]), 2741 Fupcase_initials_region (make_fixnum (search_regs.start[sub]),
2774 make_fixnum (newpoint)); 2742 make_fixnum (newpoint));
2775 2743
2776 if (search_regs.start[sub] != sub_start 2744 /* The replace_range etc. functions can trigger modification hooks
2777 || search_regs.end[sub] != sub_end 2745 (see signal_before_change and signal_after_change). Try to error
2778 || search_regs.num_regs != num_regs) 2746 out if these hooks clobber the match data since clobbering can
2747 result in confusing bugs. Although this sanity check does not
2748 catch all possible clobberings, it should catch many of them. */
2749 if (! (search_regs.num_regs == num_regs
2750 && search_regs.start[sub] == newstart
2751 && search_regs.end[sub] == newpoint))
2779 error ("Match data clobbered by buffer modification hooks"); 2752 error ("Match data clobbered by buffer modification hooks");
2780 2753
2781 /* Put point back where it was in the text. */ 2754 /* Put point back where it was in the text, if possible. */
2782 if (opoint <= 0) 2755 TEMP_SET_PT (clip_to_bounds (BEGV, opoint + (opoint <= 0 ? ZV : 0), ZV));
2783 TEMP_SET_PT (opoint + ZV);
2784 else
2785 TEMP_SET_PT (opoint);
2786
2787 /* Now move point "officially" to the start of the inserted replacement. */ 2756 /* Now move point "officially" to the start of the inserted replacement. */
2788 move_if_not_intangible (newpoint); 2757 move_if_not_intangible (newpoint);
2789 2758