Case 23 โ Virtual Method Became Pure Virtual¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
func_pure_virtual_added |
| Source files | browse on GitHub |
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¶
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¶
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.