gbranden pushed a commit to branch master
in repository groff.

commit d50be3efd3e5b4d1e139c420a75be6686a318427
Author: G. Branden Robinson <[email protected]>
AuthorDate: Mon Sep 2 00:43:47 2024 -0500

    [troff]: Fix Savannah #61100.
    
    Prevent nested use of `do` requests from causing an assertion failure.
    
    * src/roff/troff/input.cpp: Improve handling of nested selections of
      compatibility mode.  Use a global STL stack of `bool`,
      `want_att_compat_stack`, to manage them.  Drop global `int`
      `do_old_want_att_compat` with its less-useful-than-hoped "-1" value to
      signal an outermost "scope".
    
      (do_request): Drop dubious `assert()`ion.  Push the value of
      `want_att_compat` onto the stack before invoking/calling the named
      request/macro, and pop it afterwards.
    
      (class enclosing_want_att_compat_reg): Add new class to handle dynamic
      construction of `.cp` register value.  I modeled the approach on an
      idiom heavily used in "env.cpp", where read-only registers are not
      modeled as accessors of C++ globals, because they interpolate
      per-environment state.
    
      (enclosing_want_att_compat_reg::get_string): Member function of new
      class constructs `.cp` register value by peeking at the top of
      `want_att_compat_stack`.
    
      (init_input_requests): Wire up `.cp` register to an object of
      `enclosing_want_att_compat_reg` class instead of now-dead
      `do_old_want_att_compat` global.
    
    Fixes <https://savannah.gnu.org/bugs/?66110>.  Problem introduced by me
    when introducing `.cp` register in commit 6a37bb5f00, 17 April 2020.
    Thanks to Bjarni Ingi Gislason for suggesting the direction a minimal
    reproducer could take.
---
 ChangeLog                                     | 30 +++++++++++++++++++++++++++
 src/roff/groff/tests/dot-cp-register-works.sh |  4 ++--
 src/roff/troff/input.cpp                      | 24 +++++++++++++++------
 3 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fd3ceb1e2..91cef8c25 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2024-09-02  G. Branden Robinson <[email protected]>
+
+       [troff]: Prevent nested use of `do` requests from causing an
+       assertion failure.
+
+       * src/roff/troff/input.cpp: Improve handling of nested
+       selections of compatibility mode.  Use a global STL stack of
+       `bool`, `want_att_compat_stack`, to manage them.  Drop global
+       `int` `do_old_want_att_compat` with its less-useful-than-hoped
+       "-1" value to signal an outermost "scope".
+       (do_request): Drop dubious `assert()`ion.  Push the value of
+       `want_att_compat` onto the stack before invoking/calling the
+       named request/macro, and pop it afterwards.
+       (class enclosing_want_att_compat_reg): Add new class to handle
+       dynamic construction of `.cp` register value.  I modeled the
+       approach on an idiom heavily used in "env.cpp", where read-only
+       registers are not modeled as accessors of C++ globals, because
+       they interpolate per-environment state.
+       (enclosing_want_att_compat_reg::get_string): Member function of
+       new class constructs `.cp` register value by peeking at the top
+       of `want_att_compat_stack`.
+       (init_input_requests): Wire up `.cp` register to an object of
+       `enclosing_want_att_compat_reg` class instead of now-dead
+       `do_old_want_att_compat` global.
+
+       Fixes <https://savannah.gnu.org/bugs/?66110>.  Problem
+       introduced by me when introducing `.cp` register in commit
+       6a37bb5f00, 17 April 2020.  Thanks to Bjarni Ingi Gislason for
+       suggesting the direction a minimal reproducer could take.
+
 2024-09-01  G. Branden Robinson <[email protected]>
 
        Regression-test Savannah #61100.
diff --git a/src/roff/groff/tests/dot-cp-register-works.sh 
b/src/roff/groff/tests/dot-cp-register-works.sh
index 963ab8d31..6aa513d4b 100755
--- a/src/roff/groff/tests/dot-cp-register-works.sh
+++ b/src/roff/groff/tests/dot-cp-register-works.sh
@@ -57,14 +57,14 @@ echo "$output"
 
 echo "checking value of '.cp' when not started in compatibility mode" \
   >&2
-echo "$output" | grep -Fqx "A 0 B 0 C 1 D 0 E 1 F -1" || wail
+echo "$output" | grep -Fqx "A 0 B 0 C 1 D 0 E 1 F 0" || wail
 
 output=$(printf "%s" "$input" | "$groff" -C -T ascii)
 echo "$output"
 
 echo "checking value of '.cp' when started in compatibility mode" \
   >&2
-echo "$output" | grep -Fqx "A 1 B 1 C 1 D 0 E 1 F -1" || wail
+echo "$output" | grep -Fqx "A 1 B 1 C 1 D 0 E 1 F 0" || wail
 
 test -z "$fail"
 
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
index 1e0447f3e..6ef735fa1 100644
--- a/src/roff/troff/input.cpp
+++ b/src/roff/troff/input.cpp
@@ -106,7 +106,6 @@ static symbol end_of_input_macro_name;
 static symbol blank_line_macro_name;
 static symbol leading_spaces_macro_name;
 static bool want_att_compat = false;
-static int do_old_want_att_compat = -1;        // for .do request
 bool want_abstract_output = false;
 bool want_output_suppressed = false;
 bool is_writing_html = false;
@@ -2899,6 +2898,8 @@ static void trapping_blank_line()
     blank_line();
 }
 
+std::stack<bool> want_att_compat_stack;
+
 void do_request()
 {
   if (!has_arg()) {
@@ -2907,16 +2908,16 @@ void do_request()
     skip_line();
     return;
   }
-  assert(do_old_want_att_compat == -1);
-  do_old_want_att_compat = want_att_compat;
+  want_att_compat_stack.push(want_att_compat);
   want_att_compat = false;
   symbol nm = get_name();
   if (nm.is_null())
     skip_line();
   else
     interpolate_macro(nm, true /* don't want next token */);
-  want_att_compat = do_old_want_att_compat;
-  do_old_want_att_compat = -1;
+  assert(!want_att_compat_stack.empty());
+  want_att_compat = want_att_compat_stack.top();
+  want_att_compat_stack.pop();
   request_or_macro *p = lookup_request(nm);
   macro *m = p->to_macro();
   if (m)
@@ -8058,6 +8059,17 @@ const char *break_flag_reg::get_string()
   return i_to_a(input_stack::get_break_flag());
 }
 
+class enclosing_want_att_compat_reg : public reg {
+public:
+  const char *get_string();
+};
+
+const char *enclosing_want_att_compat_reg::get_string()
+{
+  return i_to_a(want_att_compat_stack.empty() ? 0
+               : want_att_compat_stack.top());
+}
+
 class readonly_text_register : public reg {
   const char *s;
 public:
@@ -9007,7 +9019,7 @@ void init_input_requests()
   register_dictionary.define(".$", new nargs_reg);
   register_dictionary.define(".br", new break_flag_reg);
   register_dictionary.define(".C", new 
readonly_boolean_register(&want_att_compat));
-  register_dictionary.define(".cp", new 
readonly_register(&do_old_want_att_compat));
+  register_dictionary.define(".cp", new enclosing_want_att_compat_reg);
   register_dictionary.define(".O", new variable_reg(&suppression_level));
   register_dictionary.define(".c", new lineno_reg);
   register_dictionary.define(".color", new 
readonly_boolean_register(&want_color_output));

_______________________________________________
Groff-commit mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/groff-commit

Reply via email to