Case 23 — Virtual Method Became Pure Virtual¶
| Field | Value |
|---|---|
| Verdict | 🔴 BREAKING |
| Category | Breaking |
| Classification | Rule |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
func_virtual_became_pure |
| Source files | catalog/cases/case23_pure_virtual_added/ |
| Rule family | pure-virtual-added |
| Subject | Vtable and virtual dispatch changes |
Verdict: 🔴 BREAKING abicheck verdict: BREAKING
What changes¶
| Version | Declaration |
|---|---|
| v1 | class Processor { virtual void process(); }; |
| v2 | class Processor { virtual void process() = 0; }; |
What breaks at binary level¶
Making Processor::process() pure virtual (= 0) has two ABI consequences:
-
The vtable entry for
process()is replaced — the slot that previously pointed toProcessor's concrete implementation ofprocess()now points to the pure-call handler (__cxa_pure_virtual). Already-compiled consumers that invokeprocess()through aProcessor*vtable dispatch will hit the pure-call handler at runtime, causingstd::terminateinstead of calling the old base implementation. -
Processorbecomes abstract — source-level rebuilds will fail to compilenew Processor()(abstract class cannot be instantiated). For already-compiled binaries this is not the direct failure mode; the runtime break comes from point 1 above (dispatch to the pure-call handler via the vtable slot).
Consumer impact¶
/* consumer compiled against v1 (concrete class) */
Processor* p = new Processor();
p->process(); /* calls concrete implementation */
/* with v2: Processor is abstract */
/* vtable slot points to __cxa_pure_virtual */
/* → runtime abort: "pure virtual method called" */
For plugin architectures where downstream code extends the interface:
/* old plugin implements only process() */
struct MyPlugin : Processor {
void process() override;
};
/* this still works — but any new pure virtual methods
added to Processor would break existing plugins */
Mitigation¶
- Create
Processor2(orIProcessor) as the new abstract interface. - Keep the original
Processorclass frozen for existing consumers. - Version plugin interfaces explicitly.
Code diff¶
new/lib.cpp also keeps an out-of-line void Processor::process() { ... } body
alongside the pure declarator. A pure virtual function may still have a
definition — it's reachable only via an explicit qualified call
(Processor::process()), never through the vtable of the abstract class
itself, so this changes no runtime behavior. It exists so v2's binary/DWARF
still carries a Processor::process() symbol to compare against v1's,
letting abicheck's virtual-to-pure-virtual detector see this as one function
changing state (FUNC_VIRTUAL_BECAME_PURE) instead of a plain removal.
abicheck's diff_types.py distinguishes two related kinds sharing the same
detection code: FUNC_VIRTUAL_BECAME_PURE (this case — an already-virtual
concrete method becomes pure) vs. FUNC_PURE_VIRTUAL_ADDED (a non-virtual
function becomes pure virtual, a rarer shape). ground_truth.json's
expected_kinds was previously the latter, which is not what this fixture
demonstrates.
Real Failure Demo¶
Severity: CRITICAL
Scenario: app calls process() via vtable. With v2 the vtable slot points to __cxa_pure_virtual → abort().
# Build old lib + app
g++ -shared -fPIC -g old/lib.cpp -Iold -o libproc.so
g++ -g app.cpp -Iold -L. -lproc -Wl,-rpath,. -o app
./app
# → Calling process()...
# → processing
# → Done.
# Swap in new lib (pure virtual → abort)
g++ -shared -fPIC -g new/lib.cpp -Inew -o libproc.so
./app
# → Calling process()...
# → pure virtual method called
# → Aborted (core dumped)
Why CRITICAL: Existing binaries that instantiate Processor and call process()
via the vtable now hit the pure-virtual handler, causing unconditional abort().
Every plugin or subclass compiled against v1 must be rebuilt with the new abstract interface.
Why runtime result may differ from verdict¶
Became pure virtual: direct instantiation causes SIGABRT
References¶
Source files¶
new/old/CMakeLists.txtapp.cpp
See also: Compatibility Catalog · All BREAKING cases · Category: Breaking · Rule: Virtual function became pure · Subject: Vtable and virtual dispatch changes.