Re: Discussion about why GNU/Linux system upgrades cause old programs to break

Arsen Arsenović <[email protected]> Wed, 12 Aug 2026 19:31:09 +0200
Newsgroups gmane.emacs.devel
Message-ID <[email protected]>
Jeremy Bryant <[email protected]> writes:

> Thanks Arsen for explaining lots of detailed technical points, it is
> interesting reading.  I have a few questions interspersed below.
>
>
> Arsen Arsenović <[email protected]> writes:
>
>> Eli Zaretskii <[email protected]> writes:
>>
>>>> > What happened was a system upgrade.  I don't expect a system upgrade
>>>> > to remove capabilities.  Am I alone in this expectation?
>
> I agree this is a reasonable expectation.
>
>>> My point is that at least in some cases we decide "not to solve" this
>>> too soon.  We could do better.  Having a program break 25 years after
>>> it was built is much better than if it stops working after 5, I hope
>>> you agree.
>>
>> Absolutely, some such cases happened; I listed one such case above.
>
> There is more agreement that a program should continue to work for a
> longer period of time than sometimes it currently does.

This thread brought up two breaking changes:

- GCC 14 started rejecting invalid C99+ programs after emitting warnings
  about them for decades.
- SIGSTKSZ was made non-constant when GNU extensions were enabled, on at
  least some platforms.

The former is a much longer period than I think was deserved.

The latter change was a lot more sudden, but SIGSTKSZ is a comparatively
rarely relied upon API.  I am not sure there was a way to implement a
sort of deprecation window for it, but perhaps there was.

