Bug 387820 - KConfigGui misbehave at runtime when link time optimizations are enabled
Summary: KConfigGui misbehave at runtime when link time optimizations are enabled
Status: RESOLVED WORKSFORME
Alias: None
Product: frameworks-kconfig
Classification: Frameworks and Libraries
Component: general (show other bugs)
Version: unspecified
Platform: Other Linux
: NOR normal
Target Milestone: ---
Assignee: Matthew Dawson
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-12-12 05:38 UTC by Emmanuel Lepage Vallée
Modified: 2022-12-10 05:17 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Emmanuel Lepage Vallée 2017-12-12 05:38:34 UTC
## Context

In the past few months, I have been fixing all frameworks to support static linking, link time optimizations and profile guided optimizations. So far the patches are a little crude and are not in mergeable shape. Since yesterday I cleaned up a few of them to begin the upstreaming process. There's already an elv13/fix_static branch for 2 frameworks to discuss the changes. LTO and static mode enables KDE apps to be built in a single binary that's about ~10mb for most applications, 20mb for big ones due to the assets, making it small enough for even low end mobile devices. It's also starting much faster thanks to the PGOs.

Most frameworks "work" after some boring build system patches such as fixing common "there is a cmake macro/variable for that, don't write your own code" type of bugs. There's also the need to make QtTest optional everywhere to avoid having hundreds of gigabytes worth of tests being built (and it takes a looooong time to link). However some frameworks are really broken such as the ones with plugins and those abusing of undefined behavior or static initialization behavior, such as KConfigGui.

## The bug

This warning is hit and kcolorsheme fail to load, rendering all applications black on black (invalid QColor).

 372     case QMetaType::QColor:
 373     case QMetaType::QFont:
 374         qWarning() << "KConfigGroup::readEntry was passed GUI type '"
 375                    << aDefault.typeName()
 376                    << "' but KConfigGui isn't linked! If it is linked to your program, "
 377                    "this is a platform bug. Please inform the KDE developers";
 378         break;

This is due to several C++ broken features used together.

1) The visitor KConfig uses to have types provided by KConfigGui and not KConfigCore is using static initialization in the form of:

193 static int initKConfigGroupGui()
194 {
195     _kde_internal_KConfigGroupGui.readEntryGui = readEntryGui;
196     _kde_internal_KConfigGroupGui.writeEntryGui = writeEntryGui;
197     return 42;                  // because 42 is nicer than 1 or 0
198 }
199 
200 #ifdef Q_CONSTRUCTOR_FUNCTION
201 Q_CONSTRUCTOR_FUNCTION(initKConfigGroupGui)
202 #else
203 static int dummyKConfigGroupGui = initKConfigGroupGui();
204 #endif

In a CPP file. This is problematic for several reasons:

1) Static initialization order is undefined behavior and changes from compilers to compilers and the level of optimizations used. Having fixed "most" common scenarios with "most" common compilers does not make it predictable. In this case it isn't called at all (and this is actually valid, not a bug due to point 1, 2 and 3 being combined)

2) This is implemented as a `static` variable in a `cpp` file. Static elements in a compilation units are never exported (as defined in the C standard). Even adding __attribute(used), volatile or an EXPORT macro in front wont export it. The compiler is free to apply dead code elimination on these variables if it thinks they are unused or used in UB ways only.

3) The macro is in a cpp file with no header and no exported symbols at all. The link time optimizer uses a reacheability tree purge unused compilation units and sections. There is nothing at all pointing to this CU, it's eliminated from the final executable in GCC and probably all other compilers.

## Is it a compiler bug?

No. It is a certain interpretation of the standard where GCC takes some extra liberty to purge code even if it does "something". It probably fail to resolve that the code actually affects static initialization of other CUs. Given that's undefined anyway, then so be it.

## How can it be fixed

My crude hack is to put `int dummyKConfigGroupGui` in a dummy class and exporting it then print dummyKConfigGroupGui from main.cpp. It fixes the framework and applications are no longer black on black. Obviously it's a bad hack.

The proper way to fix this would be to share createColorEntry in a private KConfigGui header and turn the int into a `static std::atomic_flag KCongigGui::init_flag {ATOMIC_FLAG_INIT};`. Then, in KConfigSkeleton constructor, check the flag and initialize it if it's not already initialized.

This would provide an internal reachability path for `dummyKConfigGroupGui` from the outside of `src/gui/kconfiggroupgui.cpp` and avoid it from being dead-code-eliminated by the compiler.

## tl;dr

Q_CONSTRUCTOR_FUNCTION can't be used in a .cpp file that exports nothing at all. I don't think there's anything that can be done to fix the macro, the compiler will skip it anyway.
Comment 1 Matthew Dawson 2017-12-12 16:59:19 UTC
Sorry if I misunderstand your proposed solution, but it sounds like KConfigCore will then need to make calls into KConfigGui, would require it to be linked together and defeat the purpose of having them separate.  I'm not sure how we can make the necessary call across libraries without having the two of them be linked together.  Maybe some trickery with the dynamic linker?  But that seems error prone, and unlikely to work with LTO.

I definitely would like to see this fixed in KConfig though, and making the library more robust against compilers changing is good as well.  But I would like to keep KConfigCore from linking against QtGui.
Comment 2 Emmanuel Lepage Vallée 2017-12-12 21:25:11 UTC
kconfigskeleton.cpp is part of KConfigGui, so if the private .h is part of KConfigGui too, then there's no issue. Nothing from KConfigCore is involved.
Comment 3 Matthew Dawson 2017-12-13 01:28:07 UTC
(In reply to Emmanuel Lepage Vallée from comment #2)
> kconfigskeleton.cpp is part of KConfigGui, so if the private .h is part of
> KConfigGui too, then there's no issue. Nothing from KConfigCore is involved.

Sorry, you are correct about that.  The name of the class confused me, as I wasn't able to double check that when I replied.

As such, I'm generally ok with this plan.  My only concern is ensuring that the write happens in a way ensuring that the variables are correctly initialized (through correct acquire/release semantics), but that can be handled in review!

If you (or someone else) can write a patch to fix this issue, I'd be happy to review it.  Thanks for taking a look into this and LTO!
Comment 4 Justin Zobel 2022-11-10 22:32:38 UTC
Thank you for reporting this issue in KDE software. As it has been a while since this issue was reported, can we please ask you to see if you can reproduce the issue with a recent software version?

If you can reproduce the issue, please change the status to "REPORTED" when replying. Thank you!
Comment 5 Bug Janitor Service 2022-11-25 05:20:29 UTC
Dear Bug Submitter,

This bug has been in NEEDSINFO status with no change for at least
15 days. Please provide the requested information as soon as
possible and set the bug status as REPORTED. Due to regular bug
tracker maintenance, if the bug is still in NEEDSINFO status with
no change in 30 days the bug will be closed as RESOLVED > WORKSFORME
due to lack of needed information.

For more information about our bug triaging procedures please read the
wiki located here:
https://community.kde.org/Guidelines_and_HOWTOs/Bug_triaging

If you have already provided the requested information, please
mark the bug as REPORTED so that the KDE team knows that the bug is
ready to be confirmed.

Thank you for helping us make KDE software even better for everyone!
Comment 6 Bug Janitor Service 2022-12-10 05:17:13 UTC
This bug has been in NEEDSINFO status with no change for at least
30 days. The bug is now closed as RESOLVED > WORKSFORME
due to lack of needed information.

For more information about our bug triaging procedures please read the
wiki located here:
https://community.kde.org/Guidelines_and_HOWTOs/Bug_triaging

Thank you for helping us make KDE software even better for everyone!