[bug #68558] [PATCH] [troff] assertion failure in `do` request handler when given an invalid identifier

"G. Branden Robinson" <[email protected]> Wed, 29 Jul 2026 01:10:09 -0400 (EDT)
Newsgroups gmane.comp.printing.groff.bugs
Message-ID <[email protected]>
--8323329-424238335-1785301809=:3599353
Content-Type: TEXT/plain; CHARSET=utf-8
Content-Transfer-Encoding: QUOTED-PRINTABLE
Content-Disposition: inline

Follow-up Comment #4, bug #68558 (group groff):=0A=0AThanks, Dave.=0A=0AI u=
pdated the ChangeLog, but pushed before I remembered that I needed to take=
=0Ayour comment #3 into account.=0A=0AHere's the commit, but it is not corr=
ect.=0A=0A=0Acommit b3e0b25f085abf395a1fa7e8e139946fdf312106=0AAuthor: G. B=
randen Robinson <[email protected]>=0ADate:   Mon Jul 27 20:15:1=
5 2026 -0500=0A=0A    ChangeLog: Clarify Savannah #68558 defect history.=0A=
    =0A    The problem dates back to commit b04d345ed2 (groff 1.06), 1992-0=
9-01,=0A    which originally implemented the `do` request.  The request han=
dler=0A    disabled compatibility mode, then if it failed to read a valid=
=0A    identifier for any reason (invalid syntax, or no argument present),=
=0A    called `skip_line()`--meaning that the next input line would be=0A  =
  interpreted with compatibility mode disabled.  It did _not_ undo=0A    co=
mpatibility mode disablement and did not return early.=0A    =0A    Here's =
that original 1992 implementation.=0A    =0A    void do_request()=0A    {=
=0A      int saved_compatible_flag =3D compatible_flag;=0A      compatible_=
flag =3D 0;=0A      symbol nm =3D get_name();=0A      if (nm.is_null())=0A =
       skip_line();=0A      else=0A        interpolate_macro(nm);=0A      c=
ompatible_flag =3D saved_compatible_flag;=0A    }=0A    =0A    Since commit=
 205334d6e5, 2024-09-01, an argumentless `do` request no=0A    longer manip=
ulates compatibility mode, and since commit ba660bda6d,=0A    yesterday, th=
e request no longer does so for an invalid argument either.=0A=0A=0AAfter D=
ave's comment #3 I took a second look and now think that the bug came=0Ain =
with _groff_ 1.20.0.=0A=0AWe can see here that there is risk of a null poin=
ter being dereferenced.=0A=0A=0A$ git blame 1.20 -- src/roff/troff/input.cp=
p | sed -n 2619,2633p=0Ab04d345ed2 troff/input.cc           (James Clark   =
 1992-09-01 12:28:08 -0500=0A2619) void do_request()=0Ab04d345ed2 troff/inp=
ut.cc           (James Clark    1992-09-01 12:28:08 -0500=0A2620) {=0A9fbd2=
866b6 src/roff/troff/input.cc  (Werner LEMBERG 2001-05-06 23:29:21 +0000=0A=
2621)   int old_compatible_flag =3D compatible_flag;=0Ab04d345ed2 troff/inp=
ut.cc           (James Clark    1992-09-01 12:28:08 -0500=0A2622)   compati=
ble_flag =3D 0;=0Ab04d345ed2 troff/input.cc           (James Clark    1992-=
09-01 12:28:08 -0500=0A2623)   symbol nm =3D get_name();=0Ab04d345ed2 troff=
/input.cc           (James Clark    1992-09-01 12:28:08 -0500=0A2624)   if =
(nm.is_null())=0Ab04d345ed2 troff/input.cc           (James Clark    1992-0=
9-01 12:28:08 -0500=0A2625)     skip_line();=0Ab04d345ed2 troff/input.cc   =
        (James Clark    1992-09-01 12:28:08 -0500=0A2626)   else=0A6795d15c=
7b src/roff/troff/input.cpp (Werner LEMBERG 2008-09-25 07:47:38 +0000=0A262=
7)     interpolate_macro(nm, 1);=0A9fbd2866b6 src/roff/troff/input.cc  (Wer=
ner LEMBERG 2001-05-06 23:29:21 +0000=0A2628)   compatible_flag =3D old_com=
patible_flag;=0A6795d15c7b src/roff/troff/input.cpp (Werner LEMBERG 2008-09=
-25 07:47:38 +0000=0A2629)   request_or_macro *p =3D lookup_request(nm);=0A=
6795d15c7b src/roff/troff/input.cpp (Werner LEMBERG 2008-09-25 07:47:38 +00=
00=0A2630)   macro *m =3D p->to_macro();=0A6795d15c7b src/roff/troff/input.=
cpp (Werner LEMBERG 2008-09-25 07:47:38 +0000=0A2631)   if (m)=0A6795d15c7b=
 src/roff/troff/input.cpp (Werner LEMBERG 2008-09-25 07:47:38 +0000=0A2632)=
     tok.next();=0Ab04d345ed2 troff/input.cc           (James Clark    1992=
-09-01 12:28:08 -0500=0A2633) }=0A=0A=0ASee how control can flow from 2624 =
to 2628 if `nm.is_null()`, and thence to=0A2629.=0A=0AConsulting commit 679=
5d15c7b sheds much light on what was going on here.=0A=0A=0Acommit 6795d15c=
7bed7568853eb92c5334fb628b8110e2=0AAuthor: Werner LEMBERG <[email protected]>=0ADa=
te:   Thu Sep 25 07:47:38 2008 +0000=0A=0A    Fix incompatibility between `=
.de1' and `.do'.  Without this change,=0A    the following snippet=0A    =
=0A      .de1 xx=0A      .  tm \\n(.C=0A      ..=0A      .cp 1=0A      .do =
xx=0A    =0A    prints 1 instead of 0.=0A    =0A    * src/roff/troff/input.=
cc (do_request): If a macro gets processed,=0A    call tok.next().=0A    (i=
nterpolate_macro): Add optional argument.  Update callers.=0A    (request::=
invoke): Add optional argument.=0A    (macro::invoke): Add optional argumen=
t to delay call of tok.next().=0A    =0A    * src/roff/troff/request.h (req=
uest_or_macro): Add argument to=0A    `invoke' member.  Update all derived =
classes.=0A    =0A    * doc/groff.texinfo: Improve documentation of .do req=
uest.=0A=0Adiff --git a/ChangeLog b/ChangeLog=0Aindex 2f5cfa2ad..ed7a82585 =
100644=0A--- a/ChangeLog=0A+++ b/ChangeLog=0A@@ -1,3 +1,27 @@=0A+2008-09-24=
  Werner LEMBERG  <[email protected]>=0A+=0A+       Fix incompatibility between `.=
de1' and `.do'.  Without this change,=0A+       the following snippet=0A+=
=0A+         .de1 xx=0A+         .  tm \\n(.C=0A+         ..=0A+         .c=
p 1=0A+         .do xx=0A+=0A+       prints 1 instead of 0.=0A+=0A+       *=
 src/roff/troff/input.cc (do_request): If a macro gets processed,=0A+      =
 call tok.next().=0A+       (interpolate_macro): Add optional argument.  Up=
date callers.=0A+       (request::invoke): Add optional argument.=0A+      =
 (macro::invoke): Add optional argument to delay call of tok.next().=0A+=0A=
+       * src/roff/troff/request.h (request_or_macro): Add argument to=0A+ =
      `invoke' member.  Update all derived classes.=0A+=0A+       * doc/gro=
ff.texinfo: Improve documentation of .do request.=0A+=0A 2008-09-09  Werner=
 LEMBERG  <[email protected]>=0A =0A        * tmac/an-old.tmac (FT): Initialize pr=
operly.  Reported by Tadziu=0Adiff --git a/doc/groff.texinfo b/doc/groff.te=
xinfo=0Aindex 83bec1123..017ea32e1 100644=0A--- a/doc/groff.texinfo=0A+++ b=
/doc/groff.texinfo=0A@@ -14166,7 +14166,10 @@ @node Implementation Differen=
ces=0A option.=0A =0A The @code{do} request turns off compatibility mode=0A=
-while executing its arguments as a @code{gtroff} command.=0A+while executi=
ng its arguments as a @code{gtroff} command.  However, it=0A+does not turn =
off compatibility mode while processing the macro itself.=0A+To do that, us=
e the @code{de1} request (or manipulate the @code{.C}=0A+register manually)=
.  @xref{Writing Macros}.=0A =0A @Example=0A .do fam T=0Adiff --git a/src/r=
off/troff/input.cpp b/src/roff/troff/input.cpp=0Aindex bef9274bb..58e576b45=
 100644=0A--- a/src/roff/troff/input.cpp=0A+++ b/src/roff/troff/input.cpp=
=0A@@ -139,7 +139,7 @@ static symbol read_escape_name(read_mode =3D NO_ARGS=
);=0A static symbol read_long_escape_name(read_mode =3D NO_ARGS);=0A static=
 void interpolate_string(symbol);=0A static void interpolate_string_with_ar=
gs(symbol);=0A-static void interpolate_macro(symbol);=0A+static void interp=
olate_macro(symbol, int =3D 0);=0A static void interpolate_number_format(sy=
mbol);=0A static void interpolate_environment_variable(symbol);=0A =0A@@ -2=
601,8 +2601,12 @@ void do_request()=0A   if (nm.is_null())=0A     skip_line=
();=0A   else=0A-    interpolate_macro(nm);=0A+    interpolate_macro(nm, 1)=
;=0A   compatible_flag =3D old_compatible_flag;=0A+  request_or_macro *p =
=3D lookup_request(nm);=0A+  macro *m =3D p->to_macro();=0A+  if (m)=0A+   =
 tok.next();=0A }=0A =0A inline int possibly_handle_first_page_transition()=
=0A@@ -3006,7 +3010,7 @@ request::request(REQUEST_FUNCP pp) : p(pp)=0A {=0A=
 }=0A =0A-void request::invoke(symbol)=0A+void request::invoke(symbol, int)=
=0A {=0A   (*p)();=0A }=0A@@ -3716,7 +3720,7 @@ int operator=3D=3D(const ma=
cro &m1, const macro &m2)=0A   return 1;=0A }=0A =0A-static void interpolat=
e_macro(symbol nm)=0A+static void interpolate_macro(symbol nm, int no_next)=
=0A {=0A   request_or_macro *p =3D (request_or_macro *)request_dictionary.l=
ookup(nm);=0A   if (p =3D=3D 0) {=0A@@ -3745,7 +3749,7 @@ static void inter=
polate_macro(symbol nm)=0A     }=0A   }=0A   if (p)=0A-    p->invoke(nm);=
=0A+    p->invoke(nm, no_next);=0A   else {=0A     skip_line();=0A     retu=
rn;=0A@@ -3854,12 +3858,15 @@ static void decode_string_args(macro_iterator=
 *mi)=0A   }=0A }=0A =0A-void macro::invoke(symbol nm)=0A+void macro::invok=
e(symbol nm, int no_next)=0A {=0A   macro_iterator *mi =3D new macro_iterat=
or(nm, *this);=0A   decode_args(mi);=0A   input_stack::push(mi);=0A-  tok.n=
ext();=0A+  // we must delay tok.next() in case the function has been calle=
d by=0A+  // do_request to assure proper handling of compatible_flag=0A+  i=
f (!no_next)=0A+    tok.next();=0A }=0A =0A macro *macro::to_macro()=0Adiff=
 --git a/src/roff/troff/request.h b/src/roff/troff/request.h=0Aindex 24d258=
90f..098de7cc1 100644=0A--- a/src/roff/troff/request.h=0A+++ b/src/roff/tro=
ff/request.h=0A@@ -1,5 +1,5 @@=0A // -*- C++ -*-=0A-/* Copyright (C) 1989, =
1990, 1991, 1992, 2000, 2001, 2002, 2004=0A+/* Copyright (C) 1989, 1990, 19=
91, 1992, 2000, 2001, 2002, 2004, 2008=0A    Free Software Foundation, Inc.=
=0A      Written by James Clark ([email protected])=0A =0A@@ -26,14 +26,14 @@ =
class macro;=0A class request_or_macro : public object {=0A public:=0A   re=
quest_or_macro();=0A-  virtual void invoke(symbol s) =3D 0;=0A+  virtual vo=
id invoke(symbol, int) =3D 0;=0A   virtual macro *to_macro();=0A };=0A =0A =
class request : public request_or_macro {=0A   REQUEST_FUNCP p;=0A public:=
=0A-  void invoke(symbol);=0A+  void invoke(symbol, int);=0A   request(REQU=
EST_FUNCP);=0A };=0A =0A@@ -59,13 +59,13 @@ public:=0A   macro &operator=3D=
(const macro &);=0A   void append(unsigned char);=0A   void append(node *);=
=0A-  void append_unsigned(unsigned int i);=0A-  void append_int(int i);=0A=
+  void append_unsigned(unsigned int);=0A+  void append_int(int);=0A   void=
 append_str(const char *);=0A   void set(unsigned char, int);=0A   unsigned=
 char get(int);=0A   int length();=0A-  void invoke(symbol);=0A+  void invo=
ke(symbol, int);=0A   macro *to_macro();=0A   void print_size();=0A   int e=
mpty();=0A@@ -83,7 +83,7 @@ extern void init_node_requests();=0A extern voi=
d init_reg_requests();=0A extern void init_env_requests();=0A extern void i=
nit_hyphen_requests();=0A-extern void init_request(const char *s, REQUEST_F=
UNCP f);=0A+extern void init_request(const char *, REQUEST_FUNCP);=0A =0A c=
lass charinfo;=0A class environment;=0A=0A=0AI think I want to refactor thi=
s to eliminate this `int no_next` function=0Aparameter.  (It's now called `=
do_not_want_next_token`.)=0A=0AI have an idea for an alternative solution, =
already annotated in the source.=0A=0A=0A$ sed -n 9539,9584p src/roff/troff=
/input.cpp =0A=0A// Consume the rest of the input line in copy mode and ret=
urn it as a C=0A// string; if, after spaces, the argument starts with a `"`=
, discard it,=0A// letting any immediately subsequent spaces populate the r=
eturned=0A// string.=0A//=0A// The caller must subsequently call `tok.next(=
)`, _not_ `skip_line()`,=0A// to advance the input stream pointer.=0A//=0A/=
/ TODO: Synthesize a newline token and push it onto the input stream so=0A/=
/ that the foregoing becomes unnecessary, and request handlers can=0A// uni=
formly use `skip_line()`.=0A//=0A// The caller has responsibility for `dele=
te`ing the returned buffer.=0Achar *read_rest_of_line_as_argument()=0A{=0A =
 int buf_size =3D 256;=0A  char *s =3D new char[buf_size]; // C++03: new ch=
ar[buf_size]();=0A  (void) memset(s, 0, (buf_size * sizeof(char)));=0A  int=
 c =3D read_character_in_copy_mode(0 /* nullptr */);=0A  while (' ' =3D=3D =
c)=0A    c =3D read_character_in_copy_mode(0 /* nullptr */);=0A  if ('"' =
=3D=3D c)=0A    c =3D read_character_in_copy_mode(0 /* nullptr */);=0A  int=
 i =3D 0;=0A  while ((c !=3D '\n') && (c !=3D EOF)) {=0A    if (!is_invalid=
_input_char(c)) {=0A      if ((i + 2) > buf_size) {=0A        char *tem =3D=
 s;=0A        s =3D new char[buf_size * 2]; // C++03: new char[buf_size * 2=
]();=0A        (void) memset(s, 0, (buf_size * 2 * sizeof(char)));=0A      =
  memcpy(s, tem, buf_size);=0A        buf_size *=3D 2;=0A        delete[] t=
em;=0A      }=0A      s[i++] =3D c;=0A    }=0A    c =3D read_character_in_c=
opy_mode(0 /* nullptr */);=0A  }=0A  s[i] =3D '\0';=0A  if (0 =3D=3D i) {=
=0A    delete[] s;=0A    return 0 /* nullptr */;=0A  }=0A  return s;=0A}=0A=
=0A=0AI furthermore think that implementing this newline synthesis + unifor=
m=0A`skip_line()` approach will fix bug #44714.  I don't think the diagnosi=
s there=0Ais correct--I don't think compatibility mode management is implic=
ated at all.=0A(We now handle compatibility mode with with a proper STL sta=
ck of `bool`, and=0Adoing so didn't fix that problem.)=0A=0AI look forward =
to attempting to prove my conjectures in the _groff_ 1.26=0Adevelopment cyc=
le.=0A=0AAn issue still to be dealt with is request handlers that read argu=
ments in=0Acopy mode that _don't_ consume the rest of the input line.  I ne=
ed to see if=0Athere are any, and if so, they need to synthesize a newline =
and push it onto=0Athe token stream, but only if such an argument is the la=
st thing on the input=0Aline.  (They could be followed by ignored "extra" a=
rguments, or a comment=0Aescape sequence.)=0A=0A=0A    ____________________=
___________________________________=0A=0AReply to this item at:=0A=0A  <htt=
ps://savannah.gnu.org/bugs/?68558>=0A=0A___________________________________=
____________=0AMessage sent via Savannah=0Ahttps://savannah.gnu.org/=0A
--8323329-424238335-1785301809=:3599353
Content-Type: APPLICATION/pgp-signature; name=signature.asc

-----BEGIN PGP SIGNATURE-----

iHUEABYIAB0WIQQk97aszIMMAvLLwm6qLAuaBUf3TgUCammLMQAKCRCqLAuaBUf3
ToPdAQC8je3fgsRTYyQHx+boSPQ2BgGZQWQ9s9/yZ4YlfK4magD7BinF+arjh2WJ
gxKef4cNxy6vh3wu24hh7dvb2DulFws=
=W/Mu
-----END PGP SIGNATURE-----

--8323329-424238335-1785301809=:3599353--