>>
>>> You are talking about programs that are part of the OS.  By contrast,
>>> I'm talking about programs that were built by the user -- those are
>>> not upgraded when the system is, and one of the worst effects of a
>>> system upgrade is to see programs which worked for you for years
>>> suddenly stop working.
>>
>> No, I'm not.
>>
>> My paragraph was worded a bit poorly, let me restate what I stated
>> above, and expand on it:
>>
>>>> The system vendor (be it Apple or Microsoft or Google or whoever)
>>>> simply ships you, the developer, a set of dependencies dated to, say,
>>>> some year, so you end up seeing breakages when you change which date
>>>> the system-vendor-provided dependencies attach themselves to, because
>>>> you, the developer, are the one acting as an integrator for the vast
>>>> majority of the dependencies, and your own code, with the
>>>> vendor-provided code.
>>>> 
>>>> The users end up not seeing this at all because each program gets
>>>> distributed with nearly all dependencies, with the ones distributed
>>>> as part of the OS being installed multiple times.
>>
>> These system-vendor-provided dependencies are equivalent to the "core"
>> GNU system: GCC runtime libraries, glibc, coreutils, GTK2/3/4 (at some
>> point at least), etc.  These are in that year-versioned bundle that I
>> was referring to, that gets installed a few times on each users' system.
>>
>> The dependencies outside of these, however (say, libjpeg or
>> tree-sitter), are integrated by the developer, and get shipped with the
>> program.
>>
>> So, when any of these dependencies - whichever category they belong to -
>> change, builds break *for the developer*.  The user doesn't notice,
>> because it was made the developers' problem.  The user just gets a ton
>> of binaries, with all the parts epoxied together.
>>
>> But the user in our case is given source-code, and the freedom and
>> compulsion to vary dependencies.  Exactly as the developer was in these
>> alternative models.  The user becomes the integrator.
>>
>> It is not possible to avoid breakages as a result.
>>
>> Great effort is already taken to avoid them, yet they still happen.
>>
>>>> (those who have used MS-w are aware of "VC++ Redistributable XYZW" - or
>>>> whatever the name was - which they have to install on occasion for
>>>> various XYZW).
>>>
>>> That's just a packaging issue: those redistributables should have been
>>> bundled with the program that needs them, but could not be due to
>>> copyright issues and such.  The important part is that, once you do
>>> have the redistributable installed, no system upgrade will remove it.
>>
>> I'm not sure whether a MS-w upgrade by a major version would remove
>> them.
>>
>> But, even if not, they're the exception to the rule on that OS; most
>> dependencies are shipped with most programs, thus they're mostly
>> isolated from each-other.
>>
>> Which is its own mess, of course (tried using TeXLive and Inkscape
>> together through svg.sty?  what a mess..).
>>
>> The issue we're discussing is also a packaging issue, though; programs
>> in the Linux and BSD and ... ecosystems generally don't get distributed
>> in binary form with all of their dependencies built in, they get
>> distributed in source form, without dependencies.
>>
>> Whereas programs in the ecosystems I refer to above are all distributed
>> in binary form with nearly all of their dependencies bundled.
>>
>> The former means that the same sources are expected to work with a
>> myriad of combinations of various dependency versions.
>>
>> Obviously, the developer testing them all is unfeasible.  Especially as
>> many of those combinations do not yet exist at time of writing.
>>
>> Thus, problems arise sometimes quite late.
>>
>> This is where compiler laxness comes into play.  I'll return to this
>> below.
>>
>
>>>> This is also why many cling to static linking.
>>>
>>> Static linking is a two-edged sword: it could be more robust in the
>>> long run, but in the shorter run you lose the ability to easily update
>>> a dependency library if and when its developers fix some grave bug in
>>> it, without the need to rebuild your application.
>>>
>>> Most programs nowadays use dynamic linking, IME.
>>
>> There's other problems with static linking also, but, yes, most programs
>> are dynamic linked nowadays.
>>
>> What I was saying is that there's a contingent of developers who cling
>> to static linking as a solution for the problems you lie out in this
>> thread.
>>
>> Which are real problems.  But they aren't problems solvable at the
>> source level, or even at the binary level.  We still try to, though.
>>
>> The solutions in the ecosystems I referred to above are far closer to
>> static linking (because you can't really independently update components
>> as the user) than what we usually do in the Linux and BSD and
>> ... ecosystems.
>>
>> You can't have your cake and eat it, too.  You either allow varying
>> dependencies and thus have breakages, or you don't, and everything works
>> (you even get gratis bugs because you can't independently fix them).
>>
>> I think we've largely made the right choice.  The free software world
>> permits anyone to fix problems that arise as a result of Hyrum's Law,
>> and we're able to integrate masses of code written by many thousands of
>> people and work on them collaboratively.
>>
>> But, this does mean that the constraint of "unmodified program works
>> indefinitely" is impossible to satisfy.
>
> Arsen, do you have a suggestion for improving the longevity of Emacs
> source?

By not making false assumptions, especially not in the configuration
users are expected to build.

For instance, attempting to build Emacs 24, I get:

  In file included from ../src/config.h:1430,
                   from sha256.c:23:
  ./stdio.h:1030:1: error: ‘gets’ undeclared here (not in a function); did you mean ‘fgets’?
   1030 | _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
        | ^~~~~~~~~~~~~~~
  In file included from ../src/config.h:1430,
                   from md5.c:23:
  ./stdio.h:1030:1: error: ‘gets’ undeclared here (not in a function); did you mean ‘fgets’?
   1030 | _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
        | ^~~~~~~~~~~~~~~
  In file included from ../src/config.h:1430,
                   from sha1.c:25:
  ./stdio.h:1030:1: error: ‘gets’ undeclared here (not in a function); did you mean ‘fgets’?
   1030 | _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
        | ^~~~~~~~~~~~~~~
  In file included from ../src/config.h:1430,
                   from sha512.c:23:
  ./stdio.h:1030:1: error: ‘gets’ undeclared here (not in a function); did you mean ‘fgets’?
   1030 | _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
        | ^~~~~~~~~~~~~~~
  In file included from ../src/config.h:1430,
                   from ftoastr.c:28,
                   from dtoastr.c:2:
  ./stdio.h:1030:1: error: ‘gets’ undeclared here (not in a function); did you mean ‘fgets’?
   1030 | _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
        | ^~~~~~~~~~~~~~~

... and (after fixing that):

  /tmp/emacs-24.1/lib-src/emacsclient.c: In function ‘main’:
  /tmp/emacs-24.1/lib-src/emacsclient.c:1606:9: error: implicit declaration of function ‘get_current_dir_name’ [-Wimplicit-function-declaration]
   1606 |   cwd = get_current_dir_name ();
        |         ^~~~~~~~~~~~~~~~~~~~
  /tmp/emacs-24.1/lib-src/emacsclient.c:1606:7: error: assignment to ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   1606 |   cwd = get_current_dir_name ();
        |       

The latter is a case where Emacs blatantly violates the rules of C99: it
calls a function that is not declared.

The former is a case where Gnulib redeclares a reserved symbol and
assumes that it exists, which is something explicitly not permitted in
C.

It does so in order to generate a warning when 'gets' is used.  This
means that it did something that made the program more fragile that
brings no benefit to the final user.

(Not that it'd have helped here; the failure actually happened because
 'gets' is removed with _GNU_SOURCE defined, and it is defined by
 config.h)

Unfortunately, that's not at all a simple answer, especially not for an
old codebase.  If I kept attempting to fix the above, I'm sure I'd have
ran into many more problems.

As I said before, old programs, even very portable ones, make myriads of
false assumptions all the time, because portability equated to "runs on
many systems at the moment".

But, IME (and this is why I weighed in on this thread), people are very
quick to blame some external factor for program breakage.

I've found that most times, after looking into such reports, I find that
the original programs were broken, in a way that did not manifest
immediately.

> Are you suggesting it amounts to forms of more 'static linking'?
> Should it be something to recommend to distros (Gentoo, Debian/Trisquel,
> ...) ?

Absolutely not.  Static linking only helps with distributing the binary
form outside of distros.

The 'break' related to dynamic linking in this thread would've been
fixed by static linking, but such a 'break' wouldn't have happened in
the first place for distro-built Emacs, because distro-built Emacs would
be tracked by the package manager, thus its dependencies would not be
able to be removed.

> Would there be a subset of dependencies such that there is greater
> longevity anticipated for, by way of illustration, Emacs compiled
> without GTK.  Namely, a subset of e.g. terminal-based Emacs?

I certainly expect Emacs compiled with fewer dependencies to live for
longer, because that means, by proxy, that it is smaller.

>>>> > One example of this is compiling a program that used K&R syntax.
>>>> > E.g., Emacs switched to C90-style function definitions only in version
>>>> > 24.1, which was released in 2012.  All the older versions used K&R
>>>> > syntax; they are now "broken" by your definitions, and cannot be
>>>> > easily compiled.
>>>> 
>>>> Well, yes, if Emacs versions up to 2012 were telling the compiler that
>>>> they were C99 programs, but were not valid C99, they were certainly
>>>> broken, and only worked due to a defect in GCC.
>>>
>>> See above: that's not what happens.  In reality, the build invokes the
>>> compiler without specifying the dialect, so whatever is the default C
>>> dialect is being used.  So when the default changes to be
>>> backward-incompatible, it's bound to break old programs.
>>
>> Indeed, the default dialect can change.  This is why I find the
>> autotools choice to only provide -std=... conditionally dubious.
>
> Do you have a suggestion to improve this?

Yes.  Pass it always when possible, ensure that it has an effect.

I've seen some projects do the following for C++:

  AC_DEFUN([NMS_CXX_11],
  [AC_MSG_CHECKING([whether $CXX is for C++11])
  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
  [#if __cplusplus != 201103
  #error "C++11 is required"
  #endif
  ]])],
  [AC_MSG_RESULT([yes])],
  [CXX_ORIG="$CXX"
  CXX="$CXX -std=c++11"
  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
  [#if __cplusplus != 201103
  #error "C++11 is required"
  #endif
  ]])],
  AC_MSG_RESULT([adding -std=c++11]),
  [CXX="$CXX_ORIG"
  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
  [#if __cplusplus > 201103
  #error "C++11 is required"
  #endif
  ]])],
  AC_MSG_RESULT([> C++11]),
  AC_MSG_RESULT([no])
  AC_MSG_ERROR([C++11 is required])]))
  unset CXX_ORIG])])

