TL;DR: Some times, usually when you are not using the default build flags you can easily fall in the situation were you got fails to link the WebKit libs with undefined reference errors caused by missing #include directives in .cpp files. This post describes how I manage the situation to identify the missing headers in the correct files and fix it.
The failure
The build fails at the end of the comilation, in the linker:
undefined reference to `WebCore::Document::page() const'undefined reference to `WebCore::Document::quirks()'undefined reference to `WebCore::EventTarget::ref()'undefined reference to `WebCore::Document::protectedCachedResourceLoader() const'
Are the symbols defined at all?
The first thing worth checking is whether those symbols exist anywhere in the compiled object files:
find workdir/ -name "*.o" -path "*/WebCore/*" | xargs nm -C 2>/dev/null \ | grep "Document::protectedCachedResourceLoader"
The answer comes back immediately:
U WebCore::Document::protectedCachedResourceLoader() const
U means Undefined, the symbol is referenced but never defined in any .o. It was never compiled into anything. That rules out a link ordering problem and points to a compilation issue.
WebKit uses a unified source build system to speed up compilation. Instead of invoking the compiler once per .cpp file, a Python script groups multiple source files into batches:
// UnifiedSource-950a39b6-10.cpp (generated, not hand-written)#include "html/HTMLLegendElement.cpp"#include "html/HTMLLinkElement.cpp"#include "html/HTMLMediaElement.cpp"#include "html/HTMLMetaElement.cpp"// ...
Each batch is compiled as a single translation unit. A side effect of this is that if one .cpp in the batch includes a header with inline definitions, all other .cpp files in the same batch see those definitions transitively for free, without explicitly including the header themselves.
This works fine in most situations but it becomes a problem, however, when combined with -fvisibility-inlines-hidden and --no-undefined, both of which are standard flags in production embedded builds. With those flags active, an inline function only emits a visible symbol in the translation units where the compiler explicitly sees its definition. If a .cpp in a batch never directly includes the header that defines the inline, the linker will not find the symbol.
Lately, WebKit has been running a refactor since 2024: [Build Speed] Break up DocumentInlines.h (bug 299920). The goal is to reduce header dependencies and speed up incremental builds by splitting large inline headers into smaller, focused ones:
DocumentInlines.h → DocumentPage.h DocumentQuirks.h DocumentResourceLoader.h EventTargetInlines.h ... and more
Each of these new headers defines inline methods of Document or related classes. Every .cpp that calls those methods should now include the specific header it needs. Most files were updated, but a few were not, leaving them silently relying on transitive inclusion through their batch neighbors.
Crossing the failing unified source batches with the includes present in each file gives a clear picture:
| File | Symbol used | Defining header | Included? |
|---|---|---|---|
html/HTMLLinkElement.cpp | Document::page() | DocumentPage.h | ✗ |
html/HTMLMetaElement.cpp | Document::quirks() | DocumentQuirks.h | ✗ |
loader/LinkLoader.cpp | Document::protectedCachedResourceLoader() | DocumentResourceLoader.h | ✗ |
html/HTMLTextFormControlElement.cpp | EventTarget::ref() | EventTargetInlines.h | ✗ |
So basically the solution pass through the addition of the missing headers in those files:
sed -i 's/#include "DocumentInlines.h"/#include "DocumentInlines.h"\n#include "DocumentPage.h"/' \ Source/WebCore/html/HTMLLinkElement.cppsed -i 's/#include "HTMLMetaElement.h"/#include "HTMLMetaElement.h"\n#include "DocumentQuirks.h"/' \ Source/WebCore/html/HTMLMetaElement.cppsed -i 's/#include "DocumentPage.h"/#include "DocumentPage.h"\n#include "DocumentResourceLoader.h"/' \ Source/WebCore/loader/LinkLoader.cppsed -i 's/#include "HTMLTextFormControlElement.h"/#include "HTMLTextFormControlElement.h"\n#include "EventTargetInlines.h"/' \ Source/WebCore/html/HTMLTextFormControlElement.cpp
Then force recompilation of the affected batches and relink:
find . -name "UnifiedSource-950a39b6-10.cpp.o" -deletefind . -name "UnifiedSource-950a39b6-15.cpp.o" -deletefind . -name "UnifiedSource-c57e08af-5.cpp.o" -deleteninja ... WebKit
… and the build passes. 🎉
Extra ball …
To make this kind of investigation faster in the future, I’ve created a Python script, webkit-unified-source-diagnostic.py, that automates the whole process: it parses a build log, identifies the failing batches, maps each undefined symbol to its defining header, checks which files in each batch are missing the include, and generates the sed commands to fix them.
python3 webkit-unified-source-diagnostic.py \ --log build.log \ --unified-dir builddir/.../WebCore/DerivedSources/unified-sources/ \ --source-dir Source/WebCore




Leave a comment