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

From: Pip Cet <[email-masked]>
Date: Thu, 16 Jan 2025 12:23:34 +0000

"Eli Zaretskii" <[email-masked]> writes:

>> Date: Thu, 16 Jan 2025 11:17:05 +0000
>> From: Pip Cet <[email-masked]>
>> Cc: [email-masked], [email-masked], [email-masked], [email-masked]
>>
>> "Eli Zaretskii" <[email-masked]> writes:
>>
>> > We have many SET_SOMETHING macros that modify the objects passed to
>> > them as arguments.
>>
>> The problem is treating the arguments as lvalues and turning them into
>> entirely different objects.  XSET* does that, and there are a few macros
>> that set structs, but I'm aware of only two macros that do this to Lisp
>> objects, and the other one is clearly named to indicate it coerces its
>> argument.
>>
>> So, no, not many cases like this, and this case is bad for other
>> reasons: assuming that STRING_SET_MULTIBYTE and STRING_SET_UNIBYTE
>> behave similarly seems natural to me.
>>
>> > So I see no compelling reason to make this change,
>> > which confusingly deviates from the other SET macros.
>>
>> ??? 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.

>> 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?

> Looks understandable to me.

Not to me.

Pip