... i.e. ensure that the compiler is *truly* a C++11 compiler, at least
by adding -std=c++11.

Analogously this goes for every C version.

>
>>>> I've argued before, and stand by it now, that source-level backwards
>>>> compatibility should not be the default for compiled languages.  When a
>>>> compiler is present, it should only be backwards-compatible if told to
>>>> be explicitly.
>>>
>>> This makes recompiling old sources harder, so I think it's a mistake,
>>> unless we don't care about these case.
>>
>> I'm sure many more hours have been spent tracking down stuff that
>> could've been prevented by stronger diagnostics than have been spent
>> trying to compile old code.
>>
>> I do not mean to imply that backwards compatibility is a "sin".  But,
>> backwards compatibility in this instance means relaxing compiler
>> diagnostics, thus making the compiler less strict.  This is universally
>> a disservice to users.  I've went over why earlier, but I can restate it
>> in brief again: anything that can be caught early but isn't harms
>> everyone, including program authors and users.
>>
>> There are two different classes of users of compilers: program
>> authors and program users building the source form.
>>
>> The former is harmed, substantially, by such default laxness, because
>> they're enabled to write programs that are slightly wrong.  The latter
>> receives some slight relief from default backwards compatibility.
>>
>> But, this is a Pyrrhic victory for the latter: as a result of default
>> backwards compatibility, rather than fixing defects in an early versions
>> of programs, those defects go unnoticed are kept around long enough to
>> break in more insidious ways later, blowing up in the users' face.
>>
>> Indeed, the case I showed in my last email with 'reallocarray' was
>> exactly such a case: the user of flex "benefited" from default backwards
>> compatibility, only to get a program that was slightly broken under
>> specific conditions.
>>
>> Luckily, in that case, the breakage manifested as an easy-to-find crash.
>> In many cases, it will manifest as a rare and difficult to diagnose
>> runtime bug.
>>
>> Meanwhile the developer of flex was left unaware of this breakage for a
>> longer time.
>>
>> It was a disservice to both classes of users to postpone or entirely
>> omit diagnostics on code that GCC knew was very likely to be wrong.
>>
>> The driver behind the GCC 14 change was not that anyone else decided to
>> do it (though arguably Clang doing it acted as a trigger), it was that
>> we've been fixing related bugs for many years in many programs, and that
>> we've been teaching students that "this code is not correct but the
>> compiler ignored it because old code exists, but you shouldn't ignore
>> it" (to very little effect).
>
> Arsen, do you have more ideas on how to make recompiling old Emacs sources
> specifically have greater longevity than at present?

