diff options
| author | Paul Eggert | 2019-08-03 12:45:19 -0700 |
|---|---|---|
| committer | Paul Eggert | 2019-08-03 13:00:27 -0700 |
| commit | 13fe8a27042b1539d43727e6df97c386c61c3053 (patch) | |
| tree | e176e0687bfd1c7a427b03c170a1463e0a4b515a /src | |
| parent | b60b6ffb35f4ffbeecb73381e58712ff5cdd7e40 (diff) | |
| download | emacs-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.c | 107 |
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 | ||