Coverity Scan build master-20260914 reports a group of defects in unit-test, regression-test and example code — 74 of them, with no shipped behavior at risk. This issue covers clearing that group.
Scope
74 findings across 29 files under unit_tests/, unit-tests/, plus InkAPITest.cc, example/, and the older regression-test sources (CacheTest.cc, RegressionSM.cc, NetVCTest.cc, test_AIO.cc).
By type: 20 uncaught exception, 16 argument-cannot-be-negative, 10 out-of-bounds access, 6 resource leak, 3 dereference-before-null-check, 2 use-after-free, and a tail of singles.
Two systemic causes worth recording
1. Catch2 macro bodies. Coverity names TEST_CASE/SECTION bodies dummyFunctionN (0-based, stepping by 2 per TEST_CASE; SECTIONs are not separately numbered). More importantly, REQUIRE(expr) does not constrain values for the analyzer: it expands to handleExpr(...) followed by complete(), and complete() is not [[noreturn]] — it returns normally when the assertion passes. So REQUIRE(n > 0) never teaches Coverity anything about n, which is the source of most of the "argument cannot be negative" and "out-of-bounds access" reports here.
2. The throwing _ink_assert override. src/iocore/eventsystem/unit_tests/test_MIOBufferWriter.cc redefines _ink_assert to throw InkAssertExcept() (it already carries a // coverity[UNCAUGHT_EXCEPT:FALSE] noting the confusion). Coverity merges symbol definitions across the whole analysis, so every ink_assert() in the tree resolves to the throwing one. That is why destructors reaching an assert — directly, or via THREAD_FREE → thread_freeup(), or free_MIOBuffer — are reported as uncaught-exception escapes. The aborting _ink_assert from unit_tests/stub.cc is what actually links into those binaries.
Note for anyone fixing this family: adding noexcept to a destructor is a no-op. A user-provided destructor with no exception-specification is already implicitly noexcept(true) in C++20 even when its body calls a throwing function, so an explicit noexcept changes nothing and cannot clear the finding.
Defects that turned out to be real
test_Huffmancode.cc decoder_roundtrip_fuzz passed huffman_encode's int64_t return straight into huffman_decode's uint32_t src_len. An error return would be read as a ~4 GB source length, and the existing REQUIRE never bounded it above the encode buffer.
InkAPITest.cc SDK_API_TSUrlParse and SDK_API_TSMimeHdrParse both destroy the MBuffer inside an error branch and then keep using it, ending in a second TSMBufferDestroy (use-after-free plus double-free). Open since 2022.
test_RemapPlugin.cc held a PluginDebugObject * pointing into a dlopen'd image, declared one scope outside the unique_ptr whose destructor dlcloses it.
test_HPACK.cc prepare() built paths with an unbounded strcat into a PATH_MAX + 1 buffer, safe only because of a 511-byte cap enforced in ink_args.cc.
RegressionSM.cc: leaf state machines were never deleted. ReRegressionSM::run() completes synchronously via done() and so never reaches regression_sm_waiting(), which is where composites self-delete. 12 leaf instances leak per run, 6 of them clones created internally by the repeat path. Composites are fine — deleting the tree from the test body would be a double-free.
Recommended for triage rather than a code change
These eight are false positives or intentional; they need marking in the Coverity UI, not a patch:
| CID |
Location |
Why |
| 1550403 |
query_remap.cc hash_fnv32 |
The uint32 wraparound is the FNV algorithm |
| 1528571, 1528692 |
thread_pool/thread.cc |
Every access to the queue fields is already inside TSMutexLock/TSMutexUnlock; there is no model for those in ci/coverity-model.cpp, so lock semantics cannot be inferred |
| 1021840, 1021841 |
CacheTest.cc RegressionTest_cache |
Nothing leaks — prototypes are stack objects, clones self-delete via complete(), composites self-delete |
| 1644306 |
test_ProxyProtocol.cc |
The post-move reads are the test deliberately asserting that a moved-from ProxyProtocol is a valid empty object |
| 1660643 |
test_Huffmancode.cc decoder_parity_exhaustive_short |
The only array is uint8_t src[2], written at indices 0 and 1, no computed index |
| 1644208 |
test_PluginFactory.cc setupConfigPathTest |
TOCTOU against a file the test just created in its own per-run temp sandbox |
Adjacent bugs found, not in scope here
example/plugins/c-api/thread_pool/thread.cc: thread_loop polls remove_from_queue outside cond_mutex and enters pthread_cond_wait with no predicate re-check, so a job enqueued in that window is missed until the next signal. Separately, cond_mutex is never pthread_mutex_inited.
include/iocore/net/ProxyProtocol.h: move-assignment has no self-assignment guard while copy-assignment does, so pp = std::move(pp) would silently clear the data.
test_RemapPlugin.cc:295: static char ARGC = sizeof ARGV yields 8, not the intended argument count of 3. The test is self-consistent, so changing it would alter what it asserts.
Verification note
RegressionSM and RegressionTest_cache are REGRESSION_TEST macros compiled into inkcache; no ctest case exercises them. Any change there needs a traffic_server -R run, not just the unit-test suites.
Coverity Scan build
master-20260914reports a group of defects in unit-test, regression-test and example code — 74 of them, with no shipped behavior at risk. This issue covers clearing that group.Scope
74 findings across 29 files under
unit_tests/,unit-tests/, plusInkAPITest.cc,example/, and the older regression-test sources (CacheTest.cc,RegressionSM.cc,NetVCTest.cc,test_AIO.cc).By type: 20 uncaught exception, 16 argument-cannot-be-negative, 10 out-of-bounds access, 6 resource leak, 3 dereference-before-null-check, 2 use-after-free, and a tail of singles.
Two systemic causes worth recording
1. Catch2 macro bodies. Coverity names
TEST_CASE/SECTIONbodiesdummyFunctionN(0-based, stepping by 2 perTEST_CASE;SECTIONs are not separately numbered). More importantly,REQUIRE(expr)does not constrain values for the analyzer: it expands tohandleExpr(...)followed bycomplete(), andcomplete()is not[[noreturn]]— it returns normally when the assertion passes. SoREQUIRE(n > 0)never teaches Coverity anything aboutn, which is the source of most of the "argument cannot be negative" and "out-of-bounds access" reports here.2. The throwing
_ink_assertoverride.src/iocore/eventsystem/unit_tests/test_MIOBufferWriter.ccredefines_ink_asserttothrow InkAssertExcept()(it already carries a// coverity[UNCAUGHT_EXCEPT:FALSE]noting the confusion). Coverity merges symbol definitions across the whole analysis, so everyink_assert()in the tree resolves to the throwing one. That is why destructors reaching an assert — directly, or viaTHREAD_FREE→thread_freeup(), orfree_MIOBuffer— are reported as uncaught-exception escapes. The aborting_ink_assertfromunit_tests/stub.ccis what actually links into those binaries.Note 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 throwing function, so an explicitnoexceptchanges nothing and cannot clear the finding.Defects that turned out to be real
test_Huffmancode.ccdecoder_roundtrip_fuzzpassedhuffman_encode'sint64_treturn straight intohuffman_decode'suint32_t src_len. An error return would be read as a ~4 GB source length, and the existingREQUIREnever bounded it above the encode buffer.InkAPITest.ccSDK_API_TSUrlParseandSDK_API_TSMimeHdrParseboth destroy the MBuffer inside an error branch and then keep using it, ending in a secondTSMBufferDestroy(use-after-free plus double-free). Open since 2022.test_RemapPlugin.ccheld aPluginDebugObject *pointing into adlopen'd image, declared one scope outside theunique_ptrwhose destructordlcloses it.test_HPACK.ccprepare()built paths with an unboundedstrcatinto aPATH_MAX + 1buffer, safe only because of a 511-byte cap enforced inink_args.cc.RegressionSM.cc: leaf state machines were never deleted.ReRegressionSM::run()completes synchronously viadone()and so never reachesregression_sm_waiting(), which is where composites self-delete. 12 leaf instances leak per run, 6 of them clones created internally by the repeat path. Composites are fine — deleting the tree from the test body would be a double-free.Recommended for triage rather than a code change
These eight are false positives or intentional; they need marking in the Coverity UI, not a patch:
query_remap.cc hash_fnv32thread_pool/thread.ccTSMutexLock/TSMutexUnlock; there is no model for those inci/coverity-model.cpp, so lock semantics cannot be inferredCacheTest.cc RegressionTest_cachecomplete(), composites self-deletetest_ProxyProtocol.ccProxyProtocolis a valid empty objecttest_Huffmancode.cc decoder_parity_exhaustive_shortuint8_t src[2], written at indices 0 and 1, no computed indextest_PluginFactory.cc setupConfigPathTestAdjacent bugs found, not in scope here
example/plugins/c-api/thread_pool/thread.cc:thread_looppollsremove_from_queueoutsidecond_mutexand enterspthread_cond_waitwith no predicate re-check, so a job enqueued in that window is missed until the next signal. Separately,cond_mutexis neverpthread_mutex_inited.include/iocore/net/ProxyProtocol.h: move-assignment has no self-assignment guard while copy-assignment does, sopp = std::move(pp)would silently clear the data.test_RemapPlugin.cc:295:static char ARGC = sizeof ARGVyields 8, not the intended argument count of 3. The test is self-consistent, so changing it would alter what it asserts.Verification note
RegressionSMandRegressionTest_cacheareREGRESSION_TESTmacros compiled intoinkcache; no ctest case exercises them. Any change there needs atraffic_server -Rrun, not just the unit-test suites.