From ce02e4a0b09a89cdf406e8976dd20741ce2f5ec9 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:02:41 +0100 Subject: [PATCH 01/11] C++: Test virtual calls from constructors and destructors Cover direct and indirect dispatch on this, plus qualified and non-overridden calls for cpp/virtual-call-in-constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AV Rule 71.1/AV Rule 71.1.expected | 5 ++ .../AV Rule 71.1/AV Rule 71.1.qlref | 2 + .../jsf/4.10 Classes/AV Rule 71.1/test.cpp | 47 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.expected create mode 100644 cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.qlref create mode 100644 cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.expected new file mode 100644 index 000000000000..e25e640f594b --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.expected @@ -0,0 +1,5 @@ +| test.cpp:4:5:4:8 | call to init | Call to virtual function $@ which is overridden in $@. If you intend to statically call this virtual function, it should be qualified with Base::. | test.cpp:18:16:18:19 | init | init | test.cpp:38:8:38:11 | init | Derived | +| test.cpp:5:11:5:14 | call to init | Call to virtual function $@ which is overridden in $@. If you intend to statically call this virtual function, it should be qualified with Base::. | test.cpp:18:16:18:19 | init | init | test.cpp:38:8:38:11 | init | Derived | +| test.cpp:6:13:6:16 | call to init | Call to virtual function $@ which is overridden in $@. If you intend to statically call this virtual function, it should be qualified with Base::. | test.cpp:18:16:18:19 | init | init | test.cpp:38:8:38:11 | init | Derived | +| test.cpp:7:5:7:10 | call to helper | Call to function helper that calls virtual function $@ (overridden in $@). | test.cpp:18:16:18:19 | init | init | test.cpp:38:8:38:11 | init | Derived | +| test.cpp:14:5:14:11 | call to cleanup | Call to virtual function $@ which is overridden in $@. If you intend to statically call this virtual function, it should be qualified with Base::. | test.cpp:19:16:19:22 | cleanup | cleanup | test.cpp:39:8:39:14 | cleanup | Derived | diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.qlref b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.qlref new file mode 100644 index 000000000000..f8ed7e328584 --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/AV Rule 71.1.qlref @@ -0,0 +1,2 @@ +query: jsf/4.10 Classes/AV Rule 71.1.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp new file mode 100644 index 000000000000..0245bd427420 --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp @@ -0,0 +1,47 @@ +class Base { +public: + Base() { + init(); // $ Alert // BAD: virtual call, overridden in Derived + this->init(); // $ Alert // BAD: virtual call, overridden in Derived + (*this).init(); // $ Alert // BAD: virtual call, overridden in Derived + helper(); // $ Alert // BAD: indirectly calls a virtual function + Base::init(); // GOOD: explicitly qualified, so statically bound + notOverridden(); // GOOD: not overridden in any derived class + nonVirtual(); // GOOD: not virtual + } + + ~Base() { + cleanup(); // $ Alert // BAD: virtual call, overridden in Derived + Base::cleanup(); // GOOD: explicitly qualified + } + + virtual void init() {} + virtual void cleanup() {} + virtual void notOverridden() {} + void nonVirtual() {} + + void helper() { + init(); + } + + void other() { + init(); // GOOD: not in a constructor or destructor + } +}; + +class Derived : public Base { +public: + Derived() { + init(); // GOOD: not overridden in a class derived from Derived + } + + void init() override {} + void cleanup() override {} +}; + +class Unrelated { +public: + Unrelated(Base &b) { + b.init(); // GOOD: not a call on `this` + } +}; From ab77d68671ee10cab5afbfceaec1009da6155df4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:02:52 +0100 Subject: [PATCH 02/11] C++: Test floating-point loop counters Cover float, double, long double, and typedef counters alongside integral loops for cpp/loop-variable-float; document unsupported compound updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AV Rule 197/AV Rule 197.expected | 4 ++ .../AV Rule 197/AV Rule 197.qlref | 2 + .../AV Rule 197/test.cpp | 46 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected create mode 100644 cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.qlref create mode 100644 cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected new file mode 100644 index 000000000000..0a55cfde7c7e --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected @@ -0,0 +1,4 @@ +| test.cpp:5:14:5:14 | f | Floating point variables should not be used as loop counters. | +| test.cpp:10:15:10:15 | d | Floating point variables should not be used as loop counters. | +| test.cpp:15:15:15:16 | ld | Floating point variables should not be used as loop counters. | +| test.cpp:42:13:42:13 | r | Floating point variables should not be used as loop counters. | diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.qlref b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.qlref new file mode 100644 index 000000000000..1bf55b3397a0 --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.qlref @@ -0,0 +1,2 @@ +query: jsf/4.24 Control Flow Structures/AV Rule 197.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp new file mode 100644 index 000000000000..7ed96f9bda11 --- /dev/null +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp @@ -0,0 +1,46 @@ +void use(double); + +void test() +{ + for (float f = 0.0f; f < 1.0f; f = f + 0.1f) // $ Alert // BAD: float loop counter + { + use(f); + } + + for (double d = 0.0; d < 10.0; d++) // $ Alert // BAD: double loop counter + { + use(d); + } + + long double ld; // $ Alert // BAD: long double loop counter + for (ld = 10.0; ld > 0.0; ld--) + { + use(ld); + } + + for (double c = 0.0; c < 1.0; c += 0.1) // BAD [NOT DETECTED]: compound assignment updates are not recognized + { + use(c); + } + + for (int i = 0; i < 10; i++) // GOOD: integer loop counter + { + use(i * 0.1); + } + + double x = 0.0; // GOOD: not a `for` loop counter + while (x < 1.0) + { + x = x + 0.1; + } +} + +typedef float real; + +void test_typedef() +{ + for (real r = 0.0f; r < 1.0f; r = r + 0.5f) // $ Alert // BAD: float loop counter via typedef + { + use(r); + } +} From 0a5b64395ef0cfd16f8f9fe25e87eb3471f411ed Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:15:02 +0100 Subject: [PATCH 03/11] C++: Test catching exceptions by value Cover class exceptions caught by value, with reference and pointer catches excluded from cpp/catch-by-value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CatchingByValue/CatchingByValue.expected | 2 ++ .../CatchingByValue/CatchingByValue.qlref | 2 ++ .../Exceptions/CatchingByValue/test.cpp | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.expected create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.qlref create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.expected new file mode 100644 index 000000000000..6e95febc5ce5 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.expected @@ -0,0 +1,2 @@ +| test.cpp:12:32:12:34 | { ... } | This should catch a DerivedException by (const) reference rather than by value. | +| test.cpp:16:29:16:31 | { ... } | This should catch a BaseException by (const) reference rather than by value. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.qlref b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.qlref new file mode 100644 index 000000000000..0a79c26699a0 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/CatchingByValue.qlref @@ -0,0 +1,2 @@ +query: Best Practices/Exceptions/CatchingByValue.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp new file mode 100644 index 000000000000..f467db0f0abd --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp @@ -0,0 +1,29 @@ +class BaseException { +public: + virtual ~BaseException() {} +}; + +class DerivedException : public BaseException { +}; + +void catchByValueDerived() { + try { + throw DerivedException(); + } catch (DerivedException e) { } // $ Alert + + try { + throw BaseException(); + } catch (BaseException e) { } // $ Alert + + try { + throw DerivedException(); + } catch (DerivedException &e) { } + + try { + throw new DerivedException(); + } catch (DerivedException *e) { } + + try { + throw DerivedException(); + } catch (...) { } +} From e82e8875aaf605484ea27d48d3cf01b66f95a272 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:16:59 +0100 Subject: [PATCH 04/11] C++: Test rethrows without an active exception Cover bare rethrows outside catches and exempt lexical or dynamic catch contexts for cpp/rethrow-no-exception. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AccidentalRethrow.expected | 2 ++ .../AccidentalRethrow/AccidentalRethrow.qlref | 2 ++ .../Exceptions/AccidentalRethrow/test.cpp | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.expected create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.qlref create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/test.cpp diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.expected new file mode 100644 index 000000000000..4d8fe9aa867e --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.expected @@ -0,0 +1,2 @@ +| test.cpp:2:3:2:7 | re-throw exception | As there is no current exception, this rethrow expression will terminate the program. | +| test.cpp:6:3:6:7 | re-throw exception | As there is no current exception, this rethrow expression will terminate the program. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.qlref b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.qlref new file mode 100644 index 000000000000..1f5897c9578c --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/AccidentalRethrow.qlref @@ -0,0 +1,2 @@ +query: Best Practices/Exceptions/AccidentalRethrow.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/test.cpp new file mode 100644 index 000000000000..4d96a3c14d4e --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/AccidentalRethrow/test.cpp @@ -0,0 +1,34 @@ +void rethrowOutsideCatch() { + throw; // $ Alert +} + +void helperRethrow() { + throw; // $ Alert +} + +void safeRethrowInCatch() { + try { + } catch (...) { + throw; + } +} + +// The function name matches "%exception%", so a rethrow here is assumed to +// be intentional even though it is lexically and dynamically outside any +// catch block. +void rethrowException() { + throw; +} + +// Not lexically inside a catch block, but every call to this function is +// made from within a catch block, so the rethrow is assumed to be safe. +void calledFromCatch() { + throw; +} + +void triggersFromCatch() { + try { + } catch (...) { + calledFromCatch(); + } +} From ee7c52ec3e449118db8c2fca4c31e503df69e9cf Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:19:12 +0100 Subject: [PATCH 05/11] C++: Test throwing exception pointers Exercise new-expression throws and exempt value throws and MFC-style CException subclasses for cpp/throwing-pointer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ThrowingPointers.expected | 2 ++ .../ThrowingPointers/ThrowingPointers.qlref | 2 ++ .../Exceptions/ThrowingPointers/test.cpp | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.qlref create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected new file mode 100644 index 000000000000..ec88695f2204 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected @@ -0,0 +1,2 @@ +| test.cpp:16:3:16:25 | throw ... | This should throw a MyException rather than a pointer to one. | +| test.cpp:20:3:20:28 | throw ... | This should throw a OtherException rather than a pointer to one. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.qlref b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.qlref new file mode 100644 index 000000000000..03dbfa02a324 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.qlref @@ -0,0 +1,2 @@ +query: Best Practices/Exceptions/ThrowingPointers.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp new file mode 100644 index 000000000000..3357536ce705 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp @@ -0,0 +1,29 @@ +class MyException { +}; + +class OtherException { +}; + +// Microsoft MFC's CException hierarchy is intended to be thrown (and +// caught) as a pointer, so it should not be flagged. +class CException { +}; + +class CMyFrameworkException : public CException { +}; + +void throwsPointerToMyException() { + throw new MyException(); // $ Alert +} + +void throwsPointerToOtherException() { + throw new OtherException(); // $ Alert +} + +void throwsByValue() { + throw MyException(); +} + +void throwsFrameworkExceptionPointer() { + throw new CMyFrameworkException(); +} From c5af13fc670ae52814e541ee57d495c310b0864b Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:19:18 +0100 Subject: [PATCH 06/11] C++: Test pointer catches missing a release Cover leaked caught pointers and the method-delete, operator-delete, and escape exclusions for cpp/catch-missing-free. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Exceptions/LeakyCatch/LeakyCatch.expected | 2 + .../Exceptions/LeakyCatch/LeakyCatch.qlref | 2 + .../Exceptions/LeakyCatch/test.cpp | 50 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.qlref create mode 100644 cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected new file mode 100644 index 000000000000..0a2c543fd3d8 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected @@ -0,0 +1,2 @@ +| test.cpp:16:28:16:30 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | +| test.cpp:21:31:21:51 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.qlref b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.qlref new file mode 100644 index 000000000000..f25f199c27db --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.qlref @@ -0,0 +1,2 @@ +query: Best Practices/Exceptions/LeakyCatch.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp new file mode 100644 index 000000000000..857b571f7184 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp @@ -0,0 +1,50 @@ +class MyException { +public: + void ReportError() {} + void Delete() {} +}; + +class OtherException { +public: + void ReportError() {} +}; + +void handle(OtherException *e) {} + +void leakyCatchEmpty() { + try { + } catch (MyException *e) { } // $ Alert +} + +void leakyCatchNoDelete() { + try { + } catch (OtherException *e) { e->ReportError(); } // $ Alert +} + +void catchWithDeleteMethodCall() { + try { + } catch (MyException *e) { + e->ReportError(); + e->Delete(); + } +} + +void catchWithOperatorDelete() { + try { + } catch (MyException *e) { + e->ReportError(); + delete e; + } +} + +void catchWithPassToFunction() { + try { + } catch (OtherException *e) { + handle(e); + } +} + +void catchByValueNotPointer() { + try { + } catch (MyException e) { } +} From f2c541f1ddef0e38678ad313654a34ad0feca296 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:30:30 +0100 Subject: [PATCH 07/11] C++: Trim down overly detailed alert comments. --- .../query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp | 10 +++++----- .../4.24 Control Flow Structures/AV Rule 197/test.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp index 0245bd427420..36a7722a402c 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 71.1/test.cpp @@ -1,17 +1,17 @@ class Base { public: Base() { - init(); // $ Alert // BAD: virtual call, overridden in Derived - this->init(); // $ Alert // BAD: virtual call, overridden in Derived - (*this).init(); // $ Alert // BAD: virtual call, overridden in Derived - helper(); // $ Alert // BAD: indirectly calls a virtual function + init(); // $ Alert + this->init(); // $ Alert + (*this).init(); // $ Alert + helper(); // $ Alert (indirectly calls a virtual function) Base::init(); // GOOD: explicitly qualified, so statically bound notOverridden(); // GOOD: not overridden in any derived class nonVirtual(); // GOOD: not virtual } ~Base() { - cleanup(); // $ Alert // BAD: virtual call, overridden in Derived + cleanup(); // $ Alert Base::cleanup(); // GOOD: explicitly qualified } diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp index 7ed96f9bda11..31a4970d04b9 100644 --- a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp @@ -2,17 +2,17 @@ void use(double); void test() { - for (float f = 0.0f; f < 1.0f; f = f + 0.1f) // $ Alert // BAD: float loop counter + for (float f = 0.0f; f < 1.0f; f = f + 0.1f) // $ Alert { use(f); } - for (double d = 0.0; d < 10.0; d++) // $ Alert // BAD: double loop counter + for (double d = 0.0; d < 10.0; d++) // $ Alert { use(d); } - long double ld; // $ Alert // BAD: long double loop counter + long double ld; // $ Alert for (ld = 10.0; ld > 0.0; ld--) { use(ld); @@ -39,7 +39,7 @@ typedef float real; void test_typedef() { - for (real r = 0.0f; r < 1.0f; r = r + 0.5f) // $ Alert // BAD: float loop counter via typedef + for (real r = 0.0f; r < 1.0f; r = r + 0.5f) // $ Alert { use(r); } From 65180050ce4d2fc07d170c0ac5ff104444f4e5fa Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:58:07 +0100 Subject: [PATCH 08/11] C++: Clean up the test for floating point loop counters. --- .../AV Rule 197/AV Rule 197.expected | 2 +- .../jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected index 0a55cfde7c7e..49bd7edcc0ac 100644 --- a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/AV Rule 197.expected @@ -1,4 +1,4 @@ | test.cpp:5:14:5:14 | f | Floating point variables should not be used as loop counters. | | test.cpp:10:15:10:15 | d | Floating point variables should not be used as loop counters. | | test.cpp:15:15:15:16 | ld | Floating point variables should not be used as loop counters. | -| test.cpp:42:13:42:13 | r | Floating point variables should not be used as loop counters. | +| test.cpp:36:13:36:13 | r | Floating point variables should not be used as loop counters. | diff --git a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp index 31a4970d04b9..59d85b11a236 100644 --- a/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp +++ b/cpp/ql/test/query-tests/jsf/4.24 Control Flow Structures/AV Rule 197/test.cpp @@ -18,7 +18,7 @@ void test() use(ld); } - for (double c = 0.0; c < 1.0; c += 0.1) // BAD [NOT DETECTED]: compound assignment updates are not recognized + for (double c = 0.0; c < 1.0; c += 0.1) // $ MISSING: Alert (compound assignment updates are not recognized) { use(c); } @@ -27,12 +27,6 @@ void test() { use(i * 0.1); } - - double x = 0.0; // GOOD: not a `for` loop counter - while (x < 1.0) - { - x = x + 0.1; - } } typedef float real; From 3fc0f17766932d6cea102db06448f0335ed29cf4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:04:52 +0100 Subject: [PATCH 09/11] C++: Tweak the catching by value good cases. --- .../Best Practices/Exceptions/CatchingByValue/test.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp index f467db0f0abd..c8c3da5223fa 100644 --- a/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/CatchingByValue/test.cpp @@ -20,8 +20,12 @@ void catchByValueDerived() { } catch (DerivedException &e) { } try { - throw new DerivedException(); - } catch (DerivedException *e) { } + throw DerivedException(); + } catch (BaseException &e) { } + + try { + throw new BaseException(); + } catch (BaseException *e) { } try { throw DerivedException(); From 34a1ca3f40e623495fcb3140c7c2ab6f392c1b25 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:18:00 +0100 Subject: [PATCH 10/11] C++: Clean up the throwing pointers test, expose a case we miss. --- .../ThrowingPointers.expected | 3 +-- .../Exceptions/ThrowingPointers/test.cpp | 27 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected index ec88695f2204..093b60e54d38 100644 --- a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/ThrowingPointers.expected @@ -1,2 +1 @@ -| test.cpp:16:3:16:25 | throw ... | This should throw a MyException rather than a pointer to one. | -| test.cpp:20:3:20:28 | throw ... | This should throw a OtherException rather than a pointer to one. | +| test.cpp:5:3:5:25 | throw ... | This should throw a MyException rather than a pointer to one. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp index 3357536ce705..11703a59bf8d 100644 --- a/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/ThrowingPointers/test.cpp @@ -1,29 +1,28 @@ class MyException { }; -class OtherException { -}; - -// Microsoft MFC's CException hierarchy is intended to be thrown (and -// caught) as a pointer, so it should not be flagged. -class CException { -}; - -class CMyFrameworkException : public CException { -}; - -void throwsPointerToMyException() { +void throwsPointer1() { throw new MyException(); // $ Alert } -void throwsPointerToOtherException() { - throw new OtherException(); // $ Alert +void throwsPointer2() { + MyException *e = new MyException(); + + throw e; // $ MISSING: Alert } void throwsByValue() { throw MyException(); } +// Microsoft MFC's CException hierarchy is intended to be thrown (and +// caught) as a pointer, so it should not be flagged. +class CException { +}; + +class CMyFrameworkException : public CException { +}; + void throwsFrameworkExceptionPointer() { throw new CMyFrameworkException(); } From 681a35e7365c80461d599054e892a610358c3f45 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:33:23 +0100 Subject: [PATCH 11/11] C++: Improve readability of the leaky catch test. --- .../Exceptions/LeakyCatch/LeakyCatch.expected | 4 ++-- .../Best Practices/Exceptions/LeakyCatch/test.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected index 0a2c543fd3d8..0c89ab8d5951 100644 --- a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/LeakyCatch.expected @@ -1,2 +1,2 @@ -| test.cpp:16:28:16:30 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | -| test.cpp:21:31:21:51 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | +| test.cpp:21:28:21:30 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | +| test.cpp:27:31:27:51 | { ... } | This catch block does not free the caught exception, thereby leaking memory. | diff --git a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp index 857b571f7184..f37b921d7c71 100644 --- a/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp +++ b/cpp/ql/test/query-tests/Best Practices/Exceptions/LeakyCatch/test.cpp @@ -1,3 +1,5 @@ +// --- definitions --- + class MyException { public: void ReportError() {} @@ -11,18 +13,23 @@ class OtherException { void handle(OtherException *e) {} +// --- test cases --- + void leakyCatchEmpty() { try { + // ... } catch (MyException *e) { } // $ Alert } void leakyCatchNoDelete() { try { + // ... } catch (OtherException *e) { e->ReportError(); } // $ Alert } void catchWithDeleteMethodCall() { try { + // ... } catch (MyException *e) { e->ReportError(); e->Delete(); @@ -31,6 +38,7 @@ void catchWithDeleteMethodCall() { void catchWithOperatorDelete() { try { + // ... } catch (MyException *e) { e->ReportError(); delete e; @@ -39,6 +47,7 @@ void catchWithOperatorDelete() { void catchWithPassToFunction() { try { + // ... } catch (OtherException *e) { handle(e); } @@ -46,5 +55,6 @@ void catchWithPassToFunction() { void catchByValueNotPointer() { try { + // ... } catch (MyException e) { } }