Refactoring all the things!
New config
We're on day 8 already (time flies!), and I'm almost finished refactoring the entire qutebrowser codebase to use the new config. There are still some issues here and there and some missing changes (for :set and qute:config mostly), but qutebrowser is now mostly usable again without needing any of the old config code.
This took much more work than I expected, and much of it was quite tedious:
98 files changed, 4270 insertions(+), 3865 deletions(-)
I'm glad I'm almost done with that part, the real fun (working on reading and writing the YAML/Python files) will begin soon!
Jinja templating
qutebrowser uses jinja2 as a template engine to build Qt stylesheets from config options, and then style widgets accordingly.
While refactoring the config, I noticed that it's jinja's default behavior to not raise an exception if an attribute is invalid - instead, it just assumes an empty default value.
Because this made it very hard to find issues during the refactoring, I made my jinja environment more strict so an AttributeError is actually caught when it happens.
QtWebKit segfaults
Another thing I tried to track down is why the new QtWebKit packages for Archlinux segfault a lot - I've been getting a lot of reports about this lately, and indeed I could reproduce a crash when writing replies on Reddit.
I first thought this was some bug in the updated QtWebKit-NG package, but then I could reproduce it with the legacy QtWebKit package which hasn't seen any changes in the last couple of months as well...
As this segfault was affecting a lot of qutebrowser users (anyone using it on Archlinux with QtWebKit, pretty much), I decided to dig a bit deeper into it.
The segfault happens here:
WTF::StringImpl::copyChars<char16_t>()
JSC::jsSpliceSubstringsWithSeparators()
JSC::replaceUsingRegExpSearch()
JSC::replace()
JSC::stringProtoFuncReplace()
From the stacktrace, this looked like something which would be happening when you call "abc".replace(/a/, "b") in JavaScript. I tried various various variations of that, but couldn't get it to crash.
I then thought I could get JavaScript to log everything passed to .replace() on a string, hoping I could get the string which causes the crash.
A fellow student of mine came up with this, which worked beautifully:
(function(replace) {
String.prototype.replace = function() {
console.log(this, arguments);
return replace.apply(this, arguments);
};
})(String.prototype.replace)
While I saw all those calls in the console, I didn't see the call which actually caused the crash...
So I took a closer look at the C++ stacktrace, and was able to get the string from there. It was some 9000 chars of HTML edited via JavaScript...
After some more tinkering, I was able to get it down to this example which crashes when run in jsc, a command-line JavaScript interpreter coming with WebKit:
s = 'xxxxxxxxxxxxxxAxxxxxxxxxxxxxxxxxxxxA–'; s.replace(/A/g, 'b')
I then compiled WebKitGTK (which is the upstream of QtWebKit, as strange as that sounds) with GCC 7, and indeed it was reproducable there as well.
The C++ code where it crashes turned out to be this:
template <typename T> static void copyChars(
T* destination, const T* source, unsigned numCharacters)
{
if (numCharacters == 1) {
*destination = *source;
return;
}
if (numCharacters <= s_copyCharsInlineCutOff) {
unsigned i = 0;
#if (CPU(X86) || CPU(X86_64))
const unsigned charsPerInt = sizeof(uint32_t) / sizeof(T);
if (numCharacters > charsPerInt) {
unsigned stopCount = numCharacters & ~(charsPerInt - 1);
const uint32_t* srcCharacters =
reinterpret_cast<const uint32_t*>(source);
uint32_t* destCharacters =
reinterpret_cast<uint32_t*>(destination);
for (unsigned j = 0; i < stopCount; i += charsPerInt, ++j)
destCharacters[j] = srcCharacters[j];
}
#endif
for (; i < numCharacters; ++i)
destination[i] = source[i];
} else
memcpy(destination, source, numCharacters * sizeof(T));
}
This is essentially an optimized version of memcpy, which does the copying inline when it's less than 20 (s_copyCharsInlineCutOff) chars. Nowadays, a simple memcpy would probably be optimized to something equivalent, but there we are...
I first was confused about how that optimized part (in the #if) worked together with the "simple" part below, and how the inner loop uses j and i, but eventually I got it, and was sure that part was actually fine.
(The optimized part copies 4-byte blocks at once, and then the part below copies the rest)
I didn't have an idea what was wrong, but people in the #gcc IRC channel had: Turns out it's undefined behavior when you access an uint16_t* via an uint32_t* - that was when I learned about type punning and strict aliasing...
And indeed, removing that optimized loop made things run fine.
The "fun" thing is that WebKit is compiled using the -fno-strict-aliasing flag to GCC, which should actually allow this kind of thing. Still, with GCC 6 it works fine, with GCC 7 it crashes - so this might turn out to be some kind of GCC bug.
I haven't opened an upstream WebKit bug for this yet, but I'll do so today or tomorrow unless the QtWebKit maintainer does so.
I also requested my workaround to be applied to Archlinux' packages, but that hasn't happened so far.