Case 123: Default Argument Removed¶
| Field | Value |
|---|---|
| Verdict | ๐ API_BREAK |
| Category | API Break |
| Platforms | Linux |
| Flags | API break |
Detected ChangeKinds |
param_default_value_removed |
| Source files | examples/case123_default_argument_removed/ |
Category: Modern C/C++ Contract | Verdict: ๐ API_BREAK
Verdict and consumer impact¶
v1's netcfg::connect(const char *host, int timeout_ms = 5000) has a
default value for timeout_ms; v2 removes it. The mangled symbol is
identical โ default-argument values don't participate in name mangling โ so
an already-compiled binary keeps linking and running against either .so
unchanged. But any source that relies on the default, e.g.
connect("example.org"), no longer compiles against v2.h
(too few arguments to function). This is a pure source/API break: existing
binaries are unaffected, but recompiling against the new headers breaks
existing call sites.
Old/new diff¶
| v1.h | v2.h |
|---|---|
int connect(const char *host, int timeout_ms = 5000); |
int connect(const char *host, int timeout_ms); |
abicheck command¶
g++ -shared -fPIC -g v1.cpp -o libnet_v1.so
g++ -shared -fPIC -g v2.cpp -o libnet_v2.so
abicheck compare libnet_v1.so libnet_v2.so \
--header old=v1.h --header new=v2.h \
--ast-frontend clang --gcc-path "$(command -v clang)"
Expected abicheck finding¶
Verdict: API_BREAK (exit 2)
- param_default_value_removed: Parameter default removed: connect param
timeout_ms (5000)
Without headers (abicheck compare libnet_v1.so libnet_v2.so, no -H), the
same two .so files compare as NO_CHANGE (exit 0) โ the default value
lives only in the declaration, not anywhere in the object file, confirmed by
running that exact command in this environment.
Minimum evidence¶
min_evidence: L2 โ a default-argument value exists only in the
declaration (the header); it is not encoded in the mangled symbol name,
DWARF, or anywhere else in the compiled object, so object/DWARF comparison
alone (L0/L1) cannot see it โ both .so files really are ABI-identical.
castxml is the documented default header/AST backend and exposes the
default="5000" attribute on the <Argument> element; this environment
used the supported alternative Clang AST frontend (--ast-frontend clang)
since castxml itself isn't installed here, and it surfaces the same removed
default. This case is scoped to Linux in ground_truth.json: castxml on
macOS/Homebrew is documented not to emit that default= attribute at all,
so the detection is platform-dependent on the header/AST backend used, not
on this Clang-frontend substitution specifically.
Why abicheck catches it¶
The header AST parser records each parameter's default attribute from the
declaration. abicheck's diff (param_default_value_removed/
param_default_value_changed) compares that attribute between the two
sides' declarations for the same function; removing it entirely (rather than
changing its value) is reported as an API break because call sites that
relied on omitting the argument stop compiling.
Runtime failure demonstration¶
Severity: source-level, not a runtime crash. Existing binaries are unaffected โ the mangled symbol and the object code are identical โ so there's no "swap the library, watch it crash" scenario. The break shows up at compile time instead:
# A pre-built binary keeps working against either .so, unchanged:
g++ -std=c++17 app.cpp -I. -L. -lnet -Wl,-rpath,. -o app # built against v1.h
./app
# โ rc=5000
# (swap libnet.so for the v2 build, no recompile โ still fine:)
./app
# โ rc=5000
# But recompiling app.cpp against v2.h fails outright:
g++ -std=c++17 -DUSE_V2 app.cpp -I. -L. -lnet -Wl,-rpath,. -o app
# โ error: too few arguments to function 'int netcfg::connect(const char*, int)'
# note: declared here
# int connect(const char *host, int timeout_ms);
Why this matters even though nothing crashes: a library that only ships binary compatibility checks (or none) would call this release safe, but any downstream project that recompiles from source โ the normal path for most consumers โ breaks immediately at the call site.
Safe redesign¶
Keep the default, or add a forwarding overload
(int connect(const char *host) { return connect(host, 5000); }) so
existing call sites keep compiling against the new header. Changing a
default's value rather than removing it is the compatible-but-behavioral
param_default_value_changed instead; adding a new default to a parameter
that didn't have one is source-compatible and isn't flagged at all.
Real-world example: libraries tightening an API by "making callers be explicit" about a previously-defaulted parameter (timeouts, buffer sizes, flags) hit this every time โ it reads as a quality improvement but is a breaking source change for every caller that used the default.
Cross-tool comparison¶
abidw/abidiff/abi-compliance-checker are not installed in this
environment. Default-argument values aren't part of the binary ABI at all,
so a pure abidw/abidiff DWARF comparison would be expected to report
NO_CHANGE here โ the same result this case's own object-only abicheck run
produced โ since neither tool has a header/AST-level default-argument check
comparable to abicheck's param_default_value_removed.
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview ยท All API_BREAK cases ยท Category: API Break.