No.  I fear that the only answer is to build them with era-appropriate
dependencies and tools, or to fix the problems now.

The advice above will make future releases live longer.  (But, again,
the advice is very abstract and not at all simple.  I am sorry about not
having a simpler thing to suggest)

Note that, in Gentoo, we try to provide many old versions of Emacs, and
fix breakages that happen with them.  Feel free to use our work :-)

This is because we're acting as integrators of said old Emacsen with
current software.

Here's, for instance, our recipe for building Emacs 24:
https://gitweb.gentoo.org/repo/proj/emacs.git/tree/app-editors/emacs/emacs-24.5-r11.ebuild
-- 
Arsen Arsenović
signature.asc (application/pgp-signature, 418 B)
-----BEGIN PGP SIGNATURE-----

iQECBAEWCgCqFiEE/uKz0RP8AKMWLWBhUsKUMB6ixJMFAmp8rd0bFIAAAAAABAAO
bWFudTIsMi41KzEuMTIsMiwyXxSAAAAAAC4AKGlzc3Vlci1mcHJAbm90YXRpb25z
Lm9wZW5wZ3AuZmlmdGhob3JzZW1hbi5uZXRGRUUyQjNEMTEzRkMwMEEzMTYyRDYw
NjE1MkMyOTQzMDFFQTJDNDkzEBxhcnNlbkBhYXJzZW4ubWUACgkQUsKUMB6ixJMU
nwEAr0NDQNFwOHLBu3vjYtFv3yTRiXDrZrM38oiSAxbyAYMBAIXZlZInDYHrpq75
uiaGh4SKRf3A85wUgMiKtRBiCg4J
=e4bB
-----END PGP SIGNATURE-----