Subject: bug#75581: 30.0.93; Crash when copy-sequence on clear-string'ed multibyte strings with text properties

From: Eli Zaretskii <[email-masked]>
Date: Thu, 16 Jan 2025 14:57:34 +0200

> Date: Thu, 16 Jan 2025 12:23:34 +0000
> From: Pip Cet <[email-masked]>
> Cc: [email-masked], [email-masked], [email-masked], [email-masked]
> 
> >> ??? Which other *_SET_* macro assumes it is passed an lval, and turns it
> >> into an object with a different identity?  Note that "STRING_SET_"
> >> doesn't start with "SET" or "XSET", and that distinction is important.
> >
> > I said nothing about lvalues and identities.  I was talking about the
> 
> That's the point, though:
> 
> Lisp_Object string = string2;
> STRING_SET_UNIBYTE (string);
> eassert (EQ (string, string2));
> 
> will fail, sometimes.  The only other macros that behave in this way are
> XSET* (which all follow this convention) and CHECK_FIXNUM_COERCE_MARKER,
> which clearly indicates in its name that it coerces its argument.
> 
> > various SET* macros: the XSET* ones, SSET, STRING_SET_CHARS, ASET, and
> > CHAR_TABLE_SET, to name just a few.  They all create or modify Lisp
> > objects, and they all accept the objects directly, not as a pointer.
> 
> Because they don't change the value of the Lisp_Object: they only use it
> as a pointer and change the underlying object.
> 
> >> > Let's leave alone this code, which worked for us for many years.
> >>
> >> I have no problem with reaching that decision ultimately, but the
> >> premises you used to reach it aren't correct: there are no comparable
> >> macros that do this, AFAIK.
> >
> > There are for me: I'm used to these macros from 30 years of hacking
> > Emacs, and they all receive Lisp objects, not pointers to them.  So
> > having one or two macros that break this pattern will be confusing,
> > certainly after so many years of using the same macros as we do now.
> 
> The macro that breaks the pattern is STRING_SET_UNIBYTE.  It is
> confusing.  It confused me.  "STRING_SET_UNIBYTE (string)" does not look
> like it will change the value of the Lisp_Object string: it looks like
> it treats the Lisp_Object as a pointer and modifies the string object it
> points to.  If STRING_SET_UNIBYTE were a function rather than a macro,
> it couldn't change the value of string.  STRING_SET_MULTIBYTE is a
> function, and doesn't, and that's even more confusing.  If we want to
> change string, the universally accepted way of doing that is to pass a
> pointer to it.

I understand where you are coming from, but after so many years of
staring at the Emacs code this is very natural to me and doesn't cause
any confusion.  When I think about these macros, I don't care about
the differences in their internal workings, whether they modify the
object or just its guts.

> >> The situation I described was:
> >>
> >> (setq str "a")
> >> (aset str 0 #xff)
> >> (aset str 0 #x100)
> >
> > OK, but why is this weird?  The value #x100 cannot be represented by a
> > single byte, so we refuse.
> 
> If I replace the #xff by #x7f, it works.  If I replace it by #x100, it
> works.  We don't refuse because #x100 requires several bytes and the
> previous character didn't, we refuse because the previous character
> cannot be converted to a multibyte form; however, as that character
> would be overwritten anyway, why do we care?

I'd have to dig deep into the history to recollect why we make the
exception for ASCII.  My guess is because ASCII strings are sometimes
unibyte and sometimes multibyte, and we wanted to avoid an even bigger
surprise where the same character instead of #xff sometimes works and
sometimes doesn't.  Users and Lisp programs cannot easily control when
ASCII strings are unibyte and when they are multibyte.