Coverity: handle uncaught-exception reports in test code - #13688
Conversation
The global setup in main() runs before Catch2 takes over, so a throw from Layout::create(), RecProcessInit() or the session runner escaped main and aborted the test binary with no diagnostic. Catch the escape, print it, and exit non-zero so the failure is still reportable. Coverity CID 1644283, CID 1528654, CID 1523670.
Argument parsing, the std::string path setup and the EThread allocation can all throw, and an exception leaving main() unwinds with no diagnostic. Catch it, print it and exit non-zero so the failure is still reportable. Coverity CID 1528601.
Destructors are implicitly noexcept, so an exception raised while closing or deleting the client transaction terminates traffic_server with no indication of where it came from. Abort with a message instead. Coverity CID 1559190.
Deleting the synthetic server is the second path out of this noexcept destructor, and an exception from it terminates traffic_server just as silently, so bring it under the same guard. Coverity CID 1518135.
The only calls the destructor makes are ink_assert() and MIOBuffer teardown, and the _ink_assert() linked into traffic_server aborts. Coverity instead resolves the symbol to the throwing test-only definition in eventsystem/unit_tests/test_MIOBufferWriter.cc, which that file already annotates for the same reason. Coverity CID 1528624.
The destructor only frees an MIOBuffer and destroys two HTTPInfos, both of which assert. The cache unit tests link the aborting _ink_assert() from tscore, but Coverity resolves the symbol to the throwing test-only definition in eventsystem/unit_tests/test_MIOBufferWriter.cc. Coverity CID 1528646.
The destructor only frees an MIOBuffer and destroys an HTTPInfo, both of which assert. The cache unit tests link the aborting _ink_assert() from tscore, but Coverity resolves the symbol to the throwing test-only definition in eventsystem/unit_tests/test_MIOBufferWriter.cc. Coverity CID 1528704.
next_test() reaches EThread::schedule(), which asserts. The cache unit tests link the aborting _ink_assert() from tscore, but Coverity resolves the symbol to the throwing test-only definition in eventsystem/unit_tests/test_MIOBufferWriter.cc. Coverity CID 1528771.
Config parsing, EThread/AIO_Device allocation and the event system startup in main() can all throw, and nothing caught them, so an exception left main() and killed the process with no diagnostic. Coverity CID 1644327. Coverity CID 1528590. Coverity CID 1523686.
test() allocates caches, strings and set entries, so it can throw, and nothing caught it; the exception left main() and terminated the test with no diagnostic and no exit status distinguishable from a crash. Coverity CID 1686026. Coverity CID 1587267. Coverity CID 1587256.
Destructors are implicitly noexcept, so an exception raised while cancelling the scheduled callback or waiting for it to finish terminates the test binary with no indication of where it came from. Abort with a message instead. Coverity CID 1686062.
Destructors are implicitly noexcept, so an exception raised while logging or returning either MIOBuffer terminates traffic_server with no indication of where it came from. Abort with a message instead. Coverity CID 1528569. Coverity CID 1518878.
HdrHeap::destroy() reaches THREAD_FREE(), whose thread_freeup() asserts on its postcondition, and Coverity resolves _ink_assert() to the throwing definition in test_MIOBufferWriter.cc. The exception cannot occur in a binary that links the aborting stub, so suppress rather than handle it. Coverity CID 1591506.
There was a problem hiding this comment.
🟢 Approval recommended
The reviewed changes address the reported Coverity findings with targeted handling and suppressions.
Pull request overview
This pull request addresses Coverity uncaught-exception reports in test destructors and entry points without changing production behavior.
Changes:
- Adds exception handling to test runners.
- Guards destructor failures with
ink_abort. - Documents verified Coverity false-positive suppressions.
File summaries
| File | Reviewed change |
|---|---|
src/records/unit_tests/unit_test_main.cc |
Handles test initialization and execution exceptions. |
src/proxy/http2/test_HPACK.cc |
Handles HPACK test-runner exceptions. |
src/proxy/http/remap/unit-tests/test_RemapRulesYaml.cc |
Suppresses a verified teardown false positive. |
src/iocore/net/NetVCTest.cc |
Guards buffer teardown failures. |
src/iocore/hostdb/unit_tests/test_RefCountCache.cc |
Reports test exceptions as failures. |
src/iocore/eventsystem/unit_tests/test_Lock.cc |
Guards continuation teardown. |
src/iocore/cache/unit_tests/main.h |
Suppresses cache-test destructor false positives. |
src/iocore/cache/unit_tests/CacheTestHandler.h |
Suppresses a continuation-chain false positive. |
src/iocore/cache/CacheTest.cc |
Suppresses a cache destructor false positive. |
src/iocore/aio/test_AIO.cc |
Handles AIO test-runner exceptions. |
src/api/InkAPITest.cc |
Guards ParentTest teardown failures. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
brbzull0
left a comment
There was a problem hiding this comment.
LGTM. I verified the FALSE classification independently rather than taking it on faith, and it holds: the throwing _ink_assert (throw InkAssertExcept(), test_MIOBufferWriter.cc:196) is linked into exactly one executable — add_executable(test_MIOBufferWriter unit_tests/test_MIOBufferWriter.cc) with target_link_libraries(... libswoc::libswoc Catch2::Catch2WithMain), no ts::tscore. Every binary containing an annotated destructor links ts::tscore, whose _ink_assert is TS_NORETURN and calls abort(). So nothing on those teardown paths can throw and no real failure is being hidden. A repo-wide grep turns up only three _ink_assert definitions — the aborting one in libtscore, the throwing one in test_MIOBufferWriter.cc, and an std::exit(1) one in test_LogUtils.cc:124 — and neither override links with any annotated file.
One factual fix for the PR description (not the code): it says the aborting _ink_assert comes from unit_tests/stub.cc, but no stub.cc in the tree defines it. It's src/tscore/ink_assert.cc.
On the question the PR body asks the reviewer to decide — three destructors get a runtime try/catch while five structurally identical ones get a comment-only annotation, even though the analysis establishes the exception is impossible in all eight. I'd go one way or the other: either drop the three guards in favour of annotations matching the other five (the Stripe.cc:316 comment is a good model), or keep them as defence-in-depth with a one-line note saying why those three differ.
If you keep the guards, consider adding a catch (std::exception const &e) arm ahead of each catch (...) and passing e.what() through ink_abort. As written they abort with a fixed string, discarding the exception type and message — strictly less information than the default terminate handler they replace, and inconsistent with the main() guards added in the same PR, which do print e.what().
One alternative worth considering: the root cause is a Coverity modelling gap, and the repo already owns the mechanism for it. ci/coverity-model.cpp models _TSReleaseAssert as __coverity_panic__() for exactly this reason but has no entry for _ink_assert. Adding one there would fix all eleven files at the source rather than annotating each site, and would keep future ink_assert() calls from re-triggering this.
Coverity reports uncaught-exception escapes from test destructors and test
main()functions. Part of #13682.The one thing to decide
src/iocore/eventsystem/unit_tests/test_MIOBufferWriter.ccredefines_ink_asserttothrow InkAssertExcept(). It already carries its own// coverity[UNCAUGHT_EXCEPT:FALSE]noting that "Coverity is confused and thinks this_ink_assertis the one used in traffic_server." Coverity merges symbol definitions across the whole analysis, so everyink_assert()in the tree resolves to that throwing definition.That is why these destructors look like they can throw. They reach an assert one of three ways:
~CacheTestSM)THREAD_FREE()→thread_freeup(), which asserts on its postcondition (~EasyURLthroughHdrHeap::destroy();~CacheReadTest/~CacheWriteTestthroughfree_MIOBuffer→dealloc_reader)EThread::schedule()(~TestContChain)The aborting
_ink_assertfromunit_tests/stub.ccis what actually links into these binaries, so the exception cannot occur.Important for anyone fixing this family
Adding
noexceptto a destructor is a no-op. A user-provided destructor with no exception-specification is already implicitlynoexcept(true)in C++20, even when its body calls a potentially-throwing function (std::is_nothrow_destructible_vis 1). An explicitnoexceptchanges nothing and cannot clear the finding.Two treatments, chosen per site
Suppression where the exception provably cannot occur and there is nothing to handle —
// coverity[UNCAUGHT_EXCEPT:FALSE]with the trace in a comment: CIDs 1528624, 1528646, 1528704, 1528771, 1591506.try/catch+ink_abortwhere a guard is strictly better than a silentstd::terminate, so a genuine escape still fails loudly with context: CIDs 1518135, 1559190 (~ParentTest), 1686062 (~HoldOnEThread), 1518878, 1528569 (~NetVCTest).ci/coverity-model.cppalready teaches Coverity thatink_abortpanics.try/catchreturning a non-zero exit status for themain()findings, so a test failure is still reported rather than terminating: CIDs 1523670, 1528654, 1644283 (unit_test_main.cc), 1523686, 1528590, 1644327 (test_AIO.cc), 1686026, 1587256, 1587267 (test_RefCountCache.cc), 1528601 (test_HPACK.cc).mainis deliberately not markednoexcept.Several commits close three CIDs, because Coverity reports one per throwing call site and a single handler covers them all.
Alternative worth considering
The project also has precedent for
// coverity[exn_spec_violation]annotations (Stripe::~Stripe(),HttpSM::~HttpSM()). If a reviewer prefers one uniform treatment across the whole family rather than suppression-where-provable and guard-where-useful, the destructor commits are the ones to swap.Verification
test_records,test_tsutil,test_proxy_hdrs,test_proxy_hdrs_xpack,test_cache,test_hostdb,test_tscore,test_tsconfig— 321 tests, all passing. Every touched file compiles independently on this branch.