From 3788679078a3a178201e965ec8633d6fca576d66 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:22 +0530 Subject: [PATCH 001/119] verify: Verify character literals, evaluated constants, template value parameters, default arguments, declarations without effect, __builtin_expect, initializer-list returns, and structured bindings. --- clang/lib/Verify/Frontend/ASTConverter.cpp | 102 ++++++++++++++++++++- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 7c91912f97..5fa49c9753 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -14,6 +14,7 @@ #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtContract.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" @@ -735,8 +736,12 @@ findUnsupportedType(const FunctionDecl *FD, bool AllowScalarReferences, bool VisitExpr(Expr *E) { if (const auto *ICE = dyn_cast(E)) - if (ICE->getCastKind() == CK_FunctionToPointerDecay) + if (ICE->getCastKind() == CK_FunctionToPointerDecay || + ICE->getCastKind() == CK_BuiltinFnToFnPtr) return true; + // The callee of a builtin call, which the call's conversion decides. + if (E->getType()->isSpecificPlaceholderType(BuiltinType::BuiltinFn)) + return true; if (!Found && !E->getType().isNull() && !isSupportedVerificationType(E->getType())) Found = E->getType().getAsString(); @@ -1230,10 +1235,22 @@ void ASTConverter::recordSourceVariable(const ValueDecl *D) { std::move(DisplayName), Type, D->getLocation(), D->getEndLoc()}; }; if (const RecordDecl *RD = getRecordFromType(D->getType())) { + // A structured binding's field is shown by the binding's name. + std::map Bound; + if (const auto *Decomposition = dyn_cast(D)) + for (const BindingDecl *Binding : Decomposition->bindings()) + if (const auto *Member = dyn_cast_or_null( + Binding->getBinding() + ? Binding->getBinding()->IgnoreParenImpCasts() + : nullptr)) + if (const auto *Field = dyn_cast(Member->getMemberDecl())) + Bound[Field] = Binding->getNameAsString(); if (const RecordDecl *Definition = RD->getDefinition()) for (const FieldDecl *Field : Definition->fields()) Record(InternalBase + "." + Field->getNameAsString(), - DisplayBase + "." + Field->getNameAsString(), + Bound.count(Field) + ? Bound[Field] + : DisplayBase + "." + Field->getNameAsString(), VType::fromQualType(Field->getType(), IntMode, Ctx)); return; } @@ -5732,6 +5749,30 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { if (const auto *MTE = dyn_cast(E)) return convertExpr(MTE->getSubExpr()); + // A template argument, a default argument, and a default member + // initializer are the expressions they stand for. + if (const auto *Subst = dyn_cast(E)) + return convertExpr(Subst->getReplacement()); + if (const auto *Default = dyn_cast(E)) + return convertExpr(Default->getExpr()); + if (const auto *Default = dyn_cast(E)) + return convertExpr(Default->getExpr()); + // A constant Clang evaluates (a consteval call, an if constexpr + // condition) and a character literal are their values. + if (isa(E) && + E->getType()->isIntegralOrEnumerationType()) { + Expr::EvalResult Result; + if (E->EvaluateAsInt(Result, Ctx)) { + const VType Ty = VType::fromQualType(E->getType(), IntMode, Ctx); + if (Ty.Kind == VTypeKind::Bool) + return std::make_unique( + !Result.Val.getInt().isZero(), Ty, E->getExprLoc()); + return std::make_unique( + integerValueString(Result.Val.getInt()), Ty, E->getExprLoc()); + } + } + if (const auto *Constant = dyn_cast(E)) + return convertExpr(Constant->getSubExpr()); // `this`, the parameter of a member function's object. if (isa(E)) { if (ThisSubstitute) @@ -5845,6 +5886,13 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { Ty, E->getExprLoc()); } if (const auto *DRE = dyn_cast(E)) { + // A structured binding names a member or element of its object. + if (const auto *Binding = dyn_cast(DRE->getDecl())) { + if (const Expr *Bound = Binding->getBinding()) + return convertExpr(Bound); + error(CurrentFn->Name + ": unsupported structured binding"); + return nullptr; + } if (const auto *FD = dyn_cast(DRE->getDecl())) { auto It = FieldSubstPrefix.find(FD->getNameAsString()); if (It != FieldSubstPrefix.end()) { @@ -6378,6 +6426,10 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { BinderType); } if (const auto *CE = dyn_cast(E)) { + // __builtin_expect(e, c) is e: the expectation steers code layout only. + if (CE->getBuiltinCallee() == Builtin::BI__builtin_expect || + CE->getBuiltinCallee() == Builtin::BI__builtin_expect_with_probability) + return convertExpr(CE->getArg(0)); if (const FunctionDecl *Callee = CE->getDirectCallee()) { if (Callee->isConstexpr() && !functionContract(Callee) && CE->isEvaluatable(Ctx)) { @@ -8073,6 +8125,38 @@ ASTConverter::convertStmtBody(const Stmt *S) { } } if (CurrentFn->ReturnType.Kind == VTypeKind::Struct) { + // An initializer list gives the fields in order: return {a, b}; + const Expr *Init = RetE->IgnoreParenImpCasts(); + if (const auto *Cast = dyn_cast(Init)) + Init = Cast->getSubExpr()->IgnoreParenImpCasts(); + const RecordDecl *Returned = getRecordFromType(RetE->getType()); + const RecordDecl *ReturnedDefinition = + Returned ? Returned->getDefinition() : nullptr; + if (const auto *List = dyn_cast(Init); + List && ReturnedDefinition && + isFlatScalarRecordType(RetE->getType()) && + List->getNumInits() == + static_cast(std::distance( + ReturnedDefinition->field_begin(), + ReturnedDefinition->field_end()))) { + const std::string Tmp = + "__aggregate_return_" + std::to_string(++NestedCallId); + unsigned Index = 0; + for (const FieldDecl *Field : ReturnedDefinition->fields()) { + auto Value = materialize(convertExpr(List->getInit(Index++)), + Field->getType()); + if (!Value) + return Out; + Out.push_back(std::make_unique( + Tmp + "." + Field->getNameAsString(), std::move(Value), + RS->getBeginLoc())); + } + appendReturn(std::make_unique(Tmp, CurrentFn->ReturnType, + RS->getBeginLoc()), + Out, RS->getBeginLoc()); + InitializationPathReachable = false; + return Out; + } // A record kept in memory is returned as its fields, read before // the lifetimes of the locals end. std::vector>> @@ -8919,6 +9003,12 @@ ASTConverter::convertStmtBody(const Stmt *S) { } if (const auto *DS = dyn_cast(S)) { for (const Decl *D : DS->decls()) { + // static_assert, a type alias, a using-declaration, and a local + // class or enumeration declare, and do nothing. + if (isa(D)) + continue; const auto *VD = dyn_cast(D); if (!VD) { error(CurrentFn->Name + ": unsupported declaration statement"); @@ -8930,10 +9020,14 @@ ASTConverter::convertStmtBody(const Stmt *S) { } // A local named like a parameter or an earlier local, in an enclosing // or an earlier scope, is another variable: x#2, x#3, ... + // The object of a structured binding has no name of its own. if (!LocalNames.count(VD)) { - std::string Name = VD->getNameAsString(); + const std::string Base = isa(VD) + ? std::string("__binding") + : VD->getNameAsString(); + std::string Name = Base; for (unsigned N = 2; DeclaredValueNames.count(Name); ++N) - Name = VD->getNameAsString() + "#" + std::to_string(N); + Name = Base + "#" + std::to_string(N); DeclaredValueNames.insert(Name); if (Name != VD->getNameAsString()) LocalNames[VD] = Name; From f72c7d210aaf78438f74c975aa2d245bb95df4c6 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:23 +0530 Subject: [PATCH 002/119] test: Small forms of everyday C++, each with a wrong twin. --- clang/test/Verify/suite/frontend_gaps.cpp | 217 ++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 clang/test/Verify/suite/frontend_gaps.cpp diff --git a/clang/test/Verify/suite/frontend_gaps.cpp b/clang/test/Verify/suite/frontend_gaps.cpp new file mode 100644 index 0000000000..bb1c9036fb --- /dev/null +++ b/clang/test/Verify/suite/frontend_gaps.cpp @@ -0,0 +1,217 @@ +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 > %t 2>&1 +// RUN: FileCheck %s < %t +// RUN: FileCheck %s --check-prefix=SOUND < %t +// +// Small forms of everyday C++: character literals, constants Clang +// evaluates, template value parameters, default arguments, declarations +// without effect, __builtin_expect, a record returned from an initializer +// list, and structured bindings. Every correct function has a wrong twin +// that must be refuted. +// +// No proof here holds only because no execution reaches its end. +// SOUND-NOT: vacuous + +namespace cv = cppverify; + +// A character literal is an integer of its type. +bool is_digit(char c) + cv::post(cv::result == ('0' <= c && c <= '9')) +{ + return c >= '0' && c <= '9'; +} +// CHECK-DAG: Verified: is_digit [backend=z3] + +bool is_digit_wrong(char c) + cv::post(cv::result == ('0' <= c && c < '9')) +{ + return c >= '0' && c <= '9'; +} +// CHECK-DAG: error: verification failed: is_digit_wrong [{{.*}}c [ssa=c_0] [type=i8] = 57{{[,;]}}{{.*}}[reason=counterexample] + +int digit_value(char c) + cv::pre('0' <= c && c <= '9') + cv::post(0 <= cv::result && cv::result <= 9) +{ + return c - '0'; +} +// CHECK-DAG: Verified: digit_value [backend=z3] + +// A character literal has its type's value: '\xff' is -1 as a signed char. +bool high_char(signed char c) + cv::post(cv::result == (c == -1)) +{ + return c == '\xff'; +} +// CHECK-DAG: Verified: high_char [backend=z3] + +int wide(wchar_t c) + cv::post(cv::result == (c == 65)) +{ + return c == L'A'; +} +// CHECK-DAG: Verified: wide [backend=z3] + +// A consteval call and an if constexpr condition are the values Clang +// computes; a template value parameter is its argument. +consteval int limit() { return 100; } + +int clamp(int x) + cv::post(cv::result <= 100) +{ + if (x > limit()) + return limit(); + return x; +} +// CHECK-DAG: Verified: clamp [backend=z3] + +int clamp_wrong(int x) + cv::post(cv::result < 100) +{ + if (x > limit()) + return limit(); + return x; +} +// CHECK-DAG: error: verification failed: clamp_wrong [{{.*}}[reason=counterexample] + +template int scaled(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == N * x) +{ + if constexpr (N == 0) + return 0; + else + return N * x; +} + +int scaled_twice(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == 3 * x) +{ + return scaled<0>(x) + scaled<3>(x); +} +// CHECK-DAG: Verified: scaled<0> [backend=z3] +// CHECK-DAG: Verified: scaled<3> [backend=z3] +// CHECK-DAG: Verified: scaled_twice [backend=z3] + +int scaled_wrong(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == 4 * x) +{ + return scaled<0>(x) + scaled<3>(x); +} +// CHECK-DAG: error: verification failed: scaled_wrong [{{.*}}[reason=counterexample] + +// A default argument is evaluated at the call. +int add(int x, int y = 2) + cv::pre(-1000 <= x && x <= 1000 && -1000 <= y && y <= 1000) + cv::post(cv::result == x + y) +{ + return x + y; +} +// CHECK-DAG: Verified: add [backend=z3] + +int add_default(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == x + 2) +{ + return add(x); +} +// CHECK-DAG: Verified: add_default [backend=z3] + +int add_default_wrong(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == x) +{ + return add(x); +} +// CHECK-DAG: error: verification failed: add_default_wrong [{{.*}}[reason=counterexample] + +// static_assert, type aliases, using-declarations, and local types declare, +// and do nothing. +int declarations(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == x + 1) +{ + static_assert(sizeof(int) == 4); + typedef int counter; + using other = int; + enum Step { One = 1 }; + struct Box { int v; }; + counter c = x; + other d = c + One; + return d; +} +// CHECK-DAG: Verified: declarations [backend=z3] + +// __builtin_expect(e, c) is e. +int expect(int x) + cv::post(cv::result == (x > 0 ? 1 : 0)) +{ + if (__builtin_expect(x > 0, 1)) + return 1; + return 0; +} +// CHECK-DAG: Verified: expect [backend=z3] + +int expect_wrong(int x) + cv::post(cv::result == 1) +{ + if (__builtin_expect(x > 0, 1)) + return 1; + return 0; +} +// CHECK-DAG: error: verification failed: expect_wrong [{{.*}}[reason=counterexample] + +// A record returned from an initializer list. +struct Pt { + int x; + int y; +}; + +Pt make(int a, int b) + cv::post(cv::result.x == a && cv::result.y == b) +{ + return Pt{a, b}; +} +// CHECK-DAG: Verified: make [backend=z3] + +Pt make_braced(int a) + cv::post(cv::result.x == a && cv::result.y == 0) +{ + return {a}; +} +// CHECK-DAG: Verified: make_braced [backend=z3] + +Pt make_wrong(int a, int b) + cv::post(cv::result.x == b) +{ + return Pt{a, b}; +} +// CHECK-DAG: error: verification failed: make_wrong [{{.*}}[reason=counterexample] + +// Structured bindings name the fields of a copy. +int sum(Pt p) + cv::pre(-1000 <= p.x && p.x <= 1000 && -1000 <= p.y && p.y <= 1000) + cv::post(cv::result == p.x + p.y) +{ + auto [a, b] = p; + return a + b; +} +// CHECK-DAG: Verified: sum [backend=z3] + +int first(Pt p) + cv::post(cv::result == p.y) +{ + auto [a, b] = p; + return a; +} +// CHECK-DAG: error: verification failed: first [{{.*}}[reason=counterexample] + +int both(Pt p, Pt q) + cv::post(cv::result == p.x) +{ + auto [a, b] = p; + auto [c, d] = q; + return a; +} +// CHECK-DAG: Verified: both [backend=z3] From 6d894ecbc18f06407d17e7b1760d1eb6e453a7a5 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:24 +0530 Subject: [PATCH 003/119] docs: Character literals are integers of their type. --- website/source/language/integers.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/source/language/integers.rst b/website/source/language/integers.rst index 60d25c52df..f68dad1eae 100644 --- a/website/source/language/integers.rst +++ b/website/source/language/integers.rst @@ -44,6 +44,18 @@ Each machine integer's **bit width** comes from the target's data model Narrow integers and extensions such as ``__int128`` retain their target widths; the usual integral promotions are applied before arithmetic. +A character literal is an integer of its type: ``'0'`` is 48 as a ``char``, +``'\xff'`` is -1 as a signed ``char``, and ``L'A'`` is a ``wchar_t``. + +.. code-block:: cpp + + int digit_value(char c) + cv::pre('0' <= c && c <= '9') + cv::post(0 <= cv::result && cv::result <= 9) + { + return c - '0'; + } + **Signedness** is tracked too, and it matters: signed overflow is undefined behavior in C++, while unsigned overflow is *defined* modular wraparound. The verifier treats them differently. Heap payloads are width-neutral mathematical From 629f24b8371d1001a99add4ce827ec119cb945f2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:24 +0530 Subject: [PATCH 004/119] docs: Initializer-list returns and structured bindings of records. --- website/source/language/structs.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/website/source/language/structs.rst b/website/source/language/structs.rst index d2b1a6fa15..296e2830d1 100644 --- a/website/source/language/structs.rst +++ b/website/source/language/structs.rst @@ -13,6 +13,32 @@ member syntax: cv::post(cv::result >= 0) { return r.w * r.h; } +A record may be returned from an initializer list, ``return Pt{a, b};`` or +``return {a, b};``, whose fields are given in order and the rest zero, and a +structured binding copies a record and names its fields: + +.. code-block:: cpp + + struct Pt { int x; int y; }; + + Pt make(int a, int b) + cv::post(cv::result.x == a && cv::result.y == b) + { + return Pt{a, b}; + } + + int sum(Pt p) + cv::pre(-1000 <= p.x && p.x <= 1000 && -1000 <= p.y && p.y <= 1000) + cv::post(cv::result == p.x + p.y) + { + auto [a, b] = p; + return a + b; + } + +A counterexample shows a binding by its name. Structured bindings of an +array, of a tuple-like type, or by reference (``auto &[a, b] = p;``) are not +supported yet. + Records through pointers ------------------------ From de607465e4a098358099d8e19cc79fd6a331aaec Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:25 +0530 Subject: [PATCH 005/119] docs: Default arguments, evaluated constants, declarations without effect, and if constexpr. --- website/source/language/functions-loops.rst | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index 023a056784..c870ea265d 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -30,6 +30,12 @@ and in ``[ssa=...]`` the verifier's name for it, ``i#2`` for the second ``i``. Before 0.2 such a declaration was an error, ``local variable shadowing is unsupported``. +A default argument is evaluated at the call, with the checks of its +operations there. A ``consteval`` call, like any constant Clang evaluates, is +its value. ``__builtin_expect(e, c)`` is ``e``. ``static_assert``, type +aliases, using-declarations, and local classes and enumerations declare +names and do nothing at run time. + A caller's proof uses its callees' contracts, so a verdict is only as good as those contracts. A caller whose proof relies on a callee contract that the callee's own verification did not establish is ``Unresolved`` with reason @@ -448,7 +454,33 @@ A function template is verified in each of its specializations, as Kani and CBMC verify the code a program instantiates: its contract is instantiated with its body, so the contract and every check of the body are about the template's arguments, and each specialization gets a verdict of its own, -named with its arguments. +named with its arguments. A template value parameter is its argument, and +``if constexpr`` keeps the branch its constant condition selects: + +.. code-block:: cpp + + template int scaled(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == N * x) + { + if constexpr (N == 0) + return 0; + else + return N * x; + } + + int scaled_sum(int x) + cv::pre(-1000 <= x && x <= 1000) + cv::post(cv::result == 3 * x) + { + return scaled<0>(x) + scaled<3>(x); + } + +.. code-block:: text + + Verified: scaled<0> [backend=z3] + Verified: scaled<3> [backend=z3] + Verified: scaled_sum [backend=z3] .. cppverify-example: fails twice From b91051374a6b1200415a8983430743230f1df66c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:25 +0530 Subject: [PATCH 006/119] docs: List the small forms of everyday C++ in the supported fragment. --- website/source/language/limitations.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index 219dcf880f..2f9058240b 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -113,6 +113,18 @@ C++ feature boundaries * - Scalar types - ``bool``, integral types, and enums preserve target width and signedness. - Floating point, ``volatile``, and other non-integral scalar semantics. + * - Expressions and declarations + - Character literals (``'0'``, ``L'A'``), constants Clang evaluates (a + ``consteval`` call, an ``if constexpr`` condition), template value + parameters, default arguments and default member initializers, + ``__builtin_expect``, a record returned from an initializer list + (``return Pt{a, b};``), structured bindings that copy a record, and + declarations that do nothing (``static_assert``, type aliases, + using-declarations, local classes and enumerations). + - The value of an assignment, an increment, or a comma expression + inside an expression (``a = b = 0``, ``*p++``); string literals; + other builtins; structured bindings of an array, of a tuple-like type, + or by reference. * - Pointers - One pointer level to supported complete objects. The type gate admits ``void*``, but no general erasure/ownership model is claimed. From 6dfae458b0a20711c5decc29313af00ea328a4c3 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:26 +0530 Subject: [PATCH 007/119] docs: Note the small forms of everyday C++ in the 0.2.0 release notes. --- website/source/release-notes.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index 85891b4f0f..32a0bc8bcb 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -413,6 +413,18 @@ that may write what another operand reads is an error at the expression, and an assignment's right operand is evaluated before its target, as C++17 says (:doc:`language/functions-loops`). +**Small forms of everyday C++.** Each of these stopped its function: +character literals (``'0'``, ``L'A'``), a ``consteval`` call and an ``if +constexpr`` condition, a template value parameter used as a value, a default +argument, ``static_assert``, a local ``typedef`` or ``using``, a local class +or enumeration, ``__builtin_expect``, ``return Pt{a, b};``, and ``auto [a, b] += p;``. Each is now what C++ makes it: a literal or a constant its value, a +default argument evaluated at the call, a declaration nothing at run time, +``__builtin_expect(e, c)`` the value of ``e``, an initializer list the fields +in order, and a structured binding a copy whose fields the bindings name +(:doc:`language/integers`, :doc:`language/structs`, +:doc:`language/functions-loops`). + **Pointer steps and reused names.** ``++p``, ``p--``, ``p += n``, and ``p -= n`` on a pointer were errors (``pointer increment/decrement is unsupported``, ``pointer compound assignment is unsupported``), and so was a From 43c5124ac8459316839663ecc5e167f218e673da Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:22:27 +0530 Subject: [PATCH 008/119] docs: Describe how structured bindings are named. --- docs/ARCHITECTURE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e26117327c..92babf67f2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2315,7 +2315,11 @@ When Z3 returns SAT, CppVerify evaluates source-attributed SSA variables without model completion. A local declared where its name is already taken in the function (by a parameter, or by a local of an enclosing or earlier scope) is `x#2`, `x#3`, ... in VCR (`ASTConverter::LocalNames`), so SSA keeps the -variables apart, and its display name stays `x`. Diagnostic metadata supplies the original display name, +variables apart, and its display name stays `x`. The object a structured +binding copies has no name in C++; it is `__binding` (and `__binding#2`, ...), +a reference to a binding is the member access Clang gives it +(`BindingDecl::getBinding`), and each field is displayed by its binding's +name. Diagnostic metadata supplies the original display name, exact logic sort, internal SSA identity, and declaration range. Signed and unsigned bit-vectors are rendered as source-level decimal values, and collections as their elements, members, counts, or entries in order, a run of From 8af0c393b310f573d068ad80e2ca61de9710ba1e Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:07:31 +0530 Subject: [PATCH 009/119] docs: Character literals in the book's switch example. --- .../source/book/part-ii/ch12-loops-in-practice.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/website/source/book/part-ii/ch12-loops-in-practice.rst b/website/source/book/part-ii/ch12-loops-in-practice.rst index 48d563c480..9512f8866b 100644 --- a/website/source/book/part-ii/ch12-loops-in-practice.rst +++ b/website/source/book/part-ii/ch12-loops-in-practice.rst @@ -91,11 +91,11 @@ loop over a switch needs the same invariant as one over an ``if``: cv::decreases(n - i) { switch (s[i]) { - case 97: // 'a' - case 101: // 'e' - case 105: // 'i' - case 111: // 'o' - case 117: // 'u' + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': count++; break; default: @@ -109,6 +109,8 @@ loop over a switch needs the same invariant as one over an ``if``: Verified: vowels [backend=z3] +Character literals such as ``'a'`` are integers of their type, as in C++. + A loop condition may declare a variable (``while (int k = next(i))``), made at every test; a ``for`` loop may leave out its condition (``for (;;)``) and end by ``break`` or ``return``, with a measure like any other; and its From 79ce617e2c2dcbc3a5031b69ac11678fb0096af9 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:16 +0530 Subject: [PATCH 010/119] verify: Add the unreachable proof obligation kind. --- clang/lib/Verify/IR/VStmt.h | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Verify/IR/VStmt.h b/clang/lib/Verify/IR/VStmt.h index 4d43bed959..833f1e5c3b 100644 --- a/clang/lib/Verify/IR/VStmt.h +++ b/clang/lib/Verify/IR/VStmt.h @@ -37,6 +37,7 @@ enum class ProofObligationKind { Frame, Deallocation, MissingReturn, + Unreachable, Unsupported }; From 78f6b8556c02cdb0402aa6765c1707e3c4794f11 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:17 +0530 Subject: [PATCH 011/119] verify: Add Require, true where evaluating it requires its condition. --- clang/lib/Verify/IR/VExpr.cpp | 11 +++++++++++ clang/lib/Verify/IR/VExpr.h | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/clang/lib/Verify/IR/VExpr.cpp b/clang/lib/Verify/IR/VExpr.cpp index 184006bd8f..a362e0ceb6 100644 --- a/clang/lib/Verify/IR/VExpr.cpp +++ b/clang/lib/Verify/IR/VExpr.cpp @@ -201,6 +201,11 @@ static std::unique_ptr cloneVExprImpl(const VExpr *E) { return std::make_unique( O->Op, cloneVExpr(O->Lhs.get()), cloneVExpr(O->Rhs.get()), O->Loc); } + case VExpr::Require: { + const auto *R = static_cast(E); + return std::make_unique(cloneVExpr(R->Cond.get()), + R->ProofKind, R->Loc); + } } return nullptr; } @@ -390,6 +395,9 @@ void verify::forEachVExprChild(const VExpr *E, visit(O->Rhs); return; } + case VExpr::Require: + visit(static_cast(E)->Cond); + return; } } @@ -467,5 +475,8 @@ void verify::forEachVExprChildSlot( visit(O->Rhs); return; } + case VExpr::Require: + visit(static_cast(E)->Cond); + return; } } diff --git a/clang/lib/Verify/IR/VExpr.h b/clang/lib/Verify/IR/VExpr.h index 4b4818cdc5..75324c847f 100644 --- a/clang/lib/Verify/IR/VExpr.h +++ b/clang/lib/Verify/IR/VExpr.h @@ -81,7 +81,8 @@ class VExpr { HeapFrame, FieldAccess, SpecCall, - OverflowCheck + OverflowCheck, + Require }; Kind K; @@ -311,6 +312,18 @@ class VOverflowCheckExpr : public VExpr { Rhs(std::move(R)) {} }; +/// True, where evaluating it requires \p Cond: a lifted constexpr helper's +/// assert(c) is Require(c) on its path, checked at each call as the helper's +/// undefined operations are. Its kind is a ProofObligationKind. +class VRequireExpr : public VExpr { +public: + std::unique_ptr Cond; + unsigned ProofKind; + VRequireExpr(std::unique_ptr C, unsigned ProofKind, SourceLocation Loc) + : VExpr(Require, VType::makeBool(), Loc), Cond(std::move(C)), + ProofKind(ProofKind) {} +}; + std::unique_ptr cloneVExpr(const VExpr *E); /// Calls \p Visit on each direct subexpression of \p E. From 904971b95c160c784fd27cf862b49f0fcd57c993 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:18 +0530 Subject: [PATCH 012/119] verify: Name the unreachable kind, and lower Require to true. --- clang/lib/Verify/Backend/Obligation.cpp | 12 ++++++++++++ clang/lib/Verify/Backend/Obligation.h | 1 + 2 files changed, 13 insertions(+) diff --git a/clang/lib/Verify/Backend/Obligation.cpp b/clang/lib/Verify/Backend/Obligation.cpp index fa36569e16..e6970cece7 100644 --- a/clang/lib/Verify/Backend/Obligation.cpp +++ b/clang/lib/Verify/Backend/Obligation.cpp @@ -269,6 +269,8 @@ ObligationKind verify::obligationKind(ProofObligationKind Kind) { return ObligationKind::Deallocation; case ProofObligationKind::MissingReturn: return ObligationKind::MissingReturn; + case ProofObligationKind::Unreachable: + return ObligationKind::Unreachable; case ProofObligationKind::Unsupported: return ObligationKind::Unsupported; } @@ -319,6 +321,8 @@ const char *verify::obligationKindName(ObligationKind Kind) { return "deallocation"; case ObligationKind::MissingReturn: return "missing-return"; + case ObligationKind::Unreachable: + return "unreachable"; case ObligationKind::Unsupported: return "unsupported"; } @@ -2116,6 +2120,14 @@ class ObligationBuilder { N->Children.push_back(std::move(Rhs)); return N; } + // Its requirement is checked where it is evaluated; its value is true. + case VExpr::Require: { + auto N = std::make_unique(VCExpr::BoolLit); + setBoolSort(*N); + N->Loc = E->Loc; + N->BoolVal = true; + return N; + } } return fail("unsupported VCR expression kind " + std::to_string(static_cast(E->K))); diff --git a/clang/lib/Verify/Backend/Obligation.h b/clang/lib/Verify/Backend/Obligation.h index e93984883f..188590a5ba 100644 --- a/clang/lib/Verify/Backend/Obligation.h +++ b/clang/lib/Verify/Backend/Obligation.h @@ -272,6 +272,7 @@ enum class ObligationKind { Frame, Deallocation, MissingReturn, + Unreachable, Unsupported }; From 1b45b0b15cfda9edce10ffc0f30b75549520b4de Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:18 +0530 Subject: [PATCH 013/119] verify: Archive the unreachable kind as tag 23. --- clang/lib/Verify/Backend/ObligationSerialization.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/lib/Verify/Backend/ObligationSerialization.cpp b/clang/lib/Verify/Backend/ObligationSerialization.cpp index 6b20cf5979..5cd365ff37 100644 --- a/clang/lib/Verify/Backend/ObligationSerialization.cpp +++ b/clang/lib/Verify/Backend/ObligationSerialization.cpp @@ -327,6 +327,8 @@ uint8_t obligationTag(ObligationKind Kind) { return 20; case ObligationKind::MissingReturn: return 21; + case ObligationKind::Unreachable: + return 23; case ObligationKind::Unsupported: return 22; } @@ -382,6 +384,8 @@ std::optional obligationFromTag(uint8_t Tag, uint32_t Version) { return ObligationKind::MissingReturn; case 22: return ObligationKind::Unsupported; + case 23: + return ObligationKind::Unreachable; default: return std::nullopt; } From 6d24c18cc1f4293f94f5ee748c99f0b1764ab3f8 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:19 +0530 Subject: [PATCH 014/119] verify: Print Require in Layer 1 dumps. --- clang/lib/Verify/Driver/DumpIR.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clang/lib/Verify/Driver/DumpIR.cpp b/clang/lib/Verify/Driver/DumpIR.cpp index 0ee62cf1ca..573f4876e1 100644 --- a/clang/lib/Verify/Driver/DumpIR.cpp +++ b/clang/lib/Verify/Driver/DumpIR.cpp @@ -206,6 +206,15 @@ void verify::dumpVExpr(const VExpr *E, llvm::raw_ostream &OS, unsigned Depth) { dumpVExpr(O->Rhs.get(), OS, Depth + 1); break; } + case VExpr::Require: { + const auto *R = static_cast(E); + OS << "require " + << obligationKindName( + obligationKind(static_cast(R->ProofKind))) + << "\n"; + dumpVExpr(R->Cond.get(), OS, Depth + 1); + break; + } } } From afa146dc25134309d8a2ac3dba1f2112a09e9597 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:19 +0530 Subject: [PATCH 015/119] verify: Make a lifted helper's asserts requirements of each call that reaches them. --- clang/lib/Verify/Transform/SpecInline.cpp | 72 +++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/clang/lib/Verify/Transform/SpecInline.cpp b/clang/lib/Verify/Transform/SpecInline.cpp index 2a79369019..0b3a5d5c87 100644 --- a/clang/lib/Verify/Transform/SpecInline.cpp +++ b/clang/lib/Verify/Transform/SpecInline.cpp @@ -2,6 +2,7 @@ #include "SpecInline.h" #include "../Transform/Passivize.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/SaveAndRestore.h" #include #include #include @@ -192,6 +193,9 @@ class SpecInlinerImpl { return evalExpr(E.get(), Env); } + /// The result type of the spec whose body is being evaluated. + VType Returned = VType::makeVoid(); + std::unique_ptr inlineSpecCall(const VSpecCallExpr &C) { if (InlineDepth++ >= MaxInlineDepth) return opaqueCall(C); @@ -205,6 +209,7 @@ class SpecInlinerImpl { if (It == FnMap.end() || !It->second->IsSpec || It->second->Uninterpreted) return opaqueCall(C); const VFunction &Spec = *It->second; + llvm::SaveAndRestore Returning(Returned, Spec.ReturnType); unsigned F = fuelFor(C.CalleeIdentity); if (Spec.NeedsDecreasesCheck) { if (F == 0) @@ -279,6 +284,9 @@ class SpecInlinerImpl { inlineQuantifiedCalls(O.Rhs); return; } + case VExpr::Require: + inlineQuantifiedCalls(static_cast(*E).Cond); + return; case VExpr::HeapStore: { auto &H = static_cast(*E); inlineQuantifiedCalls(H.Ptr); @@ -440,6 +448,10 @@ class SpecInlinerImpl { inlineDefinednessCalls(O.Rhs, InsideQuantifier); return; } + case VExpr::Require: + inlineDefinednessCalls(static_cast(*E).Cond, + InsideQuantifier); + return; case VExpr::Forall: case VExpr::Exists: { auto &Q = static_cast(*E); @@ -607,6 +619,14 @@ class SpecInlinerImpl { return std::make_unique(O->Op, std::move(Lhs), std::move(Rhs), O->Loc); } + case VExpr::Require: { + const auto *R = static_cast(E); + auto Cond = evalExpr(R->Cond.get(), Env); + if (!Cond) + return nullptr; + return std::make_unique(std::move(Cond), R->ProofKind, + R->Loc); + } case VExpr::SpecCall: { const auto *C = static_cast(E); std::vector> Args; @@ -787,6 +807,38 @@ class SpecInlinerImpl { return R; return evalBodySeq(Body, Env, Idx + 1, EvaluationTrace, Guard); } + // A lifted helper's assert(c) (an unreachable abort when c fails) is a + // requirement of every call that reaches it: Require(!path || c) joins + // the evaluation, whose checks each call makes. An assumption ends its + // path; nothing after it is evaluated there. + case VStmt::Assert: { + const auto &A = static_cast(S); + if (!EvaluationTrace) + return evalBodySeq(Body, Env, Idx + 1, EvaluationTrace, Guard); + auto Cond = evalExpr(A.Cond.get(), Env); + if (!Cond) + return nullptr; + if (Guard) + Cond = makeTraceOr(makeTraceNot(cloneVExpr(Guard), A.Loc), + std::move(Cond), A.Loc); + EvaluationTrace->push_back(std::make_unique( + std::move(Cond), static_cast(A.ProofKind), A.Loc)); + return evalBodySeq(Body, Env, Idx + 1, EvaluationTrace, Guard); + } + case VStmt::Assume: { + // A call never returns from the path an assumption of false ends, so + // its value there is any value; the requirement keeps calls off it. + const auto &A = static_cast(S); + if (A.Cond->K != VExpr::Literal || + static_cast(*A.Cond).Value != "0") + return nullptr; + if (Returned.Kind == VTypeKind::Bool) + return std::make_unique(false, Returned, A.Loc); + if (Returned.Kind == VTypeKind::Int32 || + Returned.Kind == VTypeKind::Int64 || Returned.Kind == VTypeKind::Ptr) + return std::make_unique(0, Returned, A.Loc); + return nullptr; + } default: return nullptr; } @@ -1035,6 +1087,9 @@ void verify::collectSpecCalls(const VExpr *E, collectSpecCalls(O->Rhs.get(), Out); return; } + case VExpr::Require: + collectSpecCalls(static_cast(E)->Cond.get(), Out); + return; case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -1255,6 +1310,11 @@ std::unique_ptr verify::substParamsInExpr( O->Op, substParamsInExpr(O->Lhs.get(), Map), O->Rhs ? substParamsInExpr(O->Rhs.get(), Map) : nullptr, O->Loc); } + case VExpr::Require: { + const auto *R = static_cast(E); + return std::make_unique(substParamsInExpr(R->Cond.get(), Map), + R->ProofKind, R->Loc); + } case VExpr::Result: if (auto It = Map.find(ResultKey); It != Map.end()) return cloneVExpr(It->second.get()); @@ -1615,6 +1675,11 @@ static void collectRecursiveSpecCallsInExpr( Quantifiers, Unsupported); return; } + case VExpr::Require: + collectRecursiveSpecCallsInExpr( + static_cast(E)->Cond.get(), Fn, FnMap, Env, Guard, + Sites, Quantifiers, Unsupported); + return; case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -2120,6 +2185,10 @@ struct SpecBodyCollector { expr(O->Rhs.get(), Env, Guard, Quantifiers); return; } + case VExpr::Require: + expr(static_cast(E)->Cond.get(), Env, Guard, + Quantifiers); + return; case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -2422,6 +2491,9 @@ static void readHeapAt(VExpr *E, const std::string &Heap) { readHeapAt(O->Rhs.get(), Heap); return; } + case VExpr::Require: + readHeapAt(static_cast(E)->Cond.get(), Heap); + return; case VExpr::HeapStore: case VExpr::HeapFrame: case VExpr::Literal: From 0b4d2834655354f9df337f6a8eb9cb7afa580802 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:20 +0530 Subject: [PATCH 016/119] verify: Check a Require's condition where it is evaluated. --- clang/lib/Verify/Transform/Passivize.cpp | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/clang/lib/Verify/Transform/Passivize.cpp b/clang/lib/Verify/Transform/Passivize.cpp index 4ee7df5281..4d3389bf76 100644 --- a/clang/lib/Verify/Transform/Passivize.cpp +++ b/clang/lib/Verify/Transform/Passivize.cpp @@ -325,6 +325,11 @@ static std::unique_ptr cloneExprImpl(const VExpr *E, O->Op, cloneExpr(O->Lhs.get(), Ctx), O->Rhs ? cloneExpr(O->Rhs.get(), Ctx) : nullptr, O->Loc); } + case VExpr::Require: { + const auto *R = static_cast(E); + return std::make_unique(cloneExpr(R->Cond.get(), Ctx), + R->ProofKind, R->Loc); + } case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -1160,6 +1165,9 @@ static bool referencesVar(const VExpr *E, const std::string &Name) { return referencesVar(O->Lhs.get(), Name) || referencesVar(O->Rhs.get(), Name); } + case VExpr::Require: + return referencesVar(static_cast(E)->Cond.get(), + Name); } return false; } @@ -1287,6 +1295,9 @@ static bool scalarDynamicExprSafe(const VExpr *E, const std::string &Param) { return scalarDynamicExprSafe(O->Lhs.get(), Param) && scalarDynamicExprSafe(O->Rhs.get(), Param); } + case VExpr::Require: + return scalarDynamicExprSafe( + static_cast(E)->Cond.get(), Param); } return false; } @@ -2294,6 +2305,13 @@ collectSafety(const VExpr *E, const FunctionMap *FnMap, Collect(O->Rhs.get(), Out); return; } + case VExpr::Require: { + const auto *R = static_cast(E); + Collect(R->Cond.get(), Out); + addSafety(Out, static_cast(R->ProofKind), + cloneVExpr(R->Cond.get()), R->Loc); + return; + } case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -2497,6 +2515,9 @@ machineMathBridgeForExpr(const VExpr *E, const FunctionMap *FnMap, C->Loc); return Bridge; } + case VExpr::Require: + return machineMathBridgeForExpr( + static_cast(E)->Cond.get(), FnMap, false); case VExpr::OverflowCheck: { const auto *O = static_cast(E); auto Bridge = @@ -2627,6 +2648,9 @@ static void collectDottedVars(const VExpr *E, std::set &Out) { collectDottedVars(O->Rhs.get(), Out); return; } + case VExpr::Require: + collectDottedVars(static_cast(E)->Cond.get(), Out); + return; case VExpr::FieldAccess: { const auto *F = static_cast(E); if (F->Base->K == VExpr::Var) { @@ -2787,6 +2811,13 @@ static std::unique_ptr substParams( : nullptr, O->Loc); } + case VExpr::Require: { + const auto *R = static_cast(E); + return std::make_unique( + substParams(R->Cond.get(), Map, Ctx, EntryHeap, HeapOverride, BoundVars, + OldMap, NormalizedPointerChecks), + R->ProofKind, R->Loc); + } case VExpr::Forall: case VExpr::Exists: { const auto *Q = static_cast(E); @@ -3140,6 +3171,9 @@ collectFramedApplications(const VExpr *E, const FunctionMap &FnMap, visit(O->Rhs.get()); return; } + case VExpr::Require: + visit(static_cast(E)->Cond.get()); + return; case VExpr::SpecCall: { const auto *C = static_cast(E); for (const auto &Arg : C->Args) From 5cccc1fae0ad9fec1414ef7afab318e7d713b7e2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:21 +0530 Subject: [PATCH 017/119] verify: Walk Require in the ownership scan. --- clang/lib/Verify/Transform/Ownership.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Verify/Transform/Ownership.cpp b/clang/lib/Verify/Transform/Ownership.cpp index 4b2a7a82b5..d3465d0a7c 100644 --- a/clang/lib/Verify/Transform/Ownership.cpp +++ b/clang/lib/Verify/Transform/Ownership.cpp @@ -103,6 +103,7 @@ class OwnedReturnAnalyzer { case VExpr::FieldAccess: case VExpr::SpecCall: case VExpr::OverflowCheck: + case VExpr::Require: return PointerValue{true}; } return PointerValue{true}; From 02d6a0f6d50fbccdff26d6be71273af6ca03c170 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:21 +0530 Subject: [PATCH 018/119] verify: Walk Require in the UB scan. --- clang/lib/Verify/Transform/UBChecks.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/Verify/Transform/UBChecks.cpp b/clang/lib/Verify/Transform/UBChecks.cpp index 7324cc217b..c6a09b2b96 100644 --- a/clang/lib/Verify/Transform/UBChecks.cpp +++ b/clang/lib/Verify/Transform/UBChecks.cpp @@ -142,6 +142,8 @@ bool containsValidCall(const VExpr *E) { const auto *O = static_cast(E); return containsValidCall(O->Lhs.get()) || containsValidCall(O->Rhs.get()); } + case VExpr::Require: + return containsValidCall(static_cast(E)->Cond.get()); } return false; } From 77a7765a52bad8bd27f3141ea984a0420819a70c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:22 +0530 Subject: [PATCH 019/119] verify: Verify assert, calls that do not return, assumptions, and discarded values. --- clang/lib/Verify/Frontend/ASTConverter.cpp | 216 +++++++++++++++++---- clang/lib/Verify/Frontend/ASTConverter.h | 9 + 2 files changed, 184 insertions(+), 41 deletions(-) diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 5fa49c9753..4489022237 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -243,6 +243,8 @@ static bool mentionsOld(const VExpr *E) { const auto *O = static_cast(E); return mentionsOld(O->Lhs.get()) || mentionsOld(O->Rhs.get()); } + case VExpr::Require: + return mentionsOld(static_cast(E)->Cond.get()); } llvm_unreachable("unknown VCR expression kind"); } @@ -326,6 +328,9 @@ usesHeapBackedLocalAsScalar(const VExpr *E, return usesHeapBackedLocalAsScalar(Check->Lhs.get(), HeapBackedLocals) || usesHeapBackedLocalAsScalar(Check->Rhs.get(), HeapBackedLocals); } + case VExpr::Require: + return usesHeapBackedLocalAsScalar( + static_cast(E)->Cond.get(), HeapBackedLocals); } llvm_unreachable("unknown VCR expression kind"); } @@ -385,6 +390,9 @@ static bool expressionReadsHeap(const VExpr *E, bool SpecReads = false) { return expressionReadsHeap(O->Lhs.get(), SpecReads) || expressionReadsHeap(O->Rhs.get(), SpecReads); } + case VExpr::Require: + return expressionReadsHeap(static_cast(E)->Cond.get(), + SpecReads); } llvm_unreachable("unknown VCR expression kind"); } @@ -1140,6 +1148,9 @@ static bool exprReferencesSpecCall(const VExpr *E, const std::string &Name) { return exprReferencesSpecCall(O->Lhs.get(), Name) || exprReferencesSpecCall(O->Rhs.get(), Name); } + case VExpr::Require: + return exprReferencesSpecCall( + static_cast(E)->Cond.get(), Name); case VExpr::Literal: case VExpr::Var: case VExpr::Result: @@ -5240,6 +5251,13 @@ static bool structuralKey(const VExpr *E, return false; break; } + case VExpr::Require: { + const auto *R = static_cast(E); + Key += std::to_string(R->ProofKind); + if (!sub(R->Cond.get())) + return false; + break; + } case VExpr::HeapStore: case VExpr::HeapFrame: return false; @@ -6155,6 +6173,12 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { if (ICE->getCastKind() == CK_NullToPointer) return std::make_unique(0, VType::makePtr(), E->getExprLoc()); + // A string literal is an object, so its address is never null: + // assert(c && "message"). + if (ICE->getCastKind() == CK_PointerToBoolean && + isa(ICE->getSubExpr()->IgnoreParenImpCasts())) + return std::make_unique(1, VType::makeBool(), + E->getExprLoc()); auto Inner = convertExpr(ICE->getSubExpr()); if (!Inner) return nullptr; @@ -7789,6 +7813,39 @@ void ASTConverter::leaveAutomaticScope(std::vector> &Out, } } +void ASTConverter::appendBranches(std::unique_ptr Cond, const Stmt *Then, + const Stmt *Else, SourceLocation Loc, + std::vector> &Out) { + const std::set Before = InitializedValues; + const bool BeforeReachable = InitializationPathReachable; + auto ThenStmts = convertScopedSubstatement(Then); + const std::set ThenInitialized = InitializedValues; + const bool ThenReachable = InitializationPathReachable; + InitializedValues = Before; + InitializationPathReachable = BeforeReachable; + std::vector> ElseStmts; + if (Else) + ElseStmts = convertScopedSubstatement(Else); + const std::set ElseInitialized = InitializedValues; + const bool ElseReachable = InitializationPathReachable; + if (ThenReachable && ElseReachable) { + std::set Merged; + std::set_intersection(ThenInitialized.begin(), ThenInitialized.end(), + ElseInitialized.begin(), ElseInitialized.end(), + std::inserter(Merged, Merged.end())); + InitializedValues = std::move(Merged); + } else if (ThenReachable) { + InitializedValues = ThenInitialized; + } else if (ElseReachable) { + InitializedValues = ElseInitialized; + } else { + InitializedValues = Before; + } + InitializationPathReachable = ThenReachable || ElseReachable; + Out.push_back(std::make_unique(std::move(Cond), std::move(ThenStmts), + std::move(ElseStmts), Loc)); +} + std::vector> ASTConverter::convertScopedSubstatement(const Stmt *S) { if (isa_and_nonnull(S)) @@ -8028,6 +8085,47 @@ ASTConverter::convertSwitch(const SwitchStmt *SS) { return Out; } +void ASTConverter::appendAssumption(const Expr *Assumption, SourceLocation Loc, + std::vector> &Out) { + const bool Executable = !InGhost && !CurrentFn->IsSpec && !CurrentFn->IsProof; + llvm::SaveAndRestore Calls(CallSink, Executable ? &Out : nullptr); + auto Holds = convertExpr(Assumption); + if (!Holds) + return; + Out.push_back(std::make_unique(cloneVExpr(Holds.get()), Loc, + ProofObligationKind::Assertion)); + Out.push_back(std::make_unique(std::move(Holds), Loc)); +} + +void ASTConverter::appendDiscarded(const Expr *E, + std::vector> &Out) { + // A discarded value is computed, with its checks, when it is a prvalue; a + // glvalue that is not volatile is not even read ([expr.context]). + if (E->HasSideEffects(Ctx)) { + error(CurrentFn->Name + ": unsupported expression statement"); + return; + } + if (E->isGLValue() || E->getType()->isVoidType()) + return; + auto Value = convertExpr(E); + if (!Value) + return; + Out.push_back(std::make_unique( + "__discarded_" + std::to_string(++NestedCallId), std::move(Value), + E->getExprLoc())); +} + +/// abort, exit, std::terminate, __builtin_unreachable, __builtin_trap, the +/// failure of assert(), and every other [[noreturn]] function. MSVC's +/// _wassert and _assert, which assert() calls when it fails, are not marked. +static bool doesNotReturn(const FunctionDecl *Callee) { + if (Callee->isNoReturn()) + return true; + const IdentifierInfo *Name = Callee->getIdentifier(); + return Name && Callee->isExternC() && + (Name->isStr("_wassert") || Name->isStr("_assert")); +} + void ASTConverter::appendReturn(std::unique_ptr Value, std::vector> &Out, SourceLocation Loc) { @@ -8238,37 +8336,47 @@ ASTConverter::convertStmtBody(const Stmt *S) { leaveAutomaticScope(Out, IS->getEndLoc()); return Out; } - const std::set Before = InitializedValues; - const bool BeforeReachable = InitializationPathReachable; - auto Then = convertScopedSubstatement(IS->getThen()); - const std::set ThenInitialized = InitializedValues; - const bool ThenReachable = InitializationPathReachable; - InitializedValues = Before; - InitializationPathReachable = BeforeReachable; - std::vector> Else; - if (IS->getElse()) - Else = convertScopedSubstatement(IS->getElse()); - const std::set ElseInitialized = InitializedValues; - const bool ElseReachable = InitializationPathReachable; - if (ThenReachable && ElseReachable) { - std::set Merged; - std::set_intersection(ThenInitialized.begin(), ThenInitialized.end(), - ElseInitialized.begin(), ElseInitialized.end(), - std::inserter(Merged, Merged.end())); - InitializedValues = std::move(Merged); - } else if (ThenReachable) { - InitializedValues = ThenInitialized; - } else if (ElseReachable) { - InitializedValues = ElseInitialized; - } else { - InitializedValues = Before; - } - InitializationPathReachable = ThenReachable || ElseReachable; - Out.push_back(std::make_unique( - std::move(Cond), std::move(Then), std::move(Else), IS->getBeginLoc())); + appendBranches(std::move(Cond), IS->getThen(), IS->getElse(), + IS->getBeginLoc(), Out); leaveAutomaticScope(Out, IS->getEndLoc()); return Out; } + // A discarded ?:, &&, or || runs its operands on their paths, as an if + // does; assert(e) expands to one. A discarded operand's implicit + // conversion only evaluates it. + if (const auto *Paren = dyn_cast(S)) + return convertStmt(Paren->getSubExpr()); + if (const auto *Implicit = dyn_cast(S); + Implicit && !Implicit->getType().isVolatileQualified()) + return convertStmt(Implicit->getSubExpr()); + if (const auto *Choice = dyn_cast(S)) { + std::unique_ptr Cond; + { + llvm::SaveAndRestore Calls(CallSink, Executable ? &Out : nullptr); + Cond = convertExpr(Choice->getCond()); + } + if (Cond) + appendBranches(std::move(Cond), Choice->getTrueExpr(), + Choice->getFalseExpr(), Choice->getExprLoc(), Out); + return Out; + } + if (const auto *Logical = dyn_cast(S); + Logical && Logical->isLogicalOp()) { + std::unique_ptr Cond; + { + llvm::SaveAndRestore Calls(CallSink, Executable ? &Out : nullptr); + Cond = convertExpr(Logical->getLHS()); + } + if (!Cond) + return Out; + if (Logical->getOpcode() == BO_LOr) + Cond = std::make_unique(VUnaryOp::Not, std::move(Cond), + VType::makeBool(), + Logical->getExprLoc()); + appendBranches(std::move(Cond), Logical->getRHS(), nullptr, + Logical->getExprLoc(), Out); + return Out; + } if (isa(S) || isa(S)) { const bool IsBreak = isa(S); // A break leaves the innermost loop or switch, a continue the innermost @@ -8521,13 +8629,15 @@ ASTConverter::convertStmtBody(const Stmt *S) { return convertSwitch(SS); if (const auto *AS = dyn_cast(S)) { // [[fallthrough]], [[likely]], and the like do not change what a - // statement does; [[assume(e)]] makes e false undefined. - if (llvm::any_of(AS->getAttrs(), - [](const Attr *A) { return isa(A); })) { - error(CurrentFn->Name + ": [[assume]] is unsupported"); - return Out; - } - return convertStmt(AS->getSubStmt()); + // statement does; [[assume(e)]] makes e false undefined, so e is proved, + // then assumed. + for (const Attr *A : AS->getAttrs()) + if (const auto *Assume = dyn_cast(A)) + appendAssumption(Assume->getAssumption(), Assume->getLocation(), Out); + auto Rest = convertStmt(AS->getSubStmt()); + Out.insert(Out.end(), std::make_move_iterator(Rest.begin()), + std::make_move_iterator(Rest.end())); + return Out; } if (const auto *Cleanups = dyn_cast(S); Cleanups && onlyCollectionTemporaries(Cleanups)) @@ -8602,7 +8712,27 @@ ASTConverter::convertStmtBody(const Stmt *S) { } } if (const auto *CE = dyn_cast(S)) { + // __builtin_assume(e): e false is undefined, so e is proved, then + // assumed. + if (CE->getBuiltinCallee() == Builtin::BI__builtin_assume) { + appendAssumption(CE->getArg(0), CE->getExprLoc(), Out); + return Out; + } if (const FunctionDecl *Callee = CE->getDirectCallee()) { + // A call that does not return (abort, exit, a failed assert, + // __builtin_unreachable) must not be reached: the path ends there. + if (doesNotReturn(Callee)) { + Out.push_back(std::make_unique( + std::make_unique(0, VType::makeBool(), + CE->getExprLoc()), + CE->getExprLoc(), ProofObligationKind::Unreachable)); + Out.push_back(std::make_unique( + std::make_unique(0, VType::makeBool(), + CE->getExprLoc()), + CE->getExprLoc())); + InitializationPathReachable = false; + return Out; + } if ((InGhost || CurrentFn->IsProof) && !calleeIsProof(Callee)) { error(CurrentFn->Name + ": proof-only code cannot call executable functions"); @@ -8941,7 +9071,7 @@ ASTConverter::convertStmtBody(const Stmt *S) { auto Value = convertAssignmentValue(BO); appendAssignment(BO->getLHS(), std::move(Value), BO->getExprLoc(), Out); } else - error(CurrentFn->Name + ": unsupported expression statement"); + appendDiscarded(BO, Out); return Out; } if (const auto *U = dyn_cast(S)) { @@ -8993,11 +9123,11 @@ ASTConverter::convertStmtBody(const Stmt *S) { error(CurrentFn->Name + ": unsupported unary expression statement"); return Out; } - if (const auto *CE = dyn_cast(S)) { - const Expr *Sub = CE->getSubExpr()->IgnoreParenImpCasts(); - if (CE->getCastKind() == CK_ToVoid && isa(Sub) && - !Sub->getType().isVolatileQualified()) - return Out; + // (void)e, static_cast(e), and void(e) evaluate e and discard it. + if (const auto *CE = dyn_cast(S); + CE && CE->getCastKind() == CK_ToVoid) + return convertStmt(CE->getSubExpr()); + if (isa(S)) { error(CurrentFn->Name + ": unsupported discarded-value expression"); return Out; } @@ -9363,6 +9493,10 @@ ASTConverter::convertStmtBody(const Stmt *S) { } return Out; } + if (const auto *E = dyn_cast(S); E && !E->HasSideEffects(Ctx)) { + appendDiscarded(E, Out); + return Out; + } error(CurrentFn->Name + ": unsupported statement: " + S->getStmtClassName()); return Out; } \ No newline at end of file diff --git a/clang/lib/Verify/Frontend/ASTConverter.h b/clang/lib/Verify/Frontend/ASTConverter.h index b4df47980c..c8a0cdfb41 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.h +++ b/clang/lib/Verify/Frontend/ASTConverter.h @@ -321,6 +321,15 @@ class ASTConverter { std::vector> convertStmtBody(const Stmt *S); std::vector> convertScopedSubstatement(const Stmt *S); std::vector> convertSwitch(const SwitchStmt *SS); + /// if (Cond) Then else Else, with what each path initializes. + void appendBranches(std::unique_ptr Cond, const Stmt *Then, + const Stmt *Else, SourceLocation Loc, + std::vector> &Out); + /// A discarded-value expression statement without side effects. + void appendDiscarded(const Expr *E, std::vector> &Out); + /// [[assume(e)]] and __builtin_assume(e): e is proved, then assumed. + void appendAssumption(const Expr *Assumption, SourceLocation Loc, + std::vector> &Out); /// The declaration of a loop's condition variable, made at every test. void declareConditionVariable(const DeclStmt *Decl, std::vector> &Out); From c11fe238ef15df711f3126bc1f34010432441b0f Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:22 +0530 Subject: [PATCH 020/119] test: assert, calls that do not return, assumptions, discarded values, lifted helpers' asserts, and LLVM's MathExtras. --- clang/test/Verify/suite/assertions.cpp | 337 +++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 clang/test/Verify/suite/assertions.cpp diff --git a/clang/test/Verify/suite/assertions.cpp b/clang/test/Verify/suite/assertions.cpp new file mode 100644 index 0000000000..73278119ae --- /dev/null +++ b/clang/test/Verify/suite/assertions.cpp @@ -0,0 +1,337 @@ +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++23 -Wno-unused-value > %t 2>&1 +// RUN: FileCheck %s < %t +// RUN: FileCheck %s --check-prefix=SOUND < %t +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++23 -Wno-unused-value -DNDEBUG 2>&1 | FileCheck %s --check-prefix=NDEBUG +// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=2 %s -- -std=c++23 -Wno-unused-value 2>&1 | FileCheck %s --check-prefix=BMC +// RUN: %cpp-verify --lower-only --dump-ir=1 %s -- -std=c++23 -Wno-unused-value 2>&1 | FileCheck %s --check-prefix=VCR +// +// assert(c), and every call that does not return (abort, exit, +// std::terminate, __builtin_unreachable, __builtin_trap), must not be +// reached where it ends the program: reaching one is a failure of kind +// unreachable, and the path ends there, so after assert(c) the condition +// holds. With NDEBUG, assert(c) is nothing, as compiled. [[assume(e)]] and +// __builtin_assume(e) make e false undefined, so e is proved, then assumed. +// Every correct function has a wrong twin that must be refuted. +// +// No proof here holds only because no execution reaches its end. +// SOUND-NOT: vacuous + +#include +#include +#include +#include + +namespace cv = cppverify; + +int checked(int x) + cv::pre(x > 0) + cv::post(cv::result == x) +{ + assert(x > 0 && "x must be positive"); + return x; +} +// CHECK-DAG: Verified: checked [backend=z3] +// NDEBUG-DAG: Verified: checked [backend=z3] + +int checked_wrong(int x) + cv::post(cv::result == x) +{ + assert(x > 0 && "x must be positive"); + return x; +} +// CHECK-DAG: error: verification failed: checked_wrong [{{.*}}::unreachable@[[@LINE-3]]:3] (counterexample: x [ssa=x_0] [type=i32] = {{-?[0-9]+}}{{.*}}[reason=counterexample] +// NDEBUG-DAG: Verified: checked_wrong [backend=z3] + +// MSVC's assert expands to a || whose right operand calls _wassert, which +// is not marked [[noreturn]]. +extern "C" void _wassert(const wchar_t *, const wchar_t *, unsigned); + +int msvc_assert(int x) + cv::post(cv::result > 0) +{ + (void)((!!(x > 0)) || (_wassert(L"x > 0", L"assertions.cpp", 1), 0)); + return x; +} +// CHECK-DAG: error: verification failed: msvc_assert [{{.*}}::unreachable@[[@LINE-3]]:{{.*}}[reason=counterexample] + +// After assert(c), c holds. +int positive_after(int x) + cv::pre(x != 0) + cv::post(cv::result > 0) +{ + assert(x > 0); + return x; +} +// CHECK-DAG: error: verification failed: positive_after [{{.*}}::unreachable@[[@LINE-3]]:3]{{.*}}[reason=counterexample] +// NDEBUG-DAG: error: verification failed: positive_after [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +int positive_after_check(int x) + cv::pre(x > 0) + cv::post(cv::result > 0) +{ + assert(x > 0); + return x; +} +// CHECK-DAG: Verified: positive_after_check [backend=z3] + +int aborts(int x) + cv::post(cv::result >= 0) +{ + if (x < 0) + abort(); + return x; +} +// CHECK-DAG: error: verification failed: aborts [{{.*}}::unreachable@[[@LINE-3]]:5] (counterexample: x [ssa=x_0] [type=i32] = {{-[0-9]+}}{{.*}}[reason=counterexample] + +int aborts_guarded(int x) + cv::pre(x >= 0) + cv::post(cv::result >= 0) +{ + if (x < 0) + abort(); + return x; +} +// CHECK-DAG: Verified: aborts_guarded [backend=z3] + +int exits(int code) + cv::pre(code != 0) + cv::post(true) +{ + if (code == 0) + exit(1); + if (code > 100) + std::terminate(); + return code; +} +// CHECK-DAG: error: verification failed: exits [{{.*}}::unreachable@[[@LINE-3]]:5] (counterexample: code [ssa=code_0] [type=i32] = {{[0-9]+}}{{.*}}[reason=counterexample] + +int classify(int x) + cv::pre(0 <= x && x < 3) + cv::post(cv::result == x + 1) +{ + if (x == 0) + return 1; + if (x == 1) + return 2; + if (x == 2) + return 3; + __builtin_unreachable(); +} +// CHECK-DAG: Verified: classify [backend=z3] + +int classify_wrong(int x) + cv::pre(0 <= x && x < 4) + cv::post(cv::result == x + 1) +{ + if (x == 0) + return 1; + if (x == 1) + return 2; + if (x == 2) + return 3; + __builtin_trap(); +} +// CHECK-DAG: error: verification failed: classify_wrong [{{.*}}::unreachable@[[@LINE-2]]:3] (counterexample: x [ssa=x_0] [type=i32] = 3{{.*}}[reason=counterexample] + +// An assumption is proved where it stands, then holds. +int assumed(int x) + cv::pre(x > 5) + cv::post(cv::result > 1) +{ + [[assume(x > 0)]]; + __builtin_assume(x > 1); + return x; +} +// CHECK-DAG: Verified: assumed [backend=z3] + +int assumed_wrong(int x) + cv::post(cv::result > 0) +{ + [[assume(x > 0)]]; + return x; +} +// CHECK-DAG: error: verification failed: assumed_wrong [{{.*}}::assertion@[[@LINE-3]]:{{.*}}x [ssa=x_0] [type=i32] = {{-?[0-9]+}}{{.*}}[reason=counterexample] + +// A discarded value is computed, with its checks; the operands of a +// discarded ||, &&, or ?: run on their paths. +int discarded(int x) + cv::pre(x == 2147483647) + cv::post(cv::result == 0) +{ + x + 1; + return 0; +} +// CHECK-DAG: error: verification failed: discarded [{{.*}}::overflow@[[@LINE-3]]:{{.*}}[reason=counterexample] + +int or_else(int x) + cv::post(cv::result > 0) +{ + x > 0 || (abort(), 0); + return x; +} +// CHECK-DAG: error: verification failed: or_else [{{.*}}::unreachable@[[@LINE-3]]:{{.*}}[reason=counterexample] + +int or_else_guarded(int x) + cv::pre(x > 0) + cv::post(cv::result > 0) +{ + x > 0 || (abort(), 0); + x <= 0 ? abort() : (void)0; + (void)(x > 0 && x < 2147483647); + return x; +} +// CHECK-DAG: Verified: or_else_guarded [backend=z3] + +// A constexpr helper without a contract is lifted; its assert(c) is a +// requirement of each call, checked there, as its undefined operations are. +constexpr int half(int x) { + assert(x % 2 == 0); + return x / 2; +} + +constexpr int quarter(int x) { return half(half(x)); } + +int use_half(int x) + cv::pre(-1000 < x && x < 1000 && x % 2 == 0) + cv::post(2 * cv::result == x) +{ + return half(x); +} +// CHECK-DAG: Verified: use_half [backend=z3] + +int use_half_wrong(int x) + cv::pre(-1000 < x && x < 1000) + cv::post(true) +{ + return half(x); +} +// CHECK-DAG: error: verification failed: use_half_wrong [{{.*}}::unreachable@[[@LINE-2]]:{{.*}}[reason=counterexample] + +int use_quarter_wrong(int x) + cv::pre(-1000 < x && x < 1000 && x % 4 == 2) + cv::post(true) +{ + return quarter(x); +} +// CHECK-DAG: error: verification failed: use_quarter_wrong [{{.*}}::unreachable@[[@LINE-2]]:{{.*}}[reason=counterexample] +// NDEBUG-DAG: Verified: use_quarter_wrong [backend=z3] + +// The requirement holds on the helper's path to the call that does not +// return, and the helper has a value wherever it returns. +constexpr int checked_div(int a, int b) { + if (b == 0) + abort(); + return a / b; +} + +constexpr int must_be_positive(int x) { + if (x > 0) + return x; + abort(); +} + +int use_div(int a, int b) + cv::pre(-1000 < a && a < 1000 && b > 0) + cv::post(cv::result == a / b) +{ + return checked_div(a, b); +} +// CHECK-DAG: Verified: use_div [backend=z3] + +int use_div_wrong(int a, int b) + cv::pre(-1000 < a && a < 1000) + cv::post(true) +{ + return checked_div(a, b); +} +// CHECK-DAG: error: verification failed: use_div_wrong [{{.*}}::unreachable@[[@LINE-2]]:{{.*}}b [ssa=b_0] [type=i32] = 0{{.*}}[reason=counterexample] + +int use_positive(int x) + cv::pre(x > 0) + cv::post(cv::result == x) +{ + return must_be_positive(x); +} +// CHECK-DAG: Verified: use_positive [backend=z3] + +// LLVM 23.1.3's MathExtras.h, verbatim but for the contracts. +constexpr uint64_t divideCeil(uint64_t Numerator, uint64_t Denominator) { + assert(Denominator && "Division by zero"); + uint64_t Bias = (Numerator != 0); + return (Numerator - Bias) / Denominator + Bias; +} + +constexpr uint64_t alignTo(uint64_t Value, uint64_t Align) + cv::pre(Align != 0 && Value <= UINT64_MAX - Align) + cv::post(cv::result % Align == 0) + cv::post(Value <= cv::result && cv::result < Value + Align) +{ + assert(Align != 0u && "Align can't be 0."); + uint64_t CeilDiv = divideCeil(Value, Align); + return CeilDiv * Align; +} +// CHECK-DAG: Verified: alignTo [backend=z3] + +uint64_t alignTo_zero(uint64_t Value) + cv::post(true) +{ + return alignTo(Value, 0); +} +// CHECK-DAG: error: verification failed: alignTo_zero [{{.*}}::precondition@[[@LINE-2]]:{{.*}}[reason=counterexample] + +inline constexpr uint64_t maxUIntN(uint64_t N) + cv::pre(N <= 64) + cv::post(N == 64 || cv::result + 1 == ((__int128)1 << N)) +{ + assert(N <= 64 && "integer width out of range"); + + // uint64_t(1) << 64 is undefined behavior, so we can't do + // (uint64_t(1) << N) - 1 + // without checking first that N != 64. But this works and doesn't have a + // branch for N != 0. + // Unfortunately, shifting a uint64_t right by 64 bit is undefined + // behavior, so the condition on N == 0 is necessary. Fortunately, most + // optimizers do not emit branches for this check. + if (N == 0) + return 0; + return UINT64_MAX >> (64 - N); +} +// CHECK-DAG: Verified: maxUIntN [backend=z3] + +inline int64_t SignExtend64(uint64_t X, unsigned B) + cv::pre(B <= 64) + cv::post(B == 0 || B == 64 || + (-((__int128)1 << (B - 1)) <= cv::result && + cv::result < ((__int128)1 << (B - 1)))) +{ + assert(B <= 64 && "Bit width out of range."); + if (B == 0) + return 0; + return int64_t(X << (64 - B)) >> (64 - B); +} +// CHECK-DAG: Verified: SignExtend64 [backend=z3] + +int64_t SignExtend64_wide(uint64_t X, unsigned B) + cv::post(true) +{ + assert(B <= 64 && "Bit width out of range."); + if (B == 0) + return 0; + return int64_t(X << (64 - B)) >> (64 - B); +} +// CHECK-DAG: error: verification failed: SignExtend64_wide [{{.*}}::unreachable@[[@LINE-5]]:3] (counterexample: {{.*}}B [ssa=B_0] [type=u32] = {{[0-9]+}}{{.*}}[reason=counterexample] +// NDEBUG-DAG: error: verification failed: SignExtend64_wide [{{.*}}::shift@{{.*}}[reason=counterexample] + +// BMC: Verified: checked [backend=bmc, bound=0] +// BMC: error: verification failed: checked_wrong [{{.*}}::unreachable@ +// BMC: error: verification failed: aborts [{{.*}}::unreachable@ + +// VCR-LABEL: fn aborts +// VCR: {{^ +}}if{{$}} +// VCR-NEXT: < +// VCR-NEXT: x +// VCR-NEXT: 0 +// VCR-NEXT: assert unreachable +// VCR-NEXT: false +// VCR-NEXT: assume +// VCR-NEXT: false From 5f2da60e0c24e5a453b3d16a6f6b5c100c9dee8a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:23 +0530 Subject: [PATCH 021/119] test: [[assume]] is proved, not rejected. --- clang/test/Verify/suite/switch_statements.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/Verify/suite/switch_statements.cpp b/clang/test/Verify/suite/switch_statements.cpp index 7e6f537cce..774370e165 100644 --- a/clang/test/Verify/suite/switch_statements.cpp +++ b/clang/test/Verify/suite/switch_statements.cpp @@ -653,9 +653,9 @@ int lent(int x) // CHECK-DAG: Verified: lent [backend=z3] // Unsupported, each alone: a label inside another statement of its switch -// (as in Duff's device), [[assume]], and a local whose address is used -// declared directly in a switch, which a later label enters without -// declaring. +// (as in Duff's device), and a local whose address is used declared +// directly in a switch, which a later label enters without declaring. +// [[assume(e)]] is proved where it stands (assertions.cpp). int duff(int n) cv::post(true) { @@ -680,7 +680,7 @@ int assumed(int x) [[assume(x > 0)]]; return x; } -// CHECK-DAG: switch_statements.cpp:[[@LINE-3]]:3: error: assumed: {{\[\[}}assume]] is unsupported +// CHECK-DAG: error: verification failed: assumed [{{.*}}::assertion@[[@LINE-3]]:{{.*}}[reason=counterexample] int lent_directly(int x) cv::post(true) From faab2c54bfe2a7d8e8a7d3efced320ff1d817f46 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:24 +0530 Subject: [PATCH 022/119] test: Move the discarded sum, now checked, to assertions.cpp. --- clang/test/Verify/suite/z3_calls_fail_closed.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/clang/test/Verify/suite/z3_calls_fail_closed.cpp b/clang/test/Verify/suite/z3_calls_fail_closed.cpp index 9394fd2803..d04d906aaa 100644 --- a/clang/test/Verify/suite/z3_calls_fail_closed.cpp +++ b/clang/test/Verify/suite/z3_calls_fail_closed.cpp @@ -48,14 +48,6 @@ int *unsupported_forged_pointer() return (int *)1; } -int unsupported_evaluated_expression(int x) - cppverify::pre(x == 2147483647) - cppverify::post(cppverify::result == 0) -{ - x + 1; - return 0; -} - void unsupported_cross_type_alias(int *value, unsigned char *byte) cppverify::aliases(value, byte) cppverify::modifies(*byte) @@ -72,7 +64,6 @@ int unsupported_recursive_exec(int n) // VERIFY-DAG: error: calls_uncontracted: call to function without a verification contract: opaque_side_effect // VERIFY-DAG: error: reads_global_state: only scalar integral global variables are supported: global_state // VERIFY-DAG: error: unsupported_forged_pointer: unsupported explicit pointer or aggregate cast -// VERIFY-DAG: error: unsupported_evaluated_expression: unsupported expression statement // VERIFY-DAG: error: unsupported_cross_type_alias: aliases between different pointee types are unsupported // VERIFY-DAG: error: unsupported_recursive_exec: recursive executable functions require decreases // VERIFY-DAG: warning: unsupported_template: function template is not verified: this translation unit instantiates none of its specializations From 8714e40a0ee52a497abce5f3ab96794f07de2543 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:24 +0530 Subject: [PATCH 023/119] docs: Add the unreachable obligation kind. --- website/source/language/tooling.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/language/tooling.rst b/website/source/language/tooling.rst index 6f23e0f672..3cc2b75747 100644 --- a/website/source/language/tooling.rst +++ b/website/source/language/tooling.rst @@ -668,6 +668,9 @@ what the obligation checks: ``valid(p, n)`` extent must denote valid storage), ``aliasing`` (implicit non-aliasing), ``frame`` (writes and callee effects stay within ``cppverify::modifies``), and ``missing-return``; +- ``unreachable``: a failed ``assert`` or a call that does not return + (``abort``, ``exit``, ``std::terminate``, ``__builtin_unreachable``, + ``__builtin_trap``) is reached; - ``unwinding`` for a BMC bound, and ``unsupported`` for a construct the verifier rejects fail-closed. From a23a04391c86bea9ba3a0c2b9e8542f5ade97a80 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:25 +0530 Subject: [PATCH 024/119] docs: Describe assert and calls that do not return. --- website/source/language/functions-loops.rst | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index c870ea265d..1a4844ed2c 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -888,6 +888,56 @@ The end-to-end acceptance programs include recursive and iterative factorial and Fibonacci implementations, each proved against a mathematical recursive specification at the exact signed-``int`` boundary. +Assertions and calls that do not return +--------------------------------------- + +``assert(c)`` must not fail: reaching a failed assert, or any call that does +not return (``abort``, ``exit``, ``std::terminate``, ``__builtin_unreachable``, +``__builtin_trap``, a ``[[noreturn]]`` function), is a failure of kind +``unreachable``, as Frama-C proves an ``assert`` and Kani a panic unreachable. +The path ends there, so after ``assert(c)`` the condition holds. With +``NDEBUG``, ``assert(c)`` is nothing, as compiled, and proves nothing. + +.. cppverify-example: fails aborts + +.. code-block:: cpp + + #include + #include + + int scaled(int x) + cv::pre(0 < x && x < 1000) + cv::post(cv::result == 2 * x) + { + assert(x > 0 && "x must be positive"); + return x + x; + } + + int aborts(int x) + cv::post(cv::result >= 0) + { + if (x < 0) + abort(); + return x; + } + +.. code-block:: text + + Verified: scaled [backend=z3] + aborts.cpp:16:5: error: verification failed: aborts [...::unreachable@16:5] (counterexample: x [ssa=x_0] [type=i32] = -1; ...) [backend=z3] [reason=counterexample] + +An uncontracted ``constexpr`` helper's ``assert(c)`` is a requirement of each +call that reaches it, checked at the call as the helper's undefined +operations are. ``[[assume(e)]]`` and ``__builtin_assume(e)`` make ``e`` +false undefined, so ``e`` is proved where it stands, then holds. A +statement that only computes a value, ``x + 1;``, is computed, with its +checks, and the operands of a discarded ``&&``, ``||``, or ``?:`` run on +their paths, which is how ``assert`` expands on every platform. + +Before 0.2, ``assert`` stopped verification of its function (``unsupported +statement: ParenExpr``), and so did a call to ``abort`` (``call to function +without a verification contract``). + Switch statements ----------------- From 4ae321940cd9c56dcafc3db19a957ff47e24130b Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:25 +0530 Subject: [PATCH 025/119] docs: List assert, assumptions, and discarded values as supported. --- website/source/language/limitations.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index 2f9058240b..e4367edaef 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -114,7 +114,10 @@ C++ feature boundaries - ``bool``, integral types, and enums preserve target width and signedness. - Floating point, ``volatile``, and other non-integral scalar semantics. * - Expressions and declarations - - Character literals (``'0'``, ``L'A'``), constants Clang evaluates (a + - ``assert``, calls that do not return (an obligation that they are + not reached), ``[[assume(e)]]`` and ``__builtin_assume(e)`` (proved, + then assumed), discarded values (``x + 1;``, ``c && f(x);``), + character literals (``'0'``, ``L'A'``), constants Clang evaluates (a ``consteval`` call, an ``if constexpr`` condition), template value parameters, default arguments and default member initializers, ``__builtin_expect``, a record returned from an initializer list @@ -198,8 +201,8 @@ C++ feature boundaries ``switch`` (fallthrough, a ``default`` anywhere, GNU case ranges, an init statement), blocks, ``return``, ``break``, and ``continue`` anywhere, and comma expressions as statements. - - Range-``for``, ``goto``, labels, a ``case`` label inside another - statement of its switch (Duff's device), and ``[[assume]]``. An + - Range-``for``, ``goto``, labels, and a ``case`` label inside another + statement of its switch (Duff's device). An expression or statement nested more than 256 operations deep (a sum of 300 terms, say) is reported where it is, and the file stops: ``error: total: the expression at 4:276 nests more than 256 From 7d610bb7a94329227d41e72abea2e3b02dfd4eba Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:26 +0530 Subject: [PATCH 026/119] docs: Note assert and calls that do not return in the 0.2.0 release notes. --- website/source/release-notes.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index 32a0bc8bcb..766121484a 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -413,6 +413,18 @@ that may write what another operand reads is an error at the expression, and an assignment's right operand is evaluated before its target, as C++17 says (:doc:`language/functions-loops`). +**assert and calls that do not return.** ``assert(c)`` stopped its function +(``unsupported statement: ParenExpr``), and a call to ``abort`` or ``exit`` +was an error (``call to function without a verification contract``). +Reaching a failed ``assert`` or a call that does not return is now a failure +of the new kind ``unreachable``, and after ``assert(c)``, ``c`` holds; with +``NDEBUG`` an ``assert`` is nothing. An uncontracted ``constexpr`` helper's +``assert`` is checked at each call. ``[[assume(e)]]`` and +``__builtin_assume(e)`` are proved, then assumed. A statement that only +computes a value is computed, with its checks (``x + 1;`` was ``unsupported +expression statement``). LLVM's ``alignTo``, ``maxUIntN``, and +``SignExtend64`` verify with their asserts (:doc:`language/functions-loops`). + **Small forms of everyday C++.** Each of these stopped its function: character literals (``'0'``, ``L'A'``), a ``consteval`` call and an ``if constexpr`` condition, a template value parameter used as a value, a default From 852219ea9fd34ed529d74216161c0f4f27983671 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:27 +0530 Subject: [PATCH 027/119] docs: Design assert, calls that do not return, and lifted helpers' asserts. --- docs/DESIGN.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a452cefd2a..df75cf99d5 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -438,6 +438,30 @@ cppverify::calc { every step is `==`, `<` or `>` when some step is strict, else `<=` or `>=`. Mixing `<`-like and `>`-like steps is rejected. +### assert() and calls that do not return + +`assert(c)` must not fail (decision Q2 of the 0.2 plan): reaching a call that +does not return (`__assert_fail` of a failed assert, `abort`, `exit`, +`std::terminate`, `__builtin_unreachable`, `__builtin_trap`, any +`[[noreturn]]` function, and MSVC's `_wassert`, which is not marked) is an +obligation of kind `unreachable` at the call, and the path ends there +(`assume false`), so after `assert(c)` the condition holds. Frama-C's +`assert` and Kani's panics are the precedent; treating such calls as +assumptions would hide the bug an assert guards against. With `NDEBUG`, +`assert(c)` expands to `(void)0` and verifies nothing, exactly as compiled. +The converter needs no knowledge of the macro: a discarded `?:`, `&&`, or +`||` is an `if`, an implicit conversion or a cast to `void` of a discarded +value evaluates the value, and a value computed for nothing (`x + 1;`) is +computed with its checks. `[[assume(e)]]` and `__builtin_assume(e)` make `e` +false undefined, so `e` is asserted (kind `assertion`), then assumed. + +An uncontracted `constexpr` helper is lifted as a spec whose definedness is +checked at each call by unfolding its body. Its `assert(c)` becomes, during +that unfolding, a `Require(!path || c)` expression in the evaluation, true +as a value, whose evaluation requires its condition, so each call checks it +with the helper's other operations; the path that does not return gives the +helper any value, since no call returns from it. + ## Ghost Blocks From 79418740c4c4f0775cfeaf6453f2435d71a3b989 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 12:45:27 +0530 Subject: [PATCH 028/119] docs: Describe Require and how a lifted helper's asserts are checked. --- docs/ARCHITECTURE.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 92babf67f2..ae60edc80d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -147,6 +147,7 @@ VExpr = | HeapFrame(before, after, [(lo, hi)]) // passive frame relation | SpecCall(identity, args: [VExpr], type) | OverflowCheck(op, lhs, rhs?) + | Require(cond, kind) // true; evaluating it requires cond | Forall(binder: VarDecl, lo: VExpr?, hi: VExpr?, body: VExpr) | Exists(binder: VarDecl, lo: VExpr?, hi: VExpr?, body: VExpr) | Old(inner: VExpr) @@ -445,6 +446,16 @@ call argument for a machine parameter, or a return value. **HeapFrame:** produced by passivization only: `after` equals `before` at every address outside the half-open regions. +**Require:** produced by the spec inliner only, from the `assert` +statements of a lifted `constexpr` helper's body (`evalBodySeq`): on the +path `g` to `assert(c)` it adds `Require(!g || c, kind)` to the evaluation +trace that each call's definedness check unfolds, so the passivizer's +definedness collection asserts `!g || c` with the statement's kind at the +call. Its value is `true`, so the lifted definition does not change. A +call that does not return is `assert false` (kind `unreachable`) followed by +`assume false` in the converter, and the unfolding gives the helper any +value on that path. + ### Statements (VStmt) ``` From d948b39c4f1b99be8125fce08f41c750fc37a862 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:07:56 +0530 Subject: [PATCH 029/119] docs: Asserts and calls that do not return in the book. --- .../book/part-ii/ch18-undefined-behavior.rst | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/website/source/book/part-ii/ch18-undefined-behavior.rst b/website/source/book/part-ii/ch18-undefined-behavior.rst index b38a637989..b15fe689ab 100644 --- a/website/source/book/part-ii/ch18-undefined-behavior.rst +++ b/website/source/book/part-ii/ch18-undefined-behavior.rst @@ -298,6 +298,39 @@ checked the same way in the state the function returns in, and a trusted contract in every state it allows. Callers do not rely on a contract that may be undefined; see :doc:`../../language/functions-loops`. +Asserts and calls that do not return +------------------------------------ + +An ``assert`` is not undefined behavior when it fails: the program aborts. +But a verified function should never abort, so a failed ``assert``, and any +call that does not return (``abort``, ``exit``, ``std::terminate``, +``__builtin_unreachable``), is an obligation of its own, of kind +``unreachable``: no execution may reach it. After ``assert(c)``, ``c`` +therefore holds, and an assert documents a fact the proof checks. With +``NDEBUG`` an ``assert`` is nothing, as compiled: + +.. cppverify-example: fails ratio + +.. code-block:: cpp + + #include + + int ratio(int total, int parts) + cv::pre(0 <= total && total <= 1000) + cv::post(cv::result >= 0) + { + assert(parts > 0 && "parts must be positive"); + return total / parts; + } + +.. code-block:: text + + ratio.cpp:7:3: error: verification failed: ratio [...::unreachable@7:3] (counterexample: parts [ssa=parts_0] [type=i32] = 0, ...) [backend=z3] [reason=counterexample] + +The division needs ``parts != 0``, which the assert establishes, and the +assert can fail: a caller may pass ``parts == 0``. Adding +``cv::pre(parts > 0)`` makes both hold. + What is not covered yet ----------------------- From 12c3c9ecc9aee3046c37678c393d593a93d83f8a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:13:40 +0530 Subject: [PATCH 030/119] verify: Read constant tables and string literals by index, as specs of the index. --- clang/lib/Verify/Frontend/ASTConverter.cpp | 197 +++++++++++++++++++++ clang/lib/Verify/Frontend/ASTConverter.h | 8 + 2 files changed, 205 insertions(+) diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 4489022237..190668966c 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -4525,6 +4525,195 @@ bool ASTConverter::subobjectPointerRoot(const Expr *E) const { return VD && !LocalReferenceProvenanceVariables.count(VD); } +bool ASTConverter::isConstantTable(const VarDecl *VD) const { + QualType Element = VD->getType(); + bool IsArray = false; + while (const ConstantArrayType *Bounded = + Ctx.getAsConstantArrayType(Element)) { + IsArray = true; + Element = Bounded->getElementType(); + } + const VarDecl *Definition = VD->getDefinition() ? VD->getDefinition() : VD; + const APValue *Value = IsArray ? Definition->evaluateValue() : nullptr; + return Value && Value->isArray() && VD->hasGlobalStorage() && + VD->getTLSKind() == VarDecl::TLS_None && + (Element->isIntegralOrEnumerationType() || Element->isBooleanType()) && + Element.isConstQualified() && !Element.isVolatileQualified(); +} + +std::optional> +ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { + // t[i][j]: the indices, outermost first, of a whole read of an array. + std::vector Indices; + const Expr *Array = AS; + while (const auto *Subscript = dyn_cast(Array)) { + const auto *Decay = dyn_cast(Subscript->getBase()); + if (!Decay || Decay->getCastKind() != CK_ArrayToPointerDecay) + return std::nullopt; + Indices.insert(Indices.begin(), Subscript->getIdx()); + Array = Decay->getSubExpr()->IgnoreParens(); + } + const auto *Ref = dyn_cast(Array); + const auto *VD = Ref ? dyn_cast(Ref->getDecl()) : nullptr; + const auto *Literal = dyn_cast(Array); + if (!VD && !Literal) + return std::nullopt; + std::vector Dims; + QualType Element = Array->getType(); + while (const ConstantArrayType *Bounded = + Ctx.getAsConstantArrayType(Element)) { + Dims.push_back(Bounded->getZExtSize()); + Element = Bounded->getElementType(); + } + if (Dims.size() != Indices.size() || Dims.empty() || + !(Element->isIntegralOrEnumerationType() || Element->isBooleanType()) || + Element.isVolatileQualified()) + return std::nullopt; + + // Its values: a constant object's initializer, or a literal's code units + // and the terminating zero. + const VType ElementTy = VType::fromQualType(Element, VIntMode::Machine, Ctx); + std::vector Values; + std::string Identity; + if (VD) { + const VarDecl *Definition = VD->getDefinition() ? VD->getDefinition() : VD; + if (!VD->hasGlobalStorage() || VD->getTLSKind() != VarDecl::TLS_None || + !Element.isConstQualified()) + return std::nullopt; + const APValue *Value = Definition->evaluateValue(); + if (!Value) + return std::nullopt; + std::function Flatten = [&](const APValue &V) { + if (V.isInt()) { + Values.push_back(V.getInt()); + return true; + } + if (!V.isArray()) + return false; + for (unsigned K = 0; K < V.getArraySize(); ++K) + if (!Flatten(K < V.getArrayInitializedElts() + ? V.getArrayInitializedElt(K) + : V.getArrayFiller())) + return false; + return true; + }; + if (!Flatten(*Value)) + return std::nullopt; + const PresumedLoc Where = + Ctx.getSourceManager().getPresumedLoc(VD->getLocation()); + Identity = "__cppverify_table." + VD->getQualifiedNameAsString(); + if (Where.isValid()) + Identity += "@" + std::to_string(Where.getLine()) + ":" + + std::to_string(Where.getColumn()); + } else { + const unsigned Width = ElementTy.BitWidth; + for (uint64_t K = 0; K < Dims.front(); ++K) + Values.push_back(llvm::APSInt( + llvm::APInt(Width, + K < Literal->getLength() ? Literal->getCodeUnit(K) : 0), + !ElementTy.IsSigned)); + const PresumedLoc Where = + Ctx.getSourceManager().getPresumedLoc(Literal->getBeginLoc()); + Identity = "__cppverify_string"; + if (Where.isValid()) + Identity += "@" + std::to_string(Where.getLine()) + ":" + + std::to_string(Where.getColumn()); + } + uint64_t Count = 1; + for (uint64_t D : Dims) + Count *= D; + if (Values.size() != Count) + return std::nullopt; + + const SourceLocation Loc = AS->getExprLoc(); + const VType Index = VType::makeInt(VIntMode::Math, 64, true); + auto Constant = [&](const llvm::APSInt &V) -> std::unique_ptr { + if (ElementTy.Kind == VTypeKind::Bool) + return std::make_unique(!V.isZero(), ElementTy, Loc); + return std::make_unique(integerValueString(V), ElementTy, + Loc); + }; + // The table as a spec of the linear index: a balanced tree of + // comparisons, so its depth grows with the logarithm of its size. + if (llvm::none_of(BuiltinFunctions, + [&](const auto &Fn) { return Fn->Identity == Identity; })) { + std::function(uint64_t, uint64_t)> Tree = + [&](uint64_t Lo, uint64_t Hi) -> std::unique_ptr { + if (std::all_of(Values.begin() + Lo + 1, Values.begin() + Hi, + [&](const llvm::APSInt &V) { + return llvm::APSInt::isSameValue(V, Values[Lo]); + })) + return Constant(Values[Lo]); + const uint64_t Mid = Lo + (Hi - Lo) / 2; + return std::make_unique( + std::make_unique( + VBinOp::Lt, std::make_unique("i", Index, Loc), + std::make_unique(std::to_string(Mid), Index, Loc), + VType::makeBool(), Loc), + Tree(Lo, Mid), Tree(Mid, Hi), ElementTy, Loc); + }; + auto Fn = std::make_unique(); + Fn->Name = VD ? VD->getNameAsString() : "string literal"; + Fn->Identity = Identity; + Fn->IsSpec = true; + Fn->IsBuiltin = true; + Fn->IntMode = VIntMode::Math; + Fn->ReturnType = ElementTy; + Fn->Params = {{"i", Index}}; + Fn->Body.push_back( + std::make_unique(Tree(0, Values.size()), Loc)); + BuiltinFunctions.push_back(std::move(Fn)); + } + + // The read: its linear index, and each index inside its array. + std::unique_ptr Linear; + std::unique_ptr Inside; + for (size_t D = 0; D < Indices.size(); ++D) { + auto I = convertExpr(Indices[D]); + if (!I) + return std::unique_ptr(); + if (I->Ty.IntMode != VIntMode::Math) { + VType Machine = I->Ty; + I = std::make_unique(std::move(I), Machine, Index, Loc); + } + auto Bounded = std::make_unique( + VBinOp::And, + std::make_unique( + VBinOp::Le, std::make_unique(0, Index, Loc), + cloneVExpr(I.get()), VType::makeBool(), Loc), + std::make_unique( + VBinOp::Lt, cloneVExpr(I.get()), + std::make_unique(std::to_string(Dims[D]), Index, Loc), + VType::makeBool(), Loc), + VType::makeBool(), Loc); + Inside = Inside + ? std::make_unique(VBinOp::And, std::move(Inside), + std::move(Bounded), + VType::makeBool(), Loc) + : std::unique_ptr(std::move(Bounded)); + Linear = Linear ? std::make_unique( + VBinOp::Add, + std::make_unique( + VBinOp::Mul, std::move(Linear), + std::make_unique( + std::to_string(Dims[D]), Index, Loc), + Index, Loc), + std::move(I), Index, Loc) + : std::move(I); + } + std::vector> Args; + Args.push_back(std::move(Linear)); + auto Read = std::make_unique( + VD ? VD->getNameAsString() : "string literal", Identity, std::move(Args), + ElementTy, Loc); + auto Copy = cloneVExpr(Read.get()); + return std::unique_ptr(std::make_unique( + std::make_unique( + std::move(Inside), static_cast(ProofObligationKind::Bounds), + Loc), + std::move(Read), std::move(Copy), ElementTy, Loc)); +} + std::string ASTConverter::arrayDecayError(const Expr *Array) const { const auto *M = dyn_cast_or_null(peelObjectExpr(Array)); const std::string Member = @@ -4533,6 +4722,9 @@ std::string ASTConverter::arrayDecayError(const Expr *Array) const { Root && Root->hasGlobalStorage()) { const std::string Kind = Root->isStaticLocal() ? "static local" : "global"; const std::string Name = "'" + Root->getNameAsString() + "'"; + if (!M && Ctx.getBaseElementType(Root->getType()).isConstQualified()) + return "a pointer into the constant table " + Name + + " is unsupported; read its elements by index"; return M ? "the array member '" + Member + "' of the " + Kind + " " + Name + " is unsupported" : "the " + Kind + " array " + Name + " is unsupported"; @@ -6042,6 +6234,8 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { return nullptr; } if (const auto *AS = dyn_cast(E)) { + if (std::optional> Element = convertTableRead(AS)) + return std::move(*Element); auto Place = subscriptPlace(AS); if (!Place) return nullptr; @@ -9145,6 +9339,9 @@ ASTConverter::convertStmtBody(const Stmt *S) { continue; } if (!VD->hasLocalStorage()) { + // A constant one is a value, read where it is named. + if (globalConstant(VD) || isConstantTable(VD)) + continue; error(CurrentFn->Name + ": static local variables are unsupported"); continue; } diff --git a/clang/lib/Verify/Frontend/ASTConverter.h b/clang/lib/Verify/Frontend/ASTConverter.h index c8a0cdfb41..20ec6f8b44 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.h +++ b/clang/lib/Verify/Frontend/ASTConverter.h @@ -317,6 +317,14 @@ class ASTConverter { SourceLocation Loc); std::unique_ptr convertArrowFieldAddress(const MemberExpr *M); std::unique_ptr convertSubscriptAddress(const ArraySubscriptExpr *AS); + /// An element of a constant table (a constant array with a constant + /// initializer, or a string literal): a spec of its index, which must lie + /// in its array. std::nullopt when \p AS reads no such table. + std::optional> + convertTableRead(const ArraySubscriptExpr *AS); + /// A constant array with a constant initializer, of integers, + /// enumerations, or bools: a table, which declares no object to run. + bool isConstantTable(const VarDecl *VD) const; std::vector> convertStmt(const Stmt *S); std::vector> convertStmtBody(const Stmt *S); std::vector> convertScopedSubstatement(const Stmt *S); From 64b730512c0912d7e7c920977603e06c2f1e5368 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:13:41 +0530 Subject: [PATCH 031/119] test: Constant tables of one and two dimensions, string literals, and their bounds. --- clang/test/Verify/suite/lookup_tables.cpp | 157 ++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 clang/test/Verify/suite/lookup_tables.cpp diff --git a/clang/test/Verify/suite/lookup_tables.cpp b/clang/test/Verify/suite/lookup_tables.cpp new file mode 100644 index 0000000000..bee395430b --- /dev/null +++ b/clang/test/Verify/suite/lookup_tables.cpp @@ -0,0 +1,157 @@ +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 > %t 2>&1 +// RUN: FileCheck %s < %t +// RUN: FileCheck %s --check-prefix=SOUND < %t +// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=2 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=BMC +// RUN: not %cpp-verify --lower-only --dump-ir=1 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=VCR +// +// An element of a constant table, a const or constexpr array with a +// constant initializer (global, static local, or static member) or a string +// literal, is the value its initializer gives it; each index must lie in its +// array. Every correct function has a wrong twin that must be refuted. +// +// No proof here holds only because no execution reaches its end. +// SOUND-NOT: vacuous + +#include + +namespace cv = cppverify; + +// The number of bits set in each nibble. +static const uint8_t NibbleBits[16] = {0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4}; + +int popcount8(uint8_t x) + cv::post(0 <= cv::result && cv::result <= 8) + cv::post(x != 255 || cv::result == 8) + cv::post(x != 0 || cv::result == 0) +{ + return NibbleBits[x & 15] + NibbleBits[x >> 4]; +} +// CHECK-DAG: Verified: popcount8 [backend=z3] + +int popcount8_wrong(uint8_t x) + cv::post(cv::result <= 7) +{ + return NibbleBits[x & 15] + NibbleBits[x >> 4]; +} +// CHECK-DAG: error: verification failed: popcount8_wrong [{{.*}}x [ssa=x_0] [type=u8] = 255{{.*}}[reason=counterexample] + +// An index outside the table is undefined. +int nibble_bits(int x) + cv::post(true) +{ + return NibbleBits[x]; +} +// CHECK-DAG: error: verification failed: nibble_bits [{{.*}}::bounds@[[@LINE-2]]:{{.*}}[reason=counterexample] + +int nibble_bits_inside(int x) + cv::pre(0 <= x && x < 16) + cv::post(0 <= cv::result && cv::result <= 4) +{ + return NibbleBits[x]; +} +// CHECK-DAG: Verified: nibble_bits_inside [backend=z3] + +// A table declared in a function, two-dimensional, with elements its +// initializer leaves zero. +int add_mod3(int a, int b) + cv::pre(0 <= a && a < 3 && 0 <= b && b < 3) + cv::post(cv::result == (a + b) % 3) +{ + static constexpr int Sum[3][3] = {{0, 1, 2}, {1, 2, 0}, {2, 0, 1}}; + return Sum[a][b]; +} +// CHECK-DAG: Verified: add_mod3 [backend=z3] + +int add_mod3_wrong(int a, int b) + cv::pre(0 <= a && a < 3 && 0 <= b && b < 3) + cv::post(cv::result == (a + b) % 3) +{ + static constexpr int Sum[3][3] = {{0, 1, 2}, {1, 2, 0}, {2, 1, 0}}; + return Sum[a][b]; +} +// CHECK-DAG: error: verification failed: add_mod3_wrong [{{.*}}a [ssa=a_0] [type=i32] = 2, b [ssa=b_0] [type=i32] = {{1|2}}{{.*}}[reason=counterexample] + +int row_outside(int a) + cv::pre(0 <= a && a < 3) + cv::post(true) +{ + static constexpr int Sum[3][3] = {{0, 1, 2}, {1, 2, 0}, {2, 0, 1}}; + return Sum[a][3]; +} +// CHECK-DAG: error: verification failed: row_outside [{{.*}}::bounds@[[@LINE-2]]:{{.*}}[reason=counterexample] + +constexpr int Primes[8] = {2, 3, 5, 7, 11}; + +int prime_or_zero(int i) + cv::pre(0 <= i && i < 8) + cv::post(i < 5 ? cv::result > 1 : cv::result == 0) +{ + return Primes[i]; +} +// CHECK-DAG: Verified: prime_or_zero [backend=z3] + +// A string literal is a constant array of its characters and a zero. +char hex_digit(int d) + cv::pre(0 <= d && d < 16) + cv::post(d < 10 ? cv::result == '0' + d : cv::result == 'a' + d - 10) +{ + return "0123456789abcdef"[d]; +} +// CHECK-DAG: Verified: hex_digit [backend=z3] + +char hex_digit_wrong(int d) + cv::pre(0 <= d && d <= 16) + cv::post(true) +{ + return "0123456789abcdef"[d + 1]; +} +// CHECK-DAG: error: verification failed: hex_digit_wrong [{{.*}}::bounds@[[@LINE-2]]:{{.*}}d [ssa=d_0] [type=i32] = 16{{.*}}[reason=counterexample] + +// Tables of enumerations and Booleans, and a static member. +enum Kind { Digit, Letter, Other }; + +struct Ascii { + static constexpr bool IsUpper[4] = {false, true, true, false}; +}; + +Kind classify(int c) + cv::pre(0 <= c && c < 4) + cv::post(cv::result != Other) +{ + static const Kind Classes[4] = {Digit, Letter, Letter, Digit}; + return Ascii::IsUpper[c] ? Letter : Classes[c]; +} +// CHECK-DAG: Verified: classify [backend=z3] + +// Contracts and ghost code read tables too. +int first_prime() + cv::post(cv::result == Primes[0]) +{ + return 2; +} +// CHECK-DAG: Verified: first_prime [backend=z3] + +// A pointer into a constant table is not supported yet. +int via_pointer() + cv::post(true) +{ + const int *p = Primes; + return p[1]; +} +// CHECK-DAG: lookup_tables.cpp:[[@LINE-3]]:18: error: via_pointer: a pointer into the constant table 'Primes' is unsupported; read its elements by index + +// BMC: Verified: popcount8 [backend=bmc, bound=0] +// BMC: error: verification failed: popcount8_wrong + +// The read requires its index in bounds; the table is a tree of comparisons. +// VCR-LABEL: fn nibble_bits_inside +// VCR: return +// VCR-NEXT: cast +// VCR-NEXT: ite +// VCR-NEXT: require bounds +// VCR: ite +// VCR-NEXT: < +// VCR-NEXT: cast +// VCR-NEXT: x +// VCR-NEXT: 8 From b67f6d100f44f1666bb26c66be43e8400bcbe874 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:38 +0530 Subject: [PATCH 032/119] docs: Constant tables, read by index. --- website/source/language/pointers.rst | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/website/source/language/pointers.rst b/website/source/language/pointers.rst index 59f71aa822..77d3685988 100644 --- a/website/source/language/pointers.rst +++ b/website/source/language/pointers.rst @@ -536,6 +536,56 @@ statements of their own. Before 0.2, each of these steps was an error (``pointer increment/decrement is unsupported``, ``pointer compound assignment is unsupported``), and a walk had to write ``q = q + 1``. +Constant tables +~~~~~~~~~~~~~~~ + +An element of a constant table is the value its initializer gives it: a +``const`` or ``constexpr`` array with a constant initializer (global, a +``static`` local, or a ``static`` member, of any number of dimensions, with +integer, enumeration, or ``bool`` elements, and zeros where the initializer +stops), or a string literal, whose elements are its characters and a zero. +Each index must lie in its array, an obligation of kind ``bounds``: + +.. cppverify-example: fails outside + +.. code-block:: cpp + + static const uint8_t NibbleBits[16] = {0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4}; + + int popcount8(uint8_t x) + cv::post(0 <= cv::result && cv::result <= 8) + cv::post(x != 255 || cv::result == 8) + { + return NibbleBits[x & 15] + NibbleBits[x >> 4]; + } + + char hex_digit(int d) + cv::pre(0 <= d && d < 16) + cv::post(d < 10 ? cv::result == '0' + d : cv::result == 'a' + d - 10) + { + return "0123456789abcdef"[d]; + } + + int outside(int x) + cv::post(true) + { + return NibbleBits[x]; + } + +.. code-block:: text + + Verified: popcount8 [backend=z3] + Verified: hex_digit [backend=z3] + tables.cpp:21:3: error: verification failed: outside [...::bounds@21:3] (counterexample: x [ssa=x_0] [type=i32] = ...) [backend=z3] [reason=counterexample] + +The verifier gives each table to the solver as a function of the index, +defined by a balanced tree of comparisons, so a 256-entry table is eight +comparisons deep. A pointer into a constant table (``const int *p = +Primes;``) is not supported yet; read the elements by index. Before 0.2, any +read of a global or ``static`` array, or of a string literal, was an error +(``the global array 'NibbleBits' is unsupported``). + Two-buffer copy loops (``memcpy``-style) need the source and destination ranges to be disjoint; otherwise storing to the destination could clobber a source cell still to be read, exactly the C ``memcpy`` vs ``memmove`` From e210577b2d1dd9ae8807b5af67817b93765c6ecf Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:38 +0530 Subject: [PATCH 033/119] docs: List constant tables in the storage row. --- website/source/language/limitations.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index e4367edaef..96ef7e7129 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -125,7 +125,8 @@ C++ feature boundaries declarations that do nothing (``static_assert``, type aliases, using-declarations, local classes and enumerations). - The value of an assignment, an increment, or a comma expression - inside an expression (``a = b = 0``, ``*p++``); string literals; + inside an expression (``a = b = 0``, ``*p++``); a pointer into a + string literal (its characters are read by index); other builtins; structured bindings of an array, of a tuple-like type, or by reference. * - Pointers @@ -192,9 +193,11 @@ C++ feature boundaries * - Storage - Supported locals, constrained scalar dynamic storage, and inferred acyclic fresh-owned scalar factory results. - - Non-integral or aggregate globals, static locals, thread-local storage, - general returned allocations, and user-declared modular allocation - effects. + - Mutable aggregate globals, mutable static locals, pointers into + constant tables, thread-local storage, general returned allocations, + and user-declared modular allocation effects. Constant tables + (``const`` and ``constexpr`` arrays with constant initializers, and + string literals) are read by index (:doc:`pointers`). * - Structured control flow - ``if`` (including C++17 initializer), ``while``, ``for`` (with or without a condition), contracted ``do`` loops, condition declarations, From 2584b9fe1c72e00416776e9813064201bf916c38 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:39 +0530 Subject: [PATCH 034/119] docs: Note lookup tables in the 0.2.0 release notes. --- website/source/release-notes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index 766121484a..33aec573e2 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -413,6 +413,13 @@ that may write what another operand reads is an error at the expression, and an assignment's right operand is evaluated before its target, as C++17 says (:doc:`language/functions-loops`). +**Lookup tables.** A read of a global or ``static`` array, or of a string +literal, was an error (``the global array 'table' is unsupported``), which +ruled out the tables at the heart of CRC, base64, and UTF-8 code. An element +of a constant table, a ``const`` or ``constexpr`` array with a constant +initializer or a string literal, is now the value its initializer gives it, +and each index must lie in its array (:doc:`language/pointers`). + **assert and calls that do not return.** ``assert(c)`` stopped its function (``unsupported statement: ParenExpr``), and a call to ``abort`` or ``exit`` was an error (``call to function without a verification contract``). From 2b55087753b89e65da4ff10564bee98a21d4443a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:39 +0530 Subject: [PATCH 035/119] docs: Design constant tables as values. --- docs/DESIGN.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index df75cf99d5..dea136c8de 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1160,6 +1160,19 @@ invariant, a `cppverify::check`, ghost code, a spec function) the marker is its builtin spec, whose body is `true`, so a claim made with it would hold without meaning anything: under `--check-ub` it is an error for that function. +### 1a. Constant tables + +A `const` or `constexpr` array with a constant initializer (global, `static` +local, or `static` member, of integer, enumeration, or `bool` elements) and +a string literal are values, not memory: nothing may write them, so an +element read `t[i][j]` is the application of a spec of the linear index, +synthesized from Clang's evaluation of the initializer (`APValue`), and each +index must lie in its array (kind `bounds`). The spec's body is a balanced +tree of comparisons of the index, whose depth is the logarithm of the size +and whose leaves merge runs of equal values. A pointer into a table would +need the table as an object at an address; it fails closed with a located +message. Mutable static storage stays B6's. + ### 2. Implicit non-aliasing default For every pair of distinct address parameters of which at least one is From 0baf3b2be0f1f2ffc8a905248f23708882909394 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:40 +0530 Subject: [PATCH 036/119] docs: Describe how a table read is lowered. --- docs/ARCHITECTURE.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ae60edc80d..11925a64e6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -446,7 +446,14 @@ call argument for a machine parameter, or a return value. **HeapFrame:** produced by passivization only: `after` equals `before` at every address outside the half-open regions. -**Require:** produced by the spec inliner only, from the `assert` +**Constant tables:** `ASTConverter::convertTableRead` lowers a whole read +of a constant table (a constant array's initializer or a string literal) to +`ite(Require(index in bounds, bounds), table(i), table(i))`, where `table` is +a synthesized spec (`IsBuiltin`, identity `__cppverify_table.@L:C` or +`__cppverify_string@L:C`) whose body is a balanced comparison tree over the +linear index. + +**Require:** produced by the spec inliner from the `assert` statements of a lifted `constexpr` helper's body (`evalBodySeq`): on the path `g` to `assert(c)` it adds `Require(!g || c, kind)` to the evaluation trace that each call's definedness check unfolds, so the passivizer's From 649f7d2397ba15f078f6b6e9769b36add3a5d86d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:14:40 +0530 Subject: [PATCH 037/119] docs: A constant table and a string literal in the book. --- .../part-ii/ch14-pointers-frames-modifies.rst | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/website/source/book/part-ii/ch14-pointers-frames-modifies.rst b/website/source/book/part-ii/ch14-pointers-frames-modifies.rst index e16bfc7d50..4acdea77d3 100644 --- a/website/source/book/part-ii/ch14-pointers-frames-modifies.rst +++ b/website/source/book/part-ii/ch14-pointers-frames-modifies.rst @@ -242,6 +242,27 @@ included; the invariant ties the pointer to a position: The value of a step inside an expression, as in ``*q++``, is not supported yet: read ``*q``, then step ``q`` in a statement of its own. +A ``const`` or ``constexpr`` array with a constant initializer, such as the +lookup tables of CRC or base64 code, is a value, not memory: reading +``table[i]`` gives the value its initializer sets, and ``i`` must lie in the +array. A string literal is such a table of its characters: + +.. code-block:: cpp + + static const unsigned char HexValue[16] = {0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15}; + + int digit_at(int d) + cv::pre(0 <= d && d < 16) + cv::post(0 <= cv::result && cv::result < 16) + { + return HexValue["0123456789abcdef"[d] % 16 == 0 ? 0 : d]; + } + +.. code-block:: text + + Verified: digit_at [backend=z3] + Records through pointers ------------------------ From 83fc7a3947c0f0edd41b91c52f8cbf5edcc0050f Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:45:03 +0530 Subject: [PATCH 038/119] test: Cover non-square tables, a literal's last element, Boolean tables, indices below zero, and mutable arrays. --- clang/test/Verify/suite/lookup_tables.cpp | 102 +++++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/clang/test/Verify/suite/lookup_tables.cpp b/clang/test/Verify/suite/lookup_tables.cpp index bee395430b..07954ebb14 100644 --- a/clang/test/Verify/suite/lookup_tables.cpp +++ b/clang/test/Verify/suite/lookup_tables.cpp @@ -1,8 +1,8 @@ -// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 > %t 2>&1 +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 -fsigned-char -Wno-array-bounds > %t 2>&1 // RUN: FileCheck %s < %t // RUN: FileCheck %s --check-prefix=SOUND < %t -// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=2 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=BMC -// RUN: not %cpp-verify --lower-only --dump-ir=1 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=VCR +// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=2 %s -- -std=c++20 -fsigned-char -Wno-array-bounds 2>&1 | FileCheck %s --check-prefix=BMC +// RUN: not %cpp-verify --lower-only --dump-ir=1 %s -- -std=c++20 -fsigned-char -Wno-array-bounds 2>&1 | FileCheck %s --check-prefix=VCR // // An element of a constant table, a const or constexpr array with a // constant initializer (global, static local, or static member) or a string @@ -52,6 +52,14 @@ int nibble_bits_inside(int x) } // CHECK-DAG: Verified: nibble_bits_inside [backend=z3] +int nibble_bits_before(int x) + cv::pre(0 <= x && x < 16) + cv::post(true) +{ + return NibbleBits[x - 1]; +} +// CHECK-DAG: error: verification failed: nibble_bits_before [{{.*}}::bounds@[[@LINE-2]]:{{.*}}x [ssa=x_0] [type=i32] = 0{{.*}}[reason=counterexample] + // A table declared in a function, two-dimensional, with elements its // initializer leaves zero. int add_mod3(int a, int b) @@ -81,6 +89,25 @@ int row_outside(int a) } // CHECK-DAG: error: verification failed: row_outside [{{.*}}::bounds@[[@LINE-2]]:{{.*}}[reason=counterexample] +// Rows of four: the element [r][c] is at 4 * r + c. +static constexpr uint8_t Rows[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}}; + +int row_major(int r, int c) + cv::pre(0 <= r && r < 2 && 0 <= c && c < 4) + cv::post(cv::result == 4 * r + c + 1) +{ + return Rows[r][c]; +} +// CHECK-DAG: Verified: row_major [backend=z3] + +int row_major_wrong(int r, int c) + cv::pre(0 <= r && r < 2 && 0 <= c && c < 4) + cv::post(cv::result == 2 * r + c + 1) +{ + return Rows[r][c]; +} +// CHECK-DAG: error: verification failed: row_major_wrong [{{.*}}r [ssa=r_0] [type=i32] = 1{{.*}}[reason=counterexample] + constexpr int Primes[8] = {2, 3, 5, 7, 11}; int prime_or_zero(int i) @@ -108,6 +135,36 @@ char hex_digit_wrong(int d) } // CHECK-DAG: error: verification failed: hex_digit_wrong [{{.*}}::bounds@[[@LINE-2]]:{{.*}}d [ssa=d_0] [type=i32] = 16{{.*}}[reason=counterexample] +// The last element of a literal is its terminating zero, and each element is +// a value of its character type: '\xff' is -1 as a signed char. +int terminator() + cv::post(cv::result == 0) +{ + return "abc"[3]; +} +// CHECK-DAG: Verified: terminator [backend=z3] + +int terminator_wrong() + cv::post(cv::result == 0) +{ + return "abc"[2]; +} +// CHECK-DAG: error: verification failed: terminator_wrong [{{.*}}[reason=counterexample] + +int high_byte() + cv::post(cv::result == -1) +{ + return "\xff"[0]; +} +// CHECK-DAG: Verified: high_byte [backend=z3] + +int high_byte_wrong() + cv::post(cv::result == 255) +{ + return "\xff"[0]; +} +// CHECK-DAG: error: verification failed: high_byte_wrong [{{.*}}[reason=counterexample] + // Tables of enumerations and Booleans, and a static member. enum Kind { Digit, Letter, Other }; @@ -124,6 +181,22 @@ Kind classify(int c) } // CHECK-DAG: Verified: classify [backend=z3] +bool upper_at(int c) + cv::pre(0 <= c && c < 4) + cv::post(cv::result == (c == 1 || c == 2)) +{ + return Ascii::IsUpper[c]; +} +// CHECK-DAG: Verified: upper_at [backend=z3] + +bool upper_at_wrong(int c) + cv::pre(0 <= c && c < 4) + cv::post(cv::result == (c == 1)) +{ + return Ascii::IsUpper[c]; +} +// CHECK-DAG: error: verification failed: upper_at_wrong [{{.*}}c [ssa=c_0] [type=i32] = 2{{.*}}[reason=counterexample] + // Contracts and ghost code read tables too. int first_prime() cv::post(cv::result == Primes[0]) @@ -132,6 +205,29 @@ int first_prime() } // CHECK-DAG: Verified: first_prime [backend=z3] +// A global array that is not constant is an object, not a table: its +// elements may change. +int Counters[4] = {0, 0, 0, 0}; + +int counter(int i) + cv::pre(0 <= i && i < 4) + cv::post(cv::result == 0) +{ + return Counters[i]; +} +// CHECK-DAG: lookup_tables.cpp:[[@LINE-2]]:10: error: counter: the global array 'Counters' is unsupported + +// A static local that is not constant is an object, not a table, and is +// not supported yet. +int seen(int i) + cv::pre(0 <= i && i < 4) + cv::post(true) +{ + static int Seen[4] = {0, 0, 0, 0}; + return Seen[i]; +} +// CHECK-DAG: error: seen: static local variables are unsupported + // A pointer into a constant table is not supported yet. int via_pointer() cv::post(true) From 73d02f0a6fcbd5a225c8eab9149d4b7da203bd8a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:15 +0530 Subject: [PATCH 039/119] verify: List cppverify::index, an expression in a range-based for loop's contract. --- clang/include/clang/Basic/CppVerifyConstructs.def | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/include/clang/Basic/CppVerifyConstructs.def b/clang/include/clang/Basic/CppVerifyConstructs.def index 5b93993e47..c4648057b9 100644 --- a/clang/include/clang/Basic/CppVerifyConstructs.def +++ b/clang/include/clang/Basic/CppVerifyConstructs.def @@ -53,6 +53,7 @@ CPPVERIFY_CONSTRUCT(Exists, "exists", Expression) CPPVERIFY_CONSTRUCT(Choose, "choose", Expression) CPPVERIFY_CONSTRUCT(Old, "old", Expression) CPPVERIFY_CONSTRUCT(Result, "result", Expression) +CPPVERIFY_CONSTRUCT(Index, "index", Expression) CPPVERIFY_CONSTRUCT(Trigger, "trigger", Expression) #undef CPPVERIFY_CONSTRUCT From 3224fb7c66785b8de3670a5d40bc79bd74da0951 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:16 +0530 Subject: [PATCH 040/119] verify: Declare and document cppverify::index. --- clang/lib/Headers/cppverify.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/lib/Headers/cppverify.h b/clang/lib/Headers/cppverify.h index 8d8d12bb2f..3199d74f8a 100644 --- a/clang/lib/Headers/cppverify.h +++ b/clang/lib/Headers/cppverify.h @@ -128,6 +128,10 @@ enum __construct { /// The return value, in postconditions. result __attribute__((unavailable("cppverify::result is the return value " "in a postcondition"))), + /// In the contract of a range-based for loop: the number of elements + /// before the current one, from 0 to the size of the range. + index __attribute__((unavailable("cppverify::index is the position of a " + "range-based for loop in its contract"))), /// After a spec function returning bool: the least predicate its body /// defines, true exactly where a finite derivation shows it. inductive __attribute__((unavailable("cppverify::inductive follows a spec " From d8ebf76d5645b5ef99aa9c2b7a749f6b5bf0d8bb Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:16 +0530 Subject: [PATCH 041/119] verify: Diagnose cppverify::index outside a range-based for loop's contract. --- clang/include/clang/Basic/DiagnosticParseKinds.td | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 0163ac4fca..1600a81d21 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1961,6 +1961,9 @@ def err_contract_redeclaration : Error< "contract clauses may only be specified on one declaration of a function">; def err_result_outside_postcondition : Error< "'cppverify::result' can only be used in postconditions">; +def err_cppverify_index_outside_range_for : Error< + "'cppverify::index' is the position of a range-based for loop, in its " + "contract">; def err_result_in_old_expression : Error< "'cppverify::result' has no value in the function pre-state">; def err_old_outside_postcondition : Error< From 3ed13ee927967807d15b32b5f5b93bfda140d2af Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:17 +0530 Subject: [PATCH 042/119] verify: Record a range-based for loop's position with its contract. --- clang/include/clang/AST/ASTContext.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 9988d988da..72f0a6d9c1 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -155,6 +155,10 @@ struct LoopContractInfo { SourceLocation MayDiverge; /// The heap the loop may write, read in each iteration's state. SmallVector Modifies; + /// A range-based for loop's position, cppverify::index, which its clauses + /// may name: an implicit variable, the number of elements before the + /// current one. + VarDecl *Index = nullptr; /// The clauses' expressions, as written. SmallVector children() const { From 7b3ae666bffad3f285737848f0ce73750122cca7 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:18 +0530 Subject: [PATCH 043/119] verify: Parse cppverify::index as the position of the range-based for loop whose contract is parsed. --- clang/include/clang/Parse/Parser.h | 3 +++ clang/lib/Parse/ParseCppVerify.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 7fd7812200..c1326afd76 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7668,6 +7668,9 @@ class Parser : public CodeCompletionHandler { /// How many quantifier bodies enclose the current expression, where /// trigger(term) marks a pattern. unsigned QuantifierBodyDepth = 0; + /// The cppverify::index of the range-based for loop whose contract is + /// being parsed. + VarDecl *CppVerifyRangeIndex = nullptr; /// True while parsing the operand of old(...). bool InOldExpression = false; diff --git a/clang/lib/Parse/ParseCppVerify.cpp b/clang/lib/Parse/ParseCppVerify.cpp index 39f8952d0c..c50ce935dd 100644 --- a/clang/lib/Parse/ParseCppVerify.cpp +++ b/clang/lib/Parse/ParseCppVerify.cpp @@ -1245,6 +1245,15 @@ ExprResult Parser::ParseCppVerifyExpression() { return ParseOldExpr(); case CppVerifyConstruct::Result: return ParseResultExpr(); + case CppVerifyConstruct::Index: { + const SourceLocation Loc = ConsumeCppVerify(); + if (!CppVerifyRangeIndex) { + Diag(Loc, diag::err_cppverify_index_outside_range_for); + return ExprError(); + } + return Actions.BuildDeclRefExpr( + CppVerifyRangeIndex, CppVerifyRangeIndex->getType(), VK_LValue, Loc); + } case CppVerifyConstruct::Trigger: { // trigger(term) in a quantifier body marks term as an instantiation // pattern. From dd224624e48a58a8335e2c58a0d2fe10492c8773 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:19 +0530 Subject: [PATCH 044/119] verify: Declare a range-based for loop's position and keep its contract. --- clang/lib/Parse/ParseStmt.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index fcc770a281..39c408a531 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2309,10 +2309,24 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, // Match the ')'. T.consumeClose(); - // CppVerify: loop contract clauses before the body. + // CppVerify: loop contract clauses before the body. A range-based for + // loop's contract may name its position, cppverify::index. LoopContractInfo ForContract; - if (getLangOpts().VerifyContracts) + if (getLangOpts().VerifyContracts) { + VarDecl *RangeIndex = nullptr; + if (ForRangeInfo.ParsedForRangeDecl()) { + ASTContext &Ctx = Actions.getASTContext(); + QualType IndexType = Ctx.getPointerDiffType(); + RangeIndex = VarDecl::Create( + Ctx, Actions.getContractBinderContext(), ForLoc, ForLoc, + &Ctx.Idents.get("__cppverify_index"), IndexType, + Ctx.getTrivialTypeSourceInfo(IndexType, ForLoc), SC_None); + RangeIndex->setImplicit(); + } + llvm::SaveAndRestore Index(CppVerifyRangeIndex, RangeIndex); ParseLoopContractClauses(ForContract); + ForContract.Index = RangeIndex; + } // C++ Coroutines [stmt.iter]: // 'co_await' can only be used for a range-based for statement. @@ -2434,8 +2448,13 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, return Actions.FinishCXXExpansionStmt(ForRangeStmt.get(), Body.get()); } - if (ForRangeInfo.ParsedForRangeDecl()) - return Actions.FinishCXXForRangeStmt(ForRangeStmt.get(), Body.get()); + if (ForRangeInfo.ParsedForRangeDecl()) { + StmtResult RangeResult = + Actions.FinishCXXForRangeStmt(ForRangeStmt.get(), Body.get()); + if (getLangOpts().VerifyContracts) + attachLoopContract(RangeResult, ForContract); + return RangeResult; + } StmtResult ForResult = Actions.ActOnForStmt( ForLoc, T.getOpenLocation(), FirstPart.get(), SecondPart, ThirdPart, From 065f41a1592aadfe61190dc0b2268826b105571a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:20 +0530 Subject: [PATCH 045/119] verify: Instantiate a range-based for loop's contract with its position. --- clang/lib/Sema/TreeTransform.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 5e33617c37..ee31ec1a1d 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9430,9 +9430,14 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { SemaRef.OpenACC().ActOnForStmtEnd(S->getBeginLoc(), Body); + std::optional Contract; + bool ContractChanged = false; + if (getDerived().TransformLoopContract(S, Contract, ContractChanged)) + return StmtError(); + // Body has changed but we didn't rebuild the for-range statement. Rebuild // it now so we have a new statement to attach the body to. - if (Body.get() != S->getBody() && NewStmt.get() == S) { + if ((Body.get() != S->getBody() || ContractChanged) && NewStmt.get() == S) { NewStmt = getDerived().RebuildCXXForRangeStmt( S->getForLoc(), S->getCoawaitLoc(), Init.get(), S->getColonLoc(), Range.get(), Begin.get(), End.get(), Cond.get(), Inc.get(), @@ -9444,7 +9449,11 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { if (NewStmt.get() == S) return S; - return FinishCXXForRangeStmt(NewStmt.get(), Body.get()); + StmtResult Result = FinishCXXForRangeStmt(NewStmt.get(), Body.get()); + if (Contract && Result.isUsable()) + SemaRef.Context.getOrCreateLoopContract(Result.get()) = + std::move(*Contract); + return Result; } template @@ -18464,6 +18473,8 @@ VarDecl *TreeTransform::TransformContractBinder(VarDecl *Binder) { SemaRef.Context, Context, Binder->getBeginLoc(), Binder->getLocation(), Binder->getIdentifier(), Binder->getType(), Binder->getTypeSourceInfo(), SC_None); + if (Binder->isImplicit()) + New->setImplicit(); getDerived().transformedLocalDecl(Binder, {New}); return New; } @@ -18588,6 +18599,9 @@ bool TreeTransform::TransformLoopContract( if (!From) return false; To.emplace(); + // A range-based for loop's cppverify::index is declared with its contract. + if (From->Index) + To->Index = getDerived().TransformContractBinder(From->Index); bool Invalid = false; auto Keep = [&](Expr *Old, ExprResult New, SmallVectorImpl &Out) { if (!New.isUsable()) { From 8ee3d287203285e121426d314e30f096847ae20c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:21 +0530 Subject: [PATCH 046/119] verify: Visit a range-based for loop's contract. --- clang/include/clang/AST/RecursiveASTVisitor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 537730537b..cadafbf9af 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2667,6 +2667,7 @@ DEF_TRAVERSE_STMT(ObjCForCollectionStmt, {}) DEF_TRAVERSE_STMT(ObjCAutoreleasePoolStmt, {}) DEF_TRAVERSE_STMT(CXXForRangeStmt, { + TRY_TO(TraverseLoopContract(S)); if (!getDerived().shouldVisitImplicitCode()) { if (S->getInit()) TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getInit()); From bdc8e4ea5d88a9ceb0a4ed948fb7780df3992f20 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:22 +0530 Subject: [PATCH 047/119] verify: Print a range-based for loop's clauses, its position as cppverify::index. --- clang/lib/AST/StmtPrinter.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 0c4b49f75a..d1d249ad1d 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -53,6 +53,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -73,6 +74,8 @@ namespace { PrintingPolicy Policy; std::string NL; const ASTContext *Context; + /// The position of the range-based for loop whose clauses are printed. + const VarDecl *RangeIndex = nullptr; public: StmtPrinter(raw_ostream &os, PrinterHelper *helper, @@ -490,6 +493,15 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) { OS << " : "; PrintExpr(Node->getRangeInit()); OS << ")"; + const LoopContractInfo *LCI = + Context ? Context->getLoopContract(Node) : nullptr; + { + llvm::SaveAndRestore Index(RangeIndex, + LCI ? LCI->Index : nullptr); + printLoopContract( + OS, Context, Node, [&]() -> raw_ostream & { return Indent(1); }, + [&](Expr *E) { PrintExpr(E); }, NL); + } PrintControlledStmt(Node->getBody()); } @@ -1481,6 +1493,10 @@ void StmtPrinter::VisitConstantExpr(ConstantExpr *Node) { void StmtPrinter::VisitDeclRefExpr(DeclRefExpr *Node) { ValueDecl *VD = Node->getDecl(); + if (RangeIndex && VD == RangeIndex) { + OS << "cppverify::index"; + return; + } if (const auto *OCED = dyn_cast(VD)) { OCED->getInit()->IgnoreImpCasts()->printPretty(OS, nullptr, Policy); return; From dad1148c43f4a83bd44eecba49ca895ce3fa8c2f Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:23 +0530 Subject: [PATCH 048/119] verify: Dump a range-based for loop's clauses. --- clang/include/clang/AST/ASTDumper.h | 1 + clang/lib/AST/ASTDumper.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/clang/include/clang/AST/ASTDumper.h b/clang/include/clang/AST/ASTDumper.h index 883f9c38e8..74fd5364d6 100644 --- a/clang/include/clang/AST/ASTDumper.h +++ b/clang/include/clang/AST/ASTDumper.h @@ -53,6 +53,7 @@ class ASTDumper : public ASTNodeTraverser { void VisitWhileStmt(const WhileStmt *S); void VisitDoStmt(const DoStmt *S); void VisitForStmt(const ForStmt *S); + void VisitCXXForRangeStmt(const CXXForRangeStmt *S); }; } // namespace clang diff --git a/clang/lib/AST/ASTDumper.cpp b/clang/lib/AST/ASTDumper.cpp index c9efb09faa..dbecef76ae 100644 --- a/clang/lib/AST/ASTDumper.cpp +++ b/clang/lib/AST/ASTDumper.cpp @@ -252,6 +252,19 @@ void ASTDumper::VisitForStmt(const ForStmt *S) { Visit(D, "decreases"); } +void ASTDumper::VisitCXXForRangeStmt(const CXXForRangeStmt *S) { + ASTNodeTraverser::VisitCXXForRangeStmt(S); + if (!Ctx) + return; + const LoopContractInfo *LCI = Ctx->getLoopContract(S); + if (!LCI) + return; + for (const Expr *E : LCI->Invariants) + Visit(E, "invariant"); + for (const Expr *D : LCI->Decreases) + Visit(D, "decreases"); +} + //===----------------------------------------------------------------------===// // Type method implementations //===----------------------------------------------------------------------===// From 6c5ef28ac9661f066fcd6131cdf1591a6920be82 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:24 +0530 Subject: [PATCH 049/119] verify: Expose a range-based for loop's clauses as libclang cursors. --- clang/tools/libclang/CIndex.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index 1852f755c5..18a7e056ad 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -3116,6 +3116,7 @@ void EnqueueVisitor::VisitCXXCatchStmt(const CXXCatchStmt *S) { void EnqueueVisitor::VisitCXXForRangeStmt(const CXXForRangeStmt *S) { AddStmt(S->getBody()); + AddLoopContract(S); AddStmt(S->getRangeInit()); AddDecl(S->getLoopVariable()); } From 260a9cc83f5f918f18e74613764a2d221f690d7d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:25 +0530 Subject: [PATCH 050/119] verify: Verify range-based for loops over arrays, their position named cppverify::index. --- clang/lib/Verify/Frontend/ASTConverter.cpp | 508 +++++++++++++++++---- clang/lib/Verify/Frontend/ASTConverter.h | 36 +- 2 files changed, 451 insertions(+), 93 deletions(-) diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 190668966c..77503e6d67 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -4525,56 +4525,34 @@ bool ASTConverter::subobjectPointerRoot(const Expr *E) const { return VD && !LocalReferenceProvenanceVariables.count(VD); } -bool ASTConverter::isConstantTable(const VarDecl *VD) const { - QualType Element = VD->getType(); - bool IsArray = false; - while (const ConstantArrayType *Bounded = - Ctx.getAsConstantArrayType(Element)) { - IsArray = true; - Element = Bounded->getElementType(); - } - const VarDecl *Definition = VD->getDefinition() ? VD->getDefinition() : VD; - const APValue *Value = IsArray ? Definition->evaluateValue() : nullptr; - return Value && Value->isArray() && VD->hasGlobalStorage() && - VD->getTLSKind() == VarDecl::TLS_None && - (Element->isIntegralOrEnumerationType() || Element->isBooleanType()) && - Element.isConstQualified() && !Element.isVolatileQualified(); -} - -std::optional> -ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { - // t[i][j]: the indices, outermost first, of a whole read of an array. - std::vector Indices; - const Expr *Array = AS; - while (const auto *Subscript = dyn_cast(Array)) { - const auto *Decay = dyn_cast(Subscript->getBase()); - if (!Decay || Decay->getCastKind() != CK_ArrayToPointerDecay) - return std::nullopt; - Indices.insert(Indices.begin(), Subscript->getIdx()); - Array = Decay->getSubExpr()->IgnoreParens(); - } +std::optional +ASTConverter::constantTable(const Expr *Array) const { + Array = Array->IgnoreParens(); const auto *Ref = dyn_cast(Array); const auto *VD = Ref ? dyn_cast(Ref->getDecl()) : nullptr; const auto *Literal = dyn_cast(Array); if (!VD && !Literal) return std::nullopt; - std::vector Dims; - QualType Element = Array->getType(); + return constantTable(VD, Literal, Array->getType()); +} + +std::optional +ASTConverter::constantTable(const VarDecl *VD, const StringLiteral *Literal, + QualType Element) const { + ConstantTable Table; while (const ConstantArrayType *Bounded = Ctx.getAsConstantArrayType(Element)) { - Dims.push_back(Bounded->getZExtSize()); + Table.Dims.push_back(Bounded->getZExtSize()); Element = Bounded->getElementType(); } - if (Dims.size() != Indices.size() || Dims.empty() || + if (Table.Dims.empty() || !(Element->isIntegralOrEnumerationType() || Element->isBooleanType()) || Element.isVolatileQualified()) return std::nullopt; // Its values: a constant object's initializer, or a literal's code units // and the terminating zero. - const VType ElementTy = VType::fromQualType(Element, VIntMode::Machine, Ctx); - std::vector Values; - std::string Identity; + Table.ElementTy = VType::fromQualType(Element, VIntMode::Machine, Ctx); if (VD) { const VarDecl *Definition = VD->getDefinition() ? VD->getDefinition() : VD; if (!VD->hasGlobalStorage() || VD->getTLSKind() != VarDecl::TLS_None || @@ -4585,7 +4563,7 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { return std::nullopt; std::function Flatten = [&](const APValue &V) { if (V.isInt()) { - Values.push_back(V.getInt()); + Table.Values.push_back(V.getInt()); return true; } if (!V.isArray()) @@ -4601,77 +4579,93 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { return std::nullopt; const PresumedLoc Where = Ctx.getSourceManager().getPresumedLoc(VD->getLocation()); - Identity = "__cppverify_table." + VD->getQualifiedNameAsString(); + Table.Name = VD->getNameAsString(); + Table.Identity = "__cppverify_table." + VD->getQualifiedNameAsString(); if (Where.isValid()) - Identity += "@" + std::to_string(Where.getLine()) + ":" + - std::to_string(Where.getColumn()); + Table.Identity += "@" + std::to_string(Where.getLine()) + ":" + + std::to_string(Where.getColumn()); } else { - const unsigned Width = ElementTy.BitWidth; - for (uint64_t K = 0; K < Dims.front(); ++K) - Values.push_back(llvm::APSInt( + const unsigned Width = Table.ElementTy.BitWidth; + for (uint64_t K = 0; K < Table.Dims.front(); ++K) + Table.Values.push_back(llvm::APSInt( llvm::APInt(Width, K < Literal->getLength() ? Literal->getCodeUnit(K) : 0), - !ElementTy.IsSigned)); + !Table.ElementTy.IsSigned)); const PresumedLoc Where = Ctx.getSourceManager().getPresumedLoc(Literal->getBeginLoc()); - Identity = "__cppverify_string"; + Table.Name = "string literal"; + Table.Identity = "__cppverify_string"; if (Where.isValid()) - Identity += "@" + std::to_string(Where.getLine()) + ":" + - std::to_string(Where.getColumn()); + Table.Identity += "@" + std::to_string(Where.getLine()) + ":" + + std::to_string(Where.getColumn()); } uint64_t Count = 1; - for (uint64_t D : Dims) + for (uint64_t D : Table.Dims) Count *= D; - if (Values.size() != Count) + if (Table.Values.size() != Count) return std::nullopt; - const SourceLocation Loc = AS->getExprLoc(); - const VType Index = VType::makeInt(VIntMode::Math, 64, true); - auto Constant = [&](const llvm::APSInt &V) -> std::unique_ptr { - if (ElementTy.Kind == VTypeKind::Bool) - return std::make_unique(!V.isZero(), ElementTy, Loc); - return std::make_unique(integerValueString(V), ElementTy, - Loc); - }; + return Table; +} + +bool ASTConverter::isConstantTable(const VarDecl *VD) const { + return constantTable(VD, nullptr, VD->getType()).has_value(); +} + +void ASTConverter::defineTable(const ConstantTable &Table, SourceLocation Loc) { // The table as a spec of the linear index: a balanced tree of // comparisons, so its depth grows with the logarithm of its size. - if (llvm::none_of(BuiltinFunctions, - [&](const auto &Fn) { return Fn->Identity == Identity; })) { + if (llvm::none_of(BuiltinFunctions, [&](const auto &Fn) { + return Fn->Identity == Table.Identity; + })) { + const VType Index = VType::makeInt(VIntMode::Math, 64, true); + auto Constant = [&](const llvm::APSInt &V) -> std::unique_ptr { + if (Table.ElementTy.Kind == VTypeKind::Bool) + return std::make_unique(!V.isZero(), Table.ElementTy, + Loc); + return std::make_unique(integerValueString(V), + Table.ElementTy, Loc); + }; std::function(uint64_t, uint64_t)> Tree = [&](uint64_t Lo, uint64_t Hi) -> std::unique_ptr { - if (std::all_of(Values.begin() + Lo + 1, Values.begin() + Hi, + if (std::all_of(Table.Values.begin() + Lo + 1, Table.Values.begin() + Hi, [&](const llvm::APSInt &V) { - return llvm::APSInt::isSameValue(V, Values[Lo]); + return llvm::APSInt::isSameValue(V, Table.Values[Lo]); })) - return Constant(Values[Lo]); + return Constant(Table.Values[Lo]); const uint64_t Mid = Lo + (Hi - Lo) / 2; return std::make_unique( std::make_unique( VBinOp::Lt, std::make_unique("i", Index, Loc), std::make_unique(std::to_string(Mid), Index, Loc), VType::makeBool(), Loc), - Tree(Lo, Mid), Tree(Mid, Hi), ElementTy, Loc); + Tree(Lo, Mid), Tree(Mid, Hi), Table.ElementTy, Loc); }; auto Fn = std::make_unique(); - Fn->Name = VD ? VD->getNameAsString() : "string literal"; - Fn->Identity = Identity; + Fn->Name = Table.Name; + Fn->Identity = Table.Identity; Fn->IsSpec = true; Fn->IsBuiltin = true; Fn->IntMode = VIntMode::Math; - Fn->ReturnType = ElementTy; + Fn->ReturnType = Table.ElementTy; Fn->Params = {{"i", Index}}; Fn->Body.push_back( - std::make_unique(Tree(0, Values.size()), Loc)); + std::make_unique(Tree(0, Table.Values.size()), Loc)); BuiltinFunctions.push_back(std::move(Fn)); } +} +std::unique_ptr +ASTConverter::tableRead(const ConstantTable &Table, + std::vector> Indices, + SourceLocation Loc) { + defineTable(Table, Loc); // The read: its linear index, and each index inside its array. + const VType Index = VType::makeInt(VIntMode::Math, 64, true); std::unique_ptr Linear; std::unique_ptr Inside; for (size_t D = 0; D < Indices.size(); ++D) { - auto I = convertExpr(Indices[D]); - if (!I) - return std::unique_ptr(); + std::unique_ptr I = std::move(Indices[D]); if (I->Ty.IntMode != VIntMode::Math) { VType Machine = I->Ty; I = std::make_unique(std::move(I), Machine, Index, Loc); @@ -4683,7 +4677,8 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { cloneVExpr(I.get()), VType::makeBool(), Loc), std::make_unique( VBinOp::Lt, cloneVExpr(I.get()), - std::make_unique(std::to_string(Dims[D]), Index, Loc), + std::make_unique(std::to_string(Table.Dims[D]), Index, + Loc), VType::makeBool(), Loc), VType::makeBool(), Loc); Inside = Inside @@ -4696,7 +4691,7 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { std::make_unique( VBinOp::Mul, std::move(Linear), std::make_unique( - std::to_string(Dims[D]), Index, Loc), + std::to_string(Table.Dims[D]), Index, Loc), Index, Loc), std::move(I), Index, Loc) : std::move(I); @@ -4704,14 +4699,37 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { std::vector> Args; Args.push_back(std::move(Linear)); auto Read = std::make_unique( - VD ? VD->getNameAsString() : "string literal", Identity, std::move(Args), - ElementTy, Loc); + Table.Name, Table.Identity, std::move(Args), Table.ElementTy, Loc); auto Copy = cloneVExpr(Read.get()); - return std::unique_ptr(std::make_unique( + return std::make_unique( std::make_unique( std::move(Inside), static_cast(ProofObligationKind::Bounds), Loc), - std::move(Read), std::move(Copy), ElementTy, Loc)); + std::move(Read), std::move(Copy), Table.ElementTy, Loc); +} + +std::optional> +ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { + // t[i][j]: the indices, outermost first, of a whole read of an array. + std::vector Indices; + const Expr *Array = AS; + while (const auto *Subscript = dyn_cast(Array)) { + const auto *Decay = dyn_cast(Subscript->getBase()); + if (!Decay || Decay->getCastKind() != CK_ArrayToPointerDecay) + return std::nullopt; + Indices.insert(Indices.begin(), Subscript->getIdx()); + Array = Decay->getSubExpr()->IgnoreParens(); + } + std::optional Table = constantTable(Array); + if (!Table || Table->Dims.size() != Indices.size()) + return std::nullopt; + std::vector> Converted; + for (const Expr *I : Indices) { + Converted.push_back(convertExpr(I)); + if (!Converted.back()) + return std::unique_ptr(); + } + return tableRead(*Table, std::move(Converted), AS->getExprLoc()); } std::string ASTConverter::arrayDecayError(const Expr *Array) const { @@ -6096,6 +6114,12 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { Ty, E->getExprLoc()); } if (const auto *DRE = dyn_cast(E)) { + // cppverify::index in a range-based for loop's contract. + if (RangeIndex && DRE->getDecl() == RangeIndex) + return std::make_unique( + RangeIndexName, + VType::fromQualType(RangeIndex->getType(), VIntMode::Machine, Ctx), + E->getExprLoc()); // A structured binding names a member or element of its object. if (const auto *Binding = dyn_cast(DRE->getDecl())) { if (const Expr *Bound = Binding->getBinding()) @@ -8007,6 +8031,303 @@ void ASTConverter::leaveAutomaticScope(std::vector> &Out, } } +/// Whether something a range-based for loop's range is located through may +/// change in its body: C++ evaluates the range once, and the verifier +/// locates its elements again at each iteration. A pointer or index +/// variable must only be read, and the range may read no pointer from +/// memory and make no call. +static bool rangeMayMove(const Expr *Range, const Stmt *Body) { + struct Located : RecursiveASTVisitor { + std::set Vars; + bool Unstable = false; + bool VisitDeclRefExpr(DeclRefExpr *Ref) { + if (const auto *Var = dyn_cast(Ref->getDecl())) + if (Var->getType()->isPointerType() || + Var->getType()->isIntegralOrEnumerationType()) { + Vars.insert(Var); + Unstable |= + Var->hasGlobalStorage() && !Var->getType().isConstQualified(); + } + return true; + } + bool VisitCallExpr(CallExpr *) { + Unstable = true; + return false; + } + bool VisitMemberExpr(MemberExpr *M) { + const Expr *Base = M->getBase()->IgnoreParenImpCasts(); + Unstable |= M->isArrow() && !isa(Base); + return true; + } + bool VisitUnaryOperator(UnaryOperator *U) { + const Expr *Operand = U->getSubExpr()->IgnoreParenImpCasts(); + Unstable |= U->getOpcode() == UO_Deref && !isa(Operand); + return true; + } + } Location; + Location.TraverseStmt(const_cast(Range)); + if (Location.Unstable) + return true; + // Every use of such a variable in the body reads its value. + struct Uses : RecursiveASTVisitor { + const std::set &Vars; + std::set Read; + bool Changed = false; + explicit Uses(const std::set &Vars) : Vars(Vars) {} + bool VisitImplicitCastExpr(ImplicitCastExpr *Cast) { + if (Cast->getCastKind() == CK_LValueToRValue) + if (const auto *Ref = + dyn_cast(Cast->getSubExpr()->IgnoreParens())) + Read.insert(Ref); + return true; + } + bool VisitDeclRefExpr(DeclRefExpr *Ref) { + const auto *Var = dyn_cast(Ref->getDecl()); + Changed |= Var && Vars.count(Var) && !Read.count(Ref); + return true; + } + } Body_(Location.Vars); + Body_.TraverseStmt(const_cast(Body)); + return Body_.Changed; +} + +/// Whether \p S names \p Var, or one of its bindings. +static bool mentionsLoopVariable(const Stmt *S, const VarDecl *Var) { + if (!S) + return false; + if (const auto *Ref = dyn_cast(S)) { + if (Ref->getDecl() == Var) + return true; + if (const auto *Binding = dyn_cast(Ref->getDecl())) + if (Binding->getDecomposedDecl() == Var) + return true; + } + return llvm::any_of(S->children(), [&](const Stmt *Child) { + return mentionsLoopVariable(Child, Var); + }); +} + +std::vector> +ASTConverter::convertRangeFor(const CXXForRangeStmt *RF) { + std::vector> Out; + const SourceLocation Loc = RF->getForLoc(); + enterAutomaticScope(); + if (RF->getInit()) { + auto Init = convertStmt(RF->getInit()); + Out.insert(Out.end(), std::make_move_iterator(Init.begin()), + std::make_move_iterator(Init.end())); + } + auto Fail = [&](const std::string &Message) { + error(CurrentFn->Name + ": " + Message); + leaveAutomaticScope(Out, RF->getEndLoc()); + return std::move(Out); + }; + const Expr *Range = RF->getRangeInit(); + const ConstantArrayType *Array = + Range ? Ctx.getAsConstantArrayType(Range->getType()) : nullptr; + if (!Array) + return Fail("a range-based for loop over a class (through its begin and " + "end) is unsupported; loop over an array, or index the class"); + if (rangeMayMove(Range, RF->getBody())) + return Fail("the range of this loop is located through a call, a pointer " + "read from memory, or a variable its body may change; locate " + "it through a variable the body does not change"); + const uint64_t Count = Array->getZExtSize(); + const QualType Element = Array->getElementType(); + const uint64_t Stride = Ctx.getTypeSizeInChars(Element).getQuantity(); + if (Element->isArrayType()) + return Fail("a range-based for loop over an array of arrays is " + "unsupported; index it"); + std::optional Table; + if (!objectPlace(Range)) { + Table = constantTable(Range); + const VarDecl *Root = selectionRootVar(Range); + if (!Table && Root && Root->hasGlobalStorage()) + return Fail( + "a range-based for loop over an array of the " + + std::string(Root->isStaticLocal() ? "static local" : "global") + + " '" + Root->getNameAsString() + "' is unsupported"); + if (!Table) + return Fail("a range-based for loop over this array is unsupported; " + "index it"); + } + + // The position: cppverify::index in the contract, a counter here. The + // loop variable is declared in each iteration, so the contract, which + // holds at the head, cannot name it. + const LoopContractInfo *LCI = Ctx.getLoopContract(RF); + const VarDecl *Var = RF->getLoopVariable(); + if (LCI && llvm::any_of(LCI->children(), [&](const Stmt *Clause) { + return mentionsLoopVariable(Clause, Var); + })) + return Fail("the contract of a range-based for loop cannot name its " + "variable, which each iteration declares; name the element " + "with cppverify::index"); + const std::string Index = "__index_" + std::to_string(++NestedCallId); + const VType IndexTy = + VType::fromQualType(Ctx.getPointerDiffType(), VIntMode::Machine, Ctx); + llvm::SaveAndRestore Named(RangeIndexName, Index); + llvm::SaveAndRestore Declared(RangeIndex, + LCI ? LCI->Index : nullptr); + DeclaredValueNames.insert(Index); + InitializedValues.insert(Index); + CurrentFn->SourceVariables[Index] = {"cppverify::index", IndexTy, Loc, Loc}; + auto Position = [&] { + return std::make_unique(Index, IndexTy, Loc); + }; + auto Size = [&] { + return std::make_unique(std::to_string(Count), IndexTy, Loc); + }; + Out.push_back(std::make_unique( + Index, std::make_unique(0, IndexTy, Loc), Loc)); + + // 0 <= index <= size holds at the head; the user's clauses follow. + std::vector> Invariants; + Invariants.push_back(std::make_unique( + VBinOp::And, + std::make_unique( + VBinOp::Le, std::make_unique(0, IndexTy, Loc), + Position(), VType::makeBool(), Loc), + std::make_unique(VBinOp::Le, Position(), Size(), + VType::makeBool(), Loc), + VType::makeBool(), Loc)); + std::vector> Decreases; + if (LCI) { + llvm::SaveAndRestore Contract(InContractExpression, true); + for (const Expr *Inv : LCI->Invariants) + if (auto E = convertExpr(Inv)) + Invariants.push_back(std::move(E)); + for (const Expr *D : LCI->Decreases) + if (auto E = convertExpr(D)) + Decreases.push_back(std::move(E)); + } + // Without a measure, the elements left: a range over an array ends. + if (Decreases.empty() && !(LCI && LCI->MayDiverge.isValid())) + Decreases.push_back(std::make_unique(VBinOp::Sub, Size(), + Position(), IndexTy, Loc)); + if (!checkLoopTermination(RF, !Decreases.empty())) { + leaveAutomaticScope(Out, RF->getEndLoc()); + return Out; + } + + const std::set Before = InitializedValues; + const bool BeforeReachable = InitializationPathReachable; + ++LoopDepth; + auto Increment = std::make_unique( + Index, + std::make_unique( + VBinOp::Add, Position(), + std::make_unique(1, IndexTy, Loc), IndexTy, Loc), + Loc); + EnclosingLoop Loop; + Loop.IsGhost = InGhost; + Loop.IncrementStmt = Increment.get(); + EnclosingLoops.push_back(std::move(Loop)); + enterAutomaticScope(); + + // The loop variable: the element at the position, or bound to it. A + // value converts from the element's type as its initialization does. + std::vector> Body; + auto Converted = [&](std::unique_ptr Value, + QualType Target) -> std::unique_ptr { + const VType To = VType::fromQualType(Target, IntMode, Ctx); + Value = materialize(std::move(Value), To); + if (!Value || sameRepresentation(Value->Ty, To)) + return Value; + if (!(Value->Ty.isInt() || Value->Ty.Kind == VTypeKind::Bool) || + !(To.isInt() || To.Kind == VTypeKind::Bool)) { + error(CurrentFn->Name + + ": unsupported conversion of a range's element to its loop " + "variable"); + return nullptr; + } + const VType From = Value->Ty; + return std::make_unique(std::move(Value), From, To, Loc); + }; + declareLocalName(Var); + recordSourceVariable(Var); + if (AddressableLocals.count(Var)) { + error(CurrentFn->Name + + ": addressable local declarations inside loops are unsupported"); + } else if (Table) { + std::vector> Indices; + Indices.push_back(Position()); + auto Value = tableRead(*Table, std::move(Indices), Loc); + QualType VarType = Var->getType().getNonReferenceType(); + if (Var->getType()->isReferenceType() && !VarType.isConstQualified()) + error(CurrentFn->Name + ": a constant table's elements cannot be " + "bound to a non-const reference"); + else if (auto Stored = Converted(std::move(Value), VarType)) { + Body.push_back(std::make_unique(valueName(Var), + std::move(Stored), Loc)); + markInitialized(Var); + } + } else if (auto Place = objectPlace(Range)) { + Place->addAccessCondition(std::make_unique( + VBinOp::Lt, Position(), Size(), VType::makeBool(), Loc)); + std::unique_ptr Offset = Position(); + if (Stride != 1) { + VType Math = IndexTy; + Math.IntMode = VIntMode::Math; + Offset = std::make_unique( + VBinOp::Mul, + std::make_unique(std::move(Offset), IndexTy, Math, Loc), + std::make_unique(std::to_string(Stride), Math, Loc), + Math, Loc); + } + Place->applyElement(std::move(Offset), Stride, Loc); + Place->ValueTy = VType::fromQualType(Element, IntMode, Ctx); + if (Var->getType()->isLValueReferenceType()) { + if (auto Condition = Place->takeAccessCondition()) + Body.push_back(std::make_unique( + std::move(Condition), Loc, ProofObligationKind::Bounds)); + auto Address = Place->takeAddress(); + appendReferenceBindingCheck(nullptr, Address.get(), Loc, Body); + auto Provenance = convertAddressProvenance(Address.get()); + Body.push_back(std::make_unique( + valueName(Var), std::move(Address), Loc, true)); + if (Provenance) { + const std::string ProvenanceName = "__cppverify_reference_provenance_" + + std::to_string(++LocalReferenceId); + LocalReferenceProvenanceVariables[Var] = ProvenanceName; + Body.push_back(std::make_unique( + ProvenanceName, std::move(Provenance), Loc)); + } + markInitialized(Var); + } else if (Element->isRecordType()) { + error(CurrentFn->Name + ": a range-based for loop that copies each " + "record is unsupported; bind a reference"); + } else { + auto Value = + std::make_unique(Place->takeAddress(), Place->ValueTy, Loc, + "", Place->takeAccessCondition()); + if (auto Stored = Converted(std::move(Value), Var->getType())) { + Body.push_back(std::make_unique(valueName(Var), + std::move(Stored), Loc)); + markInitialized(Var); + } + } + } + auto Statements = convertStmt(RF->getBody()); + Body.insert(Body.end(), std::make_move_iterator(Statements.begin()), + std::make_move_iterator(Statements.end())); + leaveAutomaticScope(Body, RF->getEndLoc()); + Body.push_back(std::move(Increment)); + EnclosingLoops.pop_back(); + --LoopDepth; + InitializedValues = Before; + InitializationPathReachable = BeforeReachable; + auto Repeat = std::make_unique( + std::make_unique(VBinOp::Lt, Position(), Size(), + VType::makeBool(), Loc), + std::move(Invariants), std::move(Decreases), std::move(Body), Loc); + if (LCI) + Repeat->Modifies = convertLoopFootprints(*LCI); + Out.push_back(std::move(Repeat)); + leaveAutomaticScope(Out, RF->getEndLoc()); + return Out; +} + void ASTConverter::appendBranches(std::unique_ptr Cond, const Stmt *Then, const Stmt *Else, SourceLocation Loc, std::vector> &Out) { @@ -8279,6 +8600,22 @@ ASTConverter::convertSwitch(const SwitchStmt *SS) { return Out; } +void ASTConverter::declareLocalName(const VarDecl *VD) { + // A local named like a parameter or an earlier local, in an enclosing or + // an earlier scope, is another variable: x#2, x#3, ... The object of a + // structured binding has no name of its own. + if (LocalNames.count(VD)) + return; + const std::string Base = isa(VD) ? std::string("__binding") + : VD->getNameAsString(); + std::string Name = Base; + for (unsigned N = 2; DeclaredValueNames.count(Name); ++N) + Name = Base + "#" + std::to_string(N); + DeclaredValueNames.insert(Name); + if (Name != VD->getNameAsString()) + LocalNames[VD] = Name; +} + void ASTConverter::appendAssumption(const Expr *Assumption, SourceLocation Loc, std::vector> &Out) { const bool Executable = !InGhost && !CurrentFn->IsSpec && !CurrentFn->IsProof; @@ -8606,6 +8943,8 @@ ASTConverter::convertStmtBody(const Stmt *S) { Out.insert(Out.end(), std::make_move_iterator(IncPart.begin()), std::make_move_iterator(IncPart.end())); } + if (Target->IncrementStmt) + Out.push_back(cloneVStmt(Target->IncrementStmt)); Out.push_back(std::make_unique(S->getBeginLoc())); } InitializationPathReachable = false; @@ -8821,6 +9160,8 @@ ASTConverter::convertStmtBody(const Stmt *S) { } if (const auto *SS = dyn_cast(S)) return convertSwitch(SS); + if (const auto *RF = dyn_cast(S)) + return convertRangeFor(RF); if (const auto *AS = dyn_cast(S)) { // [[fallthrough]], [[likely]], and the like do not change what a // statement does; [[assume(e)]] makes e false undefined, so e is proved, @@ -9345,20 +9686,7 @@ ASTConverter::convertStmtBody(const Stmt *S) { error(CurrentFn->Name + ": static local variables are unsupported"); continue; } - // A local named like a parameter or an earlier local, in an enclosing - // or an earlier scope, is another variable: x#2, x#3, ... - // The object of a structured binding has no name of its own. - if (!LocalNames.count(VD)) { - const std::string Base = isa(VD) - ? std::string("__binding") - : VD->getNameAsString(); - std::string Name = Base; - for (unsigned N = 2; DeclaredValueNames.count(Name); ++N) - Name = Base + "#" + std::to_string(N); - DeclaredValueNames.insert(Name); - if (Name != VD->getNameAsString()) - LocalNames[VD] = Name; - } + declareLocalName(VD); if (InGhost) GhostLocals.insert(VD); // A record that was not promoted keeps its flattened SSA representation, @@ -9696,4 +10024,4 @@ ASTConverter::convertStmtBody(const Stmt *S) { } error(CurrentFn->Name + ": unsupported statement: " + S->getStmtClassName()); return Out; -} \ No newline at end of file +} diff --git a/clang/lib/Verify/Frontend/ASTConverter.h b/clang/lib/Verify/Frontend/ASTConverter.h index 20ec6f8b44..d83dc0a171 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.h +++ b/clang/lib/Verify/Frontend/ASTConverter.h @@ -137,8 +137,14 @@ class ASTConverter { bool Continues = false; std::optional> BreakInitialized; std::optional> ContinueInitialized; + /// The increment of a loop the converter builds, which a continue runs. + const VStmt *IncrementStmt = nullptr; }; std::vector EnclosingLoops; + /// The counter that cppverify::index, the declaration RangeIndex, names + /// while a range-based for loop's contract is converted. + std::string RangeIndexName; + const VarDecl *RangeIndex = nullptr; /// Fixed addresses of the mutable global variables, assigned on first use. std::map GlobalAddresses; uint64_t NextGlobalAddress = GlobalRegionBase; @@ -317,14 +323,38 @@ class ASTConverter { SourceLocation Loc); std::unique_ptr convertArrowFieldAddress(const MemberExpr *M); std::unique_ptr convertSubscriptAddress(const ArraySubscriptExpr *AS); - /// An element of a constant table (a constant array with a constant - /// initializer, or a string literal): a spec of its index, which must lie - /// in its array. std::nullopt when \p AS reads no such table. + /// A constant table: a constant array with a constant initializer, or a + /// string literal, given to the solver as a spec of its linear index. + struct ConstantTable { + std::string Name; + std::string Identity; + std::vector Dims; + VType ElementTy; + std::vector Values; + }; + std::optional constantTable(const Expr *Array) const; + std::optional constantTable(const VarDecl *VD, + const StringLiteral *Literal, + QualType Element) const; + /// Declares the spec of \p Table once. + void defineTable(const ConstantTable &Table, SourceLocation Loc); + /// An element of \p Table: the spec at the linear index, where every index + /// must lie in its array. + std::unique_ptr tableRead(const ConstantTable &Table, + std::vector> Indices, + SourceLocation Loc); + /// A whole read of a constant table, t[i][j]; std::nullopt when \p AS + /// reads no such table. std::optional> convertTableRead(const ArraySubscriptExpr *AS); /// A constant array with a constant initializer, of integers, /// enumerations, or bools: a table, which declares no object to run. bool isConstantTable(const VarDecl *VD) const; + std::vector> + convertRangeFor(const CXXForRangeStmt *RF); + /// Gives the local \p VD its VCR name: its own, or name#N when the name is + /// already taken. + void declareLocalName(const VarDecl *VD); std::vector> convertStmt(const Stmt *S); std::vector> convertStmtBody(const Stmt *S); std::vector> convertScopedSubstatement(const Stmt *S); From f3ae55483dfe15de658b3a8c3ad2a1efd026b4a0 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:25 +0530 Subject: [PATCH 051/119] test: Range-based for loops over arrays, each with a wrong twin, and the ranges that fail closed. --- clang/test/Verify/suite/range_for.cpp | 491 ++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 clang/test/Verify/suite/range_for.cpp diff --git a/clang/test/Verify/suite/range_for.cpp b/clang/test/Verify/suite/range_for.cpp new file mode 100644 index 0000000000..5a217e247f --- /dev/null +++ b/clang/test/Verify/suite/range_for.cpp @@ -0,0 +1,491 @@ +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 > %t 2>&1 +// RUN: FileCheck %s < %t +// RUN: FileCheck %s --check-prefix=SOUND < %t +// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=5 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=BMC +// RUN: not %cpp-verify --lower-only --dump-ir=1 %s -- -std=c++20 2>&1 | FileCheck %s --check-prefix=VCR +// +// A range-based for loop over an array visits its elements in order: its +// variable is each element, a copy or a reference. Its contract names the +// position, cppverify::index, the number of elements before the current one +// (from 0 to the size); without a measure, it decreases the elements left. +// Every correct function has a wrong twin that must be refuted. +// +// No proof here holds only because no execution reaches its end. +// SOUND-NOT: vacuous + +namespace cv = cppverify; + +int sum3() + cv::post(cv::result == 6) +{ + int a[3] = {1, 2, 3}; + int s = 0; + for (int v : a) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : cv::index == 2 ? 3 : 6)) + s = s + v; + return s; +} +// CHECK-DAG: Verified: sum3 [backend=z3] + +int sum3_wrong() + cv::post(cv::result == 7) +{ + int a[3] = {1, 2, 3}; + int s = 0; + for (int v : a) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : cv::index == 2 ? 3 : 6)) + s = s + v; + return s; +} +// CHECK-DAG: error: verification failed: sum3_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +// A reference writes the element. +int zeroed() + cv::post(cv::result == 0) +{ + int a[4] = {5, 6, 7, 8}; + for (int &x : a) + cv::invariant(cv::forall(k, 0, cv::index, a[k] == 0)) + x = 0; + return a[0] + a[1] + a[2] + a[3]; +} +// CHECK-DAG: Verified: zeroed [backend=z3] + +int zeroed_wrong() + cv::post(cv::result == 0) +{ + int a[4] = {5, 6, 7, 8}; + for (int x : a) + cv::invariant(true) + x = 0; + return a[0]; +} +// CHECK-DAG: error: verification failed: zeroed_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +// An array member through a pointer, with a bound that needs the position. +struct Ring { + int buf[8]; + int head; +}; + +int total(const Ring *r) + cv::pre(r != nullptr) + cv::pre(cv::forall(k, 0, 8, 0 <= r->buf[k] && r->buf[k] <= 10)) + cv::post(0 <= cv::result && cv::result <= 80) +{ + int s = 0; + for (const int &v : r->buf) + cv::invariant(0 <= s && s <= 10 * cv::index) + s = s + v; + return s; +} +// CHECK-DAG: Verified: total [backend=z3] + +int total_wrong(const Ring *r) + cv::pre(r != nullptr) + cv::pre(cv::forall(k, 0, 8, 0 <= r->buf[k] && r->buf[k] <= 10)) + cv::post(cv::result <= 70) +{ + int s = 0; + for (int v : r->buf) + cv::invariant(0 <= s && s <= 10 * cv::index) + s = s + v; + return s; +} +// CHECK-DAG: error: verification failed: total_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +void clear(Ring *r) + cv::pre(r != nullptr) + cv::modifies(r->buf[0 : 8]) + cv::post(cv::forall(k, 0, 8, r->buf[k] == 0)) +{ + for (int &x : r->buf) + cv::invariant(cv::forall(k, 0, cv::index, r->buf[k] == 0)) + x = 0; +} +// CHECK-DAG: Verified: clear [backend=z3] + +// Binding a reference to an element requires the element, used or not. +int count_elements(Ring *r) + cv::pre(r != nullptr) + cv::post(cv::result == 8) +{ + int n = 0; + for (int &x : r->buf) + cv::invariant(n == cv::index) + n = n + 1; + return n; +} +// CHECK-DAG: Verified: count_elements [backend=z3] + +int count_elements_wrong(Ring *r) + cv::post(cv::result == 8) +{ + int n = 0; + for (int &x : r->buf) + cv::invariant(n == cv::index) + n = n + 1; + return n; +} +// CHECK-DAG: error: verification failed: count_elements_wrong [{{.*}}::dereference@[[@LINE-5]]:3{{.*}}r [ssa=r_0] [type=pointer] = 0{{.*}}[reason=counterexample] + +// A constant table, break, continue, and a conversion to the variable's +// type. +constexpr int Primes[5] = {2, 3, 5, 7, 11}; + +long first_above(int bound) + cv::post(cv::result == -1 || cv::result > bound) + cv::post(cv::result != -1 || cv::forall(k, 0, 5, Primes[k] <= bound)) +{ + long found = -1; + for (long p : Primes) + cv::invariant(found == -1) + cv::invariant(cv::forall(k, 0, cv::index, Primes[k] <= bound)) + { + if (p <= bound) + continue; + found = p; + break; + } + return found; +} +// CHECK-DAG: Verified: first_above [backend=z3] + +long first_above_wrong(int bound) + cv::post(cv::result > bound) +{ + long found = -1; + for (long p : Primes) + cv::invariant(found == -1) + cv::invariant(cv::forall(k, 0, cv::index, Primes[k] <= bound)) + { + if (p <= bound) + continue; + found = p; + break; + } + return found; +} +// CHECK-DAG: error: verification failed: first_above_wrong [{{.*}}bound [ssa=bound_0] [type=i32] = {{[0-9]+}}{{.*}}[reason=counterexample] + +// The elements are read in each iteration; the range is located once. +int doubled() + cv::post(cv::result == 12) +{ + int a[3] = {1, 2, 3}; + int s = 0; + for (int v : a) + cv::invariant(cv::forall(k, cv::index, 3, a[k] == k + 1)) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 2 : cv::index == 2 ? 6 : 12)) + s = s + 2 * v; + return s; +} +// CHECK-DAG: Verified: doubled [backend=z3] + +// In a template, cppverify::index is instantiated with its loop. +template +T sum_of_three() + cv::post(cv::result == 6) +{ + T a[3] = {1, 2, 3}; + T s = 0; + for (T v : a) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : cv::index == 2 ? 3 : 6)) + s = s + v; + return s; +} + +long use_sum_of_three() + cv::post(cv::result == 6) +{ + long r = sum_of_three(); + return r; +} +// CHECK-DAG: Verified: sum_of_three [backend=z3] +// CHECK-DAG: Verified: use_sum_of_three [backend=z3] + +// A loop over a loop, an init statement, a string literal (whose last +// element is its terminating zero), and Booleans. +int nested() + cv::post(cv::result == 9) +{ + int a[2] = {1, 2}; + int b[3] = {1, 1, 1}; + int s = 0; + for (int x : a) + cv::invariant(s == 3 * (cv::index == 0 ? 0 : cv::index == 1 ? 1 : 3)) + for (int y : b) + cv::invariant(s == 3 * (x == 1 ? 0 : 1) + x * cv::index) + s = s + x * y; + return s; +} +// CHECK-DAG: Verified: nested [backend=z3] + +int nested_wrong() + cv::post(cv::result == 6) +{ + int a[2] = {1, 2}; + int b[3] = {1, 1, 1}; + int s = 0; + for (int x : a) + cv::invariant(s == 3 * (cv::index == 0 ? 0 : cv::index == 1 ? 1 : 3)) + for (int y : b) + cv::invariant(s == 3 * (x == 1 ? 0 : 1) + x * cv::index) + s = s + x * y; + return s; +} +// CHECK-DAG: error: verification failed: nested_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +int with_init() + cv::post(cv::result == 6) +{ + int s = 0; + for (int a[3] = {1, 2, 3}; int v : a) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : cv::index == 2 ? 3 : 6)) + s = s + v; + return s; +} +// CHECK-DAG: Verified: with_init [backend=z3] + +int letters() + cv::post(cv::result == 3) +{ + int n = 0; + for (char c : "abc") + cv::invariant(n == (cv::index < 3 ? cv::index : 3)) + if (c != 0) + n = n + 1; + return n; +} +// CHECK-DAG: Verified: letters [backend=z3] + +int letters_wrong() + cv::post(cv::result == 4) +{ + int n = 0; + for (char c : "abc") + cv::invariant(n == (cv::index < 3 ? cv::index : 3)) + if (c != 0) + n = n + 1; + return n; +} +// CHECK-DAG: error: verification failed: letters_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +bool any_set() + cv::post(cv::result) +{ + bool f[3] = {false, true, false}; + bool r = false; + for (bool b : f) + cv::invariant(r == (cv::index >= 2)) + r = r || b; + return r; +} +// CHECK-DAG: Verified: any_set [backend=z3] + +bool any_set_wrong() + cv::post(!cv::result) +{ + bool f[3] = {false, true, false}; + bool r = false; + for (bool b : f) + cv::invariant(r == (cv::index >= 2)) + r = r || b; + return r; +} +// CHECK-DAG: error: verification failed: any_set_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +// An array member of a local record, records bound by reference, and the +// user's measure. +struct Bucket { + int items[3]; +}; + +int local_member() + cv::post(cv::result == 6) +{ + Bucket b; + b.items[0] = 1; + b.items[1] = 2; + b.items[2] = 3; + int s = 0; + for (int v : b.items) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : cv::index == 2 ? 3 : 6)) + cv::invariant(b.items[0] == 1 && b.items[1] == 2 && b.items[2] == 3) + s = s + v; + return s; +} +// CHECK-DAG: Verified: local_member [backend=z3] + +struct Pt { + int x; + int y; +}; + +int sum_x() + cv::post(cv::result == 4) +{ + Pt pts[2] = {{1, 2}, {3, 4}}; + int s = 0; + for (const Pt &p : pts) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : 4)) + s = s + p.x; + return s; +} +// CHECK-DAG: Verified: sum_x [backend=z3] + +int sum_x_wrong() + cv::post(cv::result == 6) +{ + Pt pts[2] = {{1, 2}, {3, 4}}; + int s = 0; + for (const Pt &p : pts) + cv::invariant(s == (cv::index == 0 ? 0 : cv::index == 1 ? 1 : 4)) + s = s + p.x; + return s; +} +// CHECK-DAG: error: verification failed: sum_x_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] + +int measured() + cv::post(true) +{ + int a[3] = {1, 1, 1}; + int s = 0; + for (int v : a) + cv::decreases(3 - cv::index) + s = v; + return s; +} +// CHECK-DAG: Verified: measured [backend=z3] + +int measured_wrong() + cv::post(true) +{ + int a[3] = {1, 1, 1}; + int s = 0; + for (int v : a) + cv::decreases(cv::index) + s = v; + return s; +} +// CHECK-DAG: error: verification failed: measured_wrong [{{.*}}::termination@{{.*}}[reason=counterexample] + +int may_diverge() + cv::post(true) +{ + int a[3] = {1, 1, 1}; + int s = 0; + for (int v : a) + cv::decreases(*) + s = v; + return s; +} +// CHECK-DAG: Verified: may_diverge [backend=z3] [partial] + +// The contract holds at the head, where the loop variable is not declared. +int names_variable() + cv::post(true) +{ + int a[2] = {1, 2}; + int s = 0; + for (int v : a) + cv::invariant(v >= 0) + s = v; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-5]]:3: error: names_variable: the contract of a range-based for loop cannot name its variable, which each iteration declares; name the element with cppverify::index + +// Not supported: a range that may move, and a range over a class. +// A global array is not supported, nor an array of arrays. +int Counts[4] = {1, 2, 3, 4}; + +int over_global() + cv::post(true) +{ + int s = 0; + for (int v : Counts) + s = v; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-4]]:3: error: over_global: a range-based for loop over an array of the global 'Counts' is unsupported + +int first_column() + cv::post(true) +{ + int g[2][2] = {{1, 2}, {3, 4}}; + int s = 0; + for (auto row : g) + s = row[0]; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-4]]:3: error: first_column: a range-based for loop over an array of arrays is unsupported; index it + +int moving(Ring *r, Ring *other) + cv::pre(r != nullptr && other != nullptr) + cv::post(true) +{ + int s = 0; + for (int v : r->buf) { + s = v; + r = other; + } + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-6]]:3: error: moving: the range of this loop is located through a call, a pointer read from memory, or a variable its body may change; locate it through a variable the body does not change + +[[cppverify::trusted]] Ring *self(Ring *r) + cv::pre(r != nullptr) + cv::post(cv::result == r); + +int through_call(Ring *r) + cv::pre(r != nullptr) + cv::post(true) +{ + int s = 0; + for (int v : self(r)->buf) + s = v; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-4]]:3: error: through_call: the range of this loop is located through a call, a pointer read from memory, or a variable its body may change; locate it through a variable the body does not change + +[[cppverify::trusted]] int which(int x) + cv::post(0 <= cv::result && cv::result < 2); + +int row_through_call(int x) + cv::post(true) +{ + int g[2][3] = {{1, 2, 3}, {4, 5, 6}}; + int s = 0; + for (int v : g[which(x)]) + s = v; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-4]]:3: error: row_through_call: the range of this loop is located through a call, a pointer read from memory, or a variable its body may change; locate it through a variable the body does not change + +struct Bag { + int items[2]; + const int *begin() const { return items; } + const int *end() const { return items + 2; } +}; + +int bag_total(const Bag *b) + cv::pre(b != nullptr) + cv::post(true) +{ + int s = 0; + for (int v : *b) + s = v; + return s; +} +// CHECK-DAG: range_for.cpp:[[@LINE-4]]:3: error: bag_total: a range-based for loop over a class (through its begin and end) is unsupported; loop over an array, or index the class + +// BMC: Verified: sum3 [backend=bmc, bound=3] +// BMC: error: verification failed: sum3_wrong + +// VCR-LABEL: fn sum3 +// VCR: assign __index_[[ID:[0-9]+]] +// VCR-NEXT: 0 +// VCR-NEXT: while +// VCR-NEXT: < +// VCR-NEXT: __index_[[ID]] +// VCR-NEXT: 3 From 1687d76581c0aad2cf052f38c1167e69dcca8c1a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:26 +0530 Subject: [PATCH 052/119] test: cppverify::index outside a range-based for loop's contract. --- clang/test/Verify/errors_range_index.cpp | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 clang/test/Verify/errors_range_index.cpp diff --git a/clang/test/Verify/errors_range_index.cpp b/clang/test/Verify/errors_range_index.cpp new file mode 100644 index 0000000000..eef2c10463 --- /dev/null +++ b/clang/test/Verify/errors_range_index.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -std=c++20 -fverify-contracts -verify %s +// +// cppverify::index is the position of a range-based for loop, named in that +// loop's contract only. + +int body(int n) + cppverify::pre(cppverify::index > 0) // expected-error {{'cppverify::index' is the position of a range-based for loop, in its contract}} +{ + return cppverify::index; // expected-error {{'cppverify::index' is the position of a range-based for loop, in its contract}} +} + +int counted(int n) { + int s = 0; + for (int i = 0; i < n; i = i + 1) + cppverify::decreases(n - i) + cppverify::invariant(cppverify::index >= 0) // expected-error {{'cppverify::index' is the position of a range-based for loop, in its contract}} + s = s + 1; + return s; +} + +int ranged() { + int a[3] = {1, 2, 3}; + int s = 0; + for (int v : a) + cppverify::invariant(0 <= cppverify::index && cppverify::index <= 3) + cppverify::decreases(3 - cppverify::index) + { + cppverify::check(cppverify::index < 3); // expected-error {{'cppverify::index' is the position of a range-based for loop, in its contract}} + s = s + v; + } + return s; +} + +// Unqualified, index is an ordinary name. +int index = 0; +int plain() { return index; } From d5a93145db99485d68345a481a585486d0caa919 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:27 +0530 Subject: [PATCH 053/119] test: Print a range-based for loop's clauses and parse them back. --- clang/test/Verify/ast_print_contracts.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clang/test/Verify/ast_print_contracts.cpp b/clang/test/Verify/ast_print_contracts.cpp index 9c46f96c42..7bfae47cb8 100644 --- a/clang/test/Verify/ast_print_contracts.cpp +++ b/clang/test/Verify/ast_print_contracts.cpp @@ -2,6 +2,9 @@ // RUN: %clang_cc1 -std=c++17 -fverify-contracts -ast-print -ast-dump-filter sq %s | FileCheck %s --check-prefix=SPEC // RUN: %clang_cc1 -std=c++17 -fverify-contracts -ast-print -ast-dump-filter max_index %s | grep -v '^Printing' > %t.cpp // RUN: %clang_cc1 -std=c++17 -fverify-contracts -fsyntax-only %t.cpp +// RUN: %clang_cc1 -std=c++17 -fverify-contracts -ast-print -ast-dump-filter sum_range %s | FileCheck %s --check-prefix=RANGE +// RUN: %clang_cc1 -std=c++17 -fverify-contracts -ast-print -ast-dump-filter sum_range %s | grep -v '^Printing' > %t.range.cpp +// RUN: %clang_cc1 -std=c++17 -fverify-contracts -fsyntax-only %t.range.cpp // // Printed declarations carry their contracts, written qualified, as clangd's // hover shows them; the printed code parses back. @@ -44,3 +47,21 @@ int max_index(const int *a, int n) cv::spec int sq(int x) cv::recommends(x >= 0) cv::post(cv::result >= 0) { return x * x; } + +// A range-based for loop's clauses, its position written as the construct. +// RANGE: for (T v : a) +// RANGE-NEXT: cppverify::invariant(0 <= s && s <= 10 * cppverify::index) +// RANGE-NEXT: cppverify::decreases(4 - cppverify::index) +// RANGE: template<> int sum_range(const int (&a)[4]) { +// RANGE: for (int v : a) +// RANGE-NEXT: cppverify::invariant(0 <= s && s <= 10 * cppverify::index) +template +T sum_range(const T (&a)[4]) { + T s = 0; + for (T v : a) + cv::invariant(0 <= s && s <= 10 * cv::index) + cv::decreases(4 - cv::index) + s = s + v; + return s; +} +int use_sum_range(const int (&a)[4]) { return sum_range(a); } From 9b418be249b778a24feabff5e557ddaa3545b228 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:28 +0530 Subject: [PATCH 054/119] test: Index the uses in a range-based for loop's contract. --- clang/test/Index/Core/cppverify-references.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/test/Index/Core/cppverify-references.cpp b/clang/test/Index/Core/cppverify-references.cpp index 06711d20d8..0987191d3c 100644 --- a/clang/test/Index/Core/cppverify-references.cpp +++ b/clang/test/Index/Core/cppverify-references.cpp @@ -31,3 +31,15 @@ int f(int n) // CHECK-DAG: [[@LINE-2]]:7 | function/C | check | {{.*}} | Ref,RelCont return sq(n); } + +int range(const int (&a)[3]) { + int s = 0; + for (int v : a) + cv::invariant(s >= 0 || cv::index > 0) +// CHECK-DAG: [[@LINE-1]]:19 | variable(local)/C | s | {{.*}} | Ref,Read,RelCont +// CHECK-DAG: [[@LINE-2]]:9 | function/C | invariant | {{.*}} | Ref,RelCont +// CHECK-DAG: [[@LINE-3]]:33 | enumerator/C | index | {{.*}} | Ref,RelCont +// CHECK-NOT: __cppverify_index + s = s + v; + return s; +} From 4e1517b3f2482f440f9c39d76778c0a786ad201b Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:29 +0530 Subject: [PATCH 055/119] test: A range-based for loop's clauses as libclang cursors. --- clang/test/Index/cppverify-cursors.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/test/Index/cppverify-cursors.cpp b/clang/test/Index/cppverify-cursors.cpp index da1d9117cf..b18a34c89c 100644 --- a/clang/test/Index/cppverify-cursors.cpp +++ b/clang/test/Index/cppverify-cursors.cpp @@ -35,3 +35,15 @@ int f(int n) // CHECK: cppverify-cursors.cpp:16:3: UnexposedStmt= Extent=[16:3 - 16:39] // CHECK: cppverify-cursors.cpp:16:31: DeclRefExpr=down:6:21 Extent=[16:31 - 16:35] // CHECK: cppverify-cursors.cpp:16:37: IntegerLiteral= Extent=[16:37 - 16:38] + +int g(const int (&a)[2]) { + int s = 0; + for (int v : a) + cppverify::invariant(s >= 0) + s = v; + return s; +} + +// The clauses of a range-based for loop are children of the loop. +// CHECK: cppverify-cursors.cpp:[[@LINE-7]]:3: CXXForRangeStmt= Extent=[[[@LINE-7]]:3 - [[@LINE-5]]:10] +// CHECK: cppverify-cursors.cpp:[[@LINE-7]]:26: BinaryOperator=>= Extent=[[[@LINE-7]]:26 - [[@LINE-7]]:32] From 14c1407011a224042d12838bbfe803d940e12c8d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:30 +0530 Subject: [PATCH 056/119] docs: List cppverify::index. --- website/source/language/syntax.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/language/syntax.rst b/website/source/language/syntax.rst index fc5469e9dc..524f11f4d4 100644 --- a/website/source/language/syntax.rst +++ b/website/source/language/syntax.rst @@ -203,6 +203,9 @@ Full table: * - ``cppverify::result`` - In ``cppverify::post`` - Return value + * - ``cppverify::index`` + - In a range-based ``for`` loop's contract + - Number of elements before the current one * - ``cppverify::spec T f(...)`` - Decl - Spec function From 3e61aa697c27481cdf98320918a69816083e735a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:30 +0530 Subject: [PATCH 057/119] docs: Describe range-based for loops and cppverify::index. --- website/source/language/functions-loops.rst | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index 1a4844ed2c..b8faad1890 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -636,6 +636,55 @@ rest of the loop, and a ``continue`` goes to the condition, where the invariant must first hold. ``cppverify::ghost`` code cannot leave an executable loop. +A range-based ``for`` loop over an array (a local array, an array member, +a constant table) visits its elements in order. Its contract names the +position, ``cppverify::index``, the number of elements before the current +one, from 0 to the size; without a ``cppverify::decreases`` its measure is +the number of elements left, since such a loop ends. The loop variable is a +copy of the element or a reference to it: + +.. code-block:: cpp + + struct Ring { + int buf[8]; + int head; + }; + + int total(const Ring *r) + cv::pre(r != nullptr) + cv::pre(cv::forall(k, 0, 8, 0 <= r->buf[k] && r->buf[k] <= 10)) + cv::post(0 <= cv::result && cv::result <= 80) + { + int s = 0; + for (const int &v : r->buf) + cv::invariant(0 <= s && s <= 10 * cv::index) + s = s + v; + return s; + } + + void clear(Ring *r) + cv::pre(r != nullptr) + cv::modifies(r->buf[0 : 8]) + cv::post(cv::forall(k, 0, 8, r->buf[k] == 0)) + { + for (int &x : r->buf) + cv::invariant(cv::forall(k, 0, cv::index, r->buf[k] == 0)) + x = 0; + } + +.. code-block:: text + + Verified: total [backend=z3] + Verified: clear [backend=z3] + +C++ evaluates the range once and the verifier locates each element again, so +a range located through a call, a pointer read from memory, or a variable +the body may change is rejected: copy the pointer or index into a variable +the body does not change. The contract holds at the head, where the loop +variable is not declared, so it cannot name it. A range over a class, +through its ``begin`` and ``end``, an array of arrays, and a global array +that is not a constant table are not supported yet. + A condition may declare a variable, ``while (int k = next(i))``: it is declared, and its value tested, at every test, so a loop's contract cannot name it. A ``for`` loop without a condition repeats until a ``break`` or From 24dbc5ccd5fbe5d7ae04ed1f412a1b3a1929e175 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:31 +0530 Subject: [PATCH 058/119] docs: List range-based for loops over arrays as supported. --- website/source/language/limitations.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index 96ef7e7129..d05a669bd3 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -202,10 +202,13 @@ C++ feature boundaries - ``if`` (including C++17 initializer), ``while``, ``for`` (with or without a condition), contracted ``do`` loops, condition declarations, ``switch`` (fallthrough, a ``default`` anywhere, GNU case ranges, an - init statement), blocks, ``return``, ``break``, and ``continue`` + init statement), range-based ``for`` over an array (with + ``cppverify::index``), blocks, ``return``, ``break``, and ``continue`` anywhere, and comma expressions as statements. - - Range-``for``, ``goto``, labels, and a ``case`` label inside another - statement of its switch (Duff's device). An + - A range-based ``for`` over a class (through ``begin`` and ``end``), + over an array of arrays, or over a global array that is not a constant + table, ``goto``, labels, and a ``case`` label inside another statement of its + switch (Duff's device). An expression or statement nested more than 256 operations deep (a sum of 300 terms, say) is reported where it is, and the file stops: ``error: total: the expression at 4:276 nests more than 256 From d0f9605e80bdd50419421c418019ab17a7b9a2b8 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:32 +0530 Subject: [PATCH 059/119] docs: Note range-based for loops in the 0.2.0 release notes. --- website/source/release-notes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index 33aec573e2..e1e707fe81 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -413,6 +413,13 @@ that may write what another operand reads is an error at the expression, and an assignment's right operand is evaluated before its target, as C++17 says (:doc:`language/functions-loops`). +**Range-based for loops.** ``for (int v : a)`` stopped its function +(``unsupported statement: CXXForRangeStmt``). A range-based ``for`` loop over +an array (a local array, an array member, a constant table) is now verified, +its variable a copy of each element or a reference to it, and its contract +names the position with the new construct ``cppverify::index``; without a +measure it decreases the elements left (:doc:`language/functions-loops`). + **Lookup tables.** A read of a global or ``static`` array, or of a string literal, was an error (``the global array 'table' is unsupported``), which ruled out the tables at the heart of CRC, base64, and UTF-8 code. An element From 6687b010d4f39641c4025e8979fe6cd206489de7 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:33 +0530 Subject: [PATCH 060/119] docs: Design range-based for loops and cppverify::index. --- docs/DESIGN.md | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index dea136c8de..3ee726e776 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -50,6 +50,7 @@ implicitly; without it, the constructs are not recognized. | `cppverify::choose(i, [lo, hi,] expr)` | Expression | Some integer satisfying `expr` (Hilbert ε) | | `cppverify::old(expr)` | Inside `cppverify::post` or `cppverify::invariant` | Value of `expr` at function entry | | `cppverify::result` | Inside `cppverify::post` | Return value of the enclosing function | +| `cppverify::index` | Inside a range-based `for` loop's contract | Number of elements before the current one | | `cppverify::seq`, `set`, `multiset`, `map` | `` | Mathematical collections for specifications and ghost code | ## Function Contracts: pre / post / modifies / aliases / recommends @@ -316,6 +317,37 @@ for (int i = 0; i < n; i = i + 1) through a local array, a local record, or a `new` pointer writes that object, and one through a reference to a local writes the cell it binds. +A range-based `for` loop over an array (a local array, an array member, a +constant table) visits its elements in order, and its contract names the +position, `cppverify::index`, the number of elements before the current one, +from 0 to the size (decision Q9 of the 0.2 plan; Verus names the same +position `iter.index()` in the invariants of `for x in iter: v`): + + + +```cpp +for (const int &v : r->buf) + cppverify::invariant(0 <= s && s <= 10 * cppverify::index) + s = s + v; +``` + +The converter lowers it to a loop over a counter, `while (i < N) { v = +range[i]; body; i = i + 1; }`, with the invariant `0 <= i <= N` before the +user's and, without a `cppverify::decreases`, the measure `N - i`, since a +loop over an array ends. This is Clang's own desugaring, `for (auto __begin += range, __end = range + N; __begin != __end; ++__begin)`, with `__begin` +written as `range + i`: the position is the one value an invariant needs, +and no pointer into a local array is formed. The loop variable is the +element (a copy, converted to its type) or bound to it (a reference). C++ +evaluates the range once; the verifier locates the element again at each +iteration, so a range located through a call, a pointer read from memory +(a global's value included), or a variable the body may change fails +closed. The contract holds at the head, where the loop variable is not +declared, so naming it there is an error. A range over a class (its `begin` +and `end`), an array of arrays, an array parameter (`const int (&a)[4]`, +rejected as a type), and a global array that is not a constant table are +not supported yet. + `do` loops place the clauses between the trailing condition and its semicolon: @@ -2035,7 +2067,7 @@ cppverify::proof void sum_concat(seq s, seq t) ### Construct recognition The lexer reserves nothing. `clang/include/clang/Basic/CppVerifyConstructs.def` -lists the 28 construct words and the positions each may take (function +lists the 29 construct words and the positions each may take (function clause, loop clause, class member, declaration specifier, statement, expression). At each position the parser calls `tryAnnotateCppVerify` (`clang/lib/Parse/ParseCppVerify.cpp`), which looks ahead for @@ -2098,7 +2130,7 @@ in place and records it in an `ASTContext` side table. | Existing Node | New Data | |---|---| | FunctionDecl (via ASTContext side table) | preconditions, postconditions, modifies, aliases, recommends, isSpec, isProof, decreases, behavior checks | -| WhileStmt / ForStmt / DoStmt | invariants, decreases, modifies | +| WhileStmt / ForStmt / DoStmt / CXXForRangeStmt | invariants, decreases, modifies; for a range-based `for`, the implicit variable `cppverify::index` names | | RecordDecl | type_invariants | | VarDecl | ghost marker | @@ -2114,7 +2146,7 @@ a one-line hook. | Statements (`ghost`, `check`, `calc`, `reveal_with_fuel`, `hide`, `reveal`) | `ParseCppVerifyStatement` | `ParseStatementOrDeclarationAfterAttributes` | | `cppverify::spec` / `cppverify::proof` | `ParseCppVerifySpecifier` | `ParseDeclarationSpecifiers` | | `cppverify::type_invariant(...)` | `ParseTypeInvariant` | `ParseCXXMemberSpecification` | -| Expressions (`forall`, `exists`, `choose`, `old`, `result`, `trigger`) | `ParseCppVerifyExpression` | `ParseCastExpression` | +| Expressions (`forall`, `exists`, `choose`, `old`, `result`, `index`, `trigger`) | `ParseCppVerifyExpression` | `ParseCastExpression` | ### Sema Rules From ebc45f2c3691b103b021437d3bd031c83ed948fa Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:34 +0530 Subject: [PATCH 061/119] docs: Describe how a range-based for loop is lowered and where its position lives. --- docs/ARCHITECTURE.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 11925a64e6..fc849ae21f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -764,6 +764,21 @@ state and ends the path; the frontend emits a `for` increment before it. BMC unrolling lowers `break` and `continue` to flag assignments that guard the remaining statements and the next iteration. +**Range-based for loops** (`ASTConverter::convertRangeFor`) lower a loop +over an array to a counter, `__index_N = 0; while (__index_N < N) +invariant(0 <= __index_N <= N, ...) { var = element; body; __index_N += 1 +}`, the increment also before each `continue` +(`EnclosingLoop::IncrementStmt`). `cppverify::index` names the counter while +the contract is converted: the parser declares it as an implicit variable +with the contract (`LoopContractInfo::Index`), so its uses are ordinary +`DeclRefExpr`s, and template instantiation maps it like a quantifier's +binder (`TransformContractBinder`). The +element is the place of the range (`objectPlace`) stepped by the counter, +or the read of a constant table; `rangeMayMove` rejects a range whose +location the body may change, and a contract that names the loop variable +is rejected, since it holds at the head, where the variable is not +declared. + **Exits the frontend lowers.** VCR's `break` and `continue` leave a `VWhileStmt`. Two exits have no loop to leave and are lowered to flags by `lowerExits` (`ASTConverter.cpp`), BMC's rule applied once: a `do` loop's @@ -2903,7 +2918,7 @@ code: - `RecursiveASTVisitor` (and `DynamicRecursiveASTVisitor`) visits function contracts and the construct references written in a function from `TraverseFunctionHelper`, loop contracts from the `while`/`for`/`do` - traversals (found through the `ASTContext` the function's traversal sets), + and range-based `for` traversals (found through the `ASTContext` the function's traversal sets), and type invariants from the `CXXRecordDecl` traversal. clangd's hover, go-to-definition, rename, document highlights, semantic highlighting, and inlay hints, clang-tidy's matchers, and the parent map all build on it. @@ -2923,7 +2938,8 @@ code: children. - `DeclPrinter` prints a function's clauses, one per line, after its declarator, and `cppverify::spec`/`cppverify::proof` as its specifier; - `StmtPrinter` prints loop clauses and the constructs qualified. clangd's + `StmtPrinter` prints loop clauses and the constructs qualified, a range-based + `for` loop's position as `cppverify::index`. clangd's hover on a function therefore shows its contract, and `-ast-print` output parses again. - Each construct written in a function is recorded as a `DeclRefExpr` to its From c6ed751ac02eec96961191740d2a314d60b80cf4 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 13:52:35 +0530 Subject: [PATCH 062/119] docs: Loops over an array in the book. --- .../book/part-ii/ch12-loops-in-practice.rst | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/website/source/book/part-ii/ch12-loops-in-practice.rst b/website/source/book/part-ii/ch12-loops-in-practice.rst index 9512f8866b..4a8cb9583f 100644 --- a/website/source/book/part-ii/ch12-loops-in-practice.rst +++ b/website/source/book/part-ii/ch12-loops-in-practice.rst @@ -230,6 +230,51 @@ a value from before the loop: } } +Loops over an array +------------------- + +A range-based ``for`` loop over an array visits its elements in order. Its +contract holds at the loop's head, where the loop variable is not declared +yet, so it names the position instead: ``cppverify::index``, the number of +elements already visited, 0 at the first test and the size at the end. A +property of the elements so far is a quantifier up to it: + +.. code-block:: cpp + + struct Samples { + int values[16]; + }; + + int largest(const Samples *s) + cv::pre(s != nullptr) + cv::post(cv::forall(k, 0, 16, s->values[k] <= cv::result)) + cv::post(cv::exists(k, 0, 16, s->values[k] == cv::result)) + { + int best = s->values[0]; + for (int v : s->values) + cv::invariant(cv::forall(k, 0, cv::index, s->values[k] <= best)) + cv::invariant(cv::exists(k, 0, 16, s->values[k] == best)) + { + if (v > best) + best = v; + } + return best; + } + +.. code-block:: text + + Verified: largest [backend=z3] + +The loop needs no ``cppverify::decreases``: without one, its measure is the +number of elements left. Its variable may be a reference, which writes the +element (``for (int &x : a) x = 0;``), and its range a local array, an array +member reached through a pointer, a constant table, or a string literal, +whose last element is the terminating zero. The verifier finds the element +again at each iteration, where C++ evaluates the range once, so a range found +through a call, a pointer read from memory, or a variable the body changes +is rejected; copy the pointer into a variable the body leaves alone. A loop +over a class, through its ``begin`` and ``end``, is not supported yet. + Two loops, one name ------------------- From e90b81a5dc76f5c1a0717fa2d677c3436234fcb3 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:36 +0530 Subject: [PATCH 063/119] verify: Name unsupported constructs in C++ terms, report each once, and take expressions that evaluate nothing at run time as their values. --- clang/lib/Verify/Frontend/ASTConverter.cpp | 66 ++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 77503e6d67..f4a41723d3 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -2276,6 +2276,37 @@ static bool hasExtentMarker(const FunctionContractInfo &FCI, return F.Found; } +/// What a statement or expression the verifier does not represent is, in +/// C++'s terms. +static std::string unsupportedConstruct(const Stmt *S) { + switch (S->getStmtClass()) { + case Stmt::GotoStmtClass: + return "goto is unsupported"; + case Stmt::IndirectGotoStmtClass: + return "a computed goto is unsupported"; + case Stmt::LabelStmtClass: + return "labels are unsupported"; + case Stmt::AddrLabelExprClass: + return "the address of a label is unsupported"; + case Stmt::CXXThrowExprClass: + return "throw is unsupported: exceptions are not modeled"; + case Stmt::CXXTryStmtClass: + return "try is unsupported: exceptions are not modeled"; + case Stmt::GCCAsmStmtClass: + case Stmt::MSAsmStmtClass: + return "inline assembly is unsupported"; + case Stmt::StmtExprClass: + return "a statement expression is unsupported"; + case Stmt::CompoundLiteralExprClass: + return "a compound literal is unsupported"; + case Stmt::LambdaExprClass: + return "lambdas are unsupported"; + default: + return std::string(isa(S) ? "this expression" : "this statement") + + " is unsupported (" + S->getStmtClassName() + ")"; + } +} + static bool mentionsThis(const Stmt *S) { if (!S) return false; @@ -4733,6 +4764,12 @@ ASTConverter::convertTableRead(const ArraySubscriptExpr *AS) { } std::string ASTConverter::arrayDecayError(const Expr *Array) const { + if (isa(Array->IgnoreParens())) + return "a pointer into a string literal is unsupported; read its " + "characters by index"; + if (const auto *Name = dyn_cast(Array->IgnoreParens())) + return std::string(Name->getIdentKindName()) + + " as a pointer is unsupported"; const auto *M = dyn_cast_or_null(peelObjectExpr(Array)); const std::string Member = M ? M->getMemberDecl()->getNameAsString() : std::string(); @@ -5986,8 +6023,12 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { if (const auto *Default = dyn_cast(E)) return convertExpr(Default->getExpr()); // A constant Clang evaluates (a consteval call, an if constexpr - // condition) and a character literal are their values. - if (isa(E) && + // condition), a character literal, and an expression that evaluates + // nothing at run time (noexcept, a requires-expression, a concept, a type + // trait, offsetof, __builtin_LINE) are their values. + if (isa(E) && E->getType()->isIntegralOrEnumerationType()) { Expr::EvalResult Result; if (E->EvaluateAsInt(Result, Ctx)) { @@ -6761,9 +6802,10 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { error(CurrentFn->Name + ": call to function without a verification contract: " + functionName(Callee)); + return nullptr; } } - error(CurrentFn->Name + ": unsupported expression: " + E->getStmtClassName()); + error(CurrentFn->Name + ": " + unsupportedConstruct(E)); return nullptr; } @@ -10005,16 +10047,20 @@ ASTConverter::convertStmtBody(const Stmt *S) { continue; if (appendRecordCopy(Init, VD, VD->getBeginLoc(), Out)) continue; - error(CurrentFn->Name + ": aggregate initialization and copying are " - "unsupported"); + if (isa(Init)) + error(Init->getSourceRange(), + CurrentFn->Name + ": lambdas are unsupported"); + else + error(CurrentFn->Name + ": aggregate initialization and copying are " + "unsupported"); + markInitialized(VD); continue; } - auto Val = materialize(convertExpr(VD->getInit()), VD->getType()); - if (Val) { + // A rejected initializer is reported once, not again at each use. + if (auto Val = materialize(convertExpr(VD->getInit()), VD->getType())) Out.push_back(std::make_unique( valueName(VD), std::move(Val), VD->getBeginLoc())); - markInitialized(VD); - } + markInitialized(VD); } return Out; } @@ -10022,6 +10068,6 @@ ASTConverter::convertStmtBody(const Stmt *S) { appendDiscarded(E, Out); return Out; } - error(CurrentFn->Name + ": unsupported statement: " + S->getStmtClassName()); + error(CurrentFn->Name + ": " + unsupportedConstruct(S)); return Out; } From b1cdcfddd792671cec1ab17b5f7a50383e821e1b Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:36 +0530 Subject: [PATCH 064/119] test: Expect throw named as C++ names it. --- clang/test/Verify/located_errors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Verify/located_errors.cpp b/clang/test/Verify/located_errors.cpp index 760c1b57ab..afc9b9cdb6 100644 --- a/clang/test/Verify/located_errors.cpp +++ b/clang/test/Verify/located_errors.cpp @@ -13,10 +13,10 @@ int pick(int x) { if (x < 0) throw x; return 0; } -// CHECK: {{.*}}located_errors.cpp:[[@LINE-2]]:14: error: pick: unsupported statement: CXXThrowExpr +// CHECK: {{.*}}located_errors.cpp:[[@LINE-2]]:14: error: pick: throw is unsupported: exceptions are not modeled // CHECK-NEXT: {{^ +}}[[@LINE-3]] | if (x < 0) throw x; return 0; // CHECK-NEXT: {{^ +}}| ^~~~~~~ -// JSON: {"function":"pick","message":"pick: unsupported statement: CXXThrowExpr","reason":"construct.unsupported","schema":"cppverify.diagnostic/1","severity":"error","source":{"column":14,"end_column":20,"end_line":[[#@LINE-5]],"file":"{{.*}}located_errors.cpp","line":[[#@LINE-5]]},"status":"unresolved"} +// JSON: {"function":"pick","message":"pick: throw is unsupported: exceptions are not modeled","reason":"construct.unsupported","schema":"cppverify.diagnostic/1","severity":"error","source":{"column":14,"end_column":20,"end_line":[[#@LINE-5]],"file":"{{.*}}located_errors.cpp","line":[[#@LINE-5]]},"status":"unresolved"} struct Shape { virtual int get(int x) { cv::check(x >= 0 || x < 0); return x; } From 56033dc5b5c1fe130973f7523e7d85e0a38a0189 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:36 +0530 Subject: [PATCH 065/119] test: Expect throw named as C++ names it in every output format. --- clang/test/Verify/suite/per_function_errors.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/test/Verify/suite/per_function_errors.test b/clang/test/Verify/suite/per_function_errors.test index c2834b9dc9..8ced5b94cc 100644 --- a/clang/test/Verify/suite/per_function_errors.test +++ b/clang/test/Verify/suite/per_function_errors.test @@ -17,11 +17,11 @@ # RUN: not %cpp-verify --backend=lean --lean-out=%t.lean %S/Inputs/per_function_errors.cpp -- 2>&1 \ # RUN: | FileCheck %s --check-prefix=LEAN -TEXT: per_function_errors.cpp:11:14: error: pick: unsupported statement: CXXThrowExpr +TEXT: per_function_errors.cpp:11:14: error: pick: throw is unsupported: exceptions are not modeled TEXT: per_function_errors.cpp:27:37: error: lookup: the global array 'table' is unsupported -TEXT: per_function_errors.cpp:41:14: error: choice: unsupported statement: CXXThrowExpr -TEXT: per_function_errors.cpp:64:14: error: zero_lemma: unsupported statement: CXXThrowExpr -TEXT: per_function_errors.cpp:79:14: error: step: unsupported statement: CXXThrowExpr +TEXT: per_function_errors.cpp:41:14: error: choice: throw is unsupported: exceptions are not modeled +TEXT: per_function_errors.cpp:64:14: error: zero_lemma: throw is unsupported: exceptions are not modeled +TEXT: per_function_errors.cpp:79:14: error: step: throw is unsupported: exceptions are not modeled TEXT-NOT: {{(Verified|Unresolved): (spec [a-z]+: )?(pick|lookup|choice|zero_lemma|step) }} TEXT: Unresolved: uses_pick [backend=z3] [reason=callee.contract] (relies on the contract of pick (it uses a construct the verifier does not support), which is not established) TEXT-NEXT: per_function_errors.cpp:36:3: Unresolved: uses_lookup [backend=z3] [reason=construct.unsupported] (the verifier cannot model a construct here: a call of lookup, whose contract is not available) @@ -34,7 +34,7 @@ TEXT-NEXT: Verified: spec axiom: twice TEXT-NEXT: Verified: uses_twice [backend=z3] TEXT-NOT: {{.}} -JSON: {"function":"pick","message":"pick: unsupported statement: CXXThrowExpr","reason":"construct.unsupported",{{.*}}"line":11},"status":"unresolved"} +JSON: {"function":"pick","message":"pick: throw is unsupported: exceptions are not modeled","reason":"construct.unsupported",{{.*}}"line":11},"status":"unresolved"} JSON: {"function":"lookup","message":"lookup: the global array 'table' is unsupported","reason":"construct.unsupported",{{.*}}"status":"unresolved"} JSON-NEXT: {"function":"lookup","message":"lookup: unsupported expression in pre",{{.*}}"status":"error"} JSON: {"backend":"z3","function":"uses_pick",{{.*}}"reason":"callee.contract",{{.*}}"status":"unresolved"} @@ -44,7 +44,7 @@ BMC: Unresolved: uses_pick [backend=bmc, bound=0] [reason=callee.contract] BMC: Verified: inc [backend=bmc, bound=0] BMC: Verified: uses_twice [backend=bmc, bound=0] -LEAN: per_function_errors.cpp:11:14: error: pick: unsupported statement: CXXThrowExpr +LEAN: per_function_errors.cpp:11:14: error: pick: throw is unsupported: exceptions are not modeled LEAN: Exported: lean obligation: uses_pick LEAN: per_function_errors.cpp:49:5: Unresolved: uses_choice [reason=construct.unsupported] LEAN: Exported: lean obligation: inc From ad70b6aad0c0adddea98f186d7703faf9cad42b4 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:37 +0530 Subject: [PATCH 066/119] test: Each unsupported construct named once, at its location. --- clang/test/Verify/unsupported_constructs.cpp | 121 +++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 clang/test/Verify/unsupported_constructs.cpp diff --git a/clang/test/Verify/unsupported_constructs.cpp b/clang/test/Verify/unsupported_constructs.cpp new file mode 100644 index 0000000000..feaa47549c --- /dev/null +++ b/clang/test/Verify/unsupported_constructs.cpp @@ -0,0 +1,121 @@ +// RUN: not %cpp-verify --lower-only %s -- -std=c++20 -fopenmp 2>&1 | FileCheck %s --implicit-check-not=error: +// +// Each construct the verifier does not represent is named as C++ names it, at +// its location, once: a use of a variable whose initializer was rejected +// reports nothing more, and neither does a call that was. + +namespace cv = cppverify; + +int jump(int x) + cv::post(true) +{ + if (x) + goto out; + x = 1; +out: + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-5]]:5: error: jump: goto is unsupported +// CHECK: unsupported_constructs.cpp:[[@LINE-4]]:1: error: jump: labels are unsupported + +int computed(int x) + cv::post(true) +{ + void *target = &&done; + goto *target; +done: + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-5]]:18: error: computed: the address of a label is unsupported +// CHECK: unsupported_constructs.cpp:[[@LINE-5]]:3: error: computed: a computed goto is unsupported +// CHECK: unsupported_constructs.cpp:[[@LINE-5]]:1: error: computed: labels are unsupported + +int raise(int x) + cv::post(true) +{ + if (x < 0) + throw x; + return x > 0 ? x : throw 0; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:5: error: raise: throw is unsupported: exceptions are not modeled +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:22: error: raise: throw is unsupported: exceptions are not modeled + +int guarded(int x) + cv::post(true) +{ + try { + x = 1; + } catch (...) { + } + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-6]]:3: error: guarded: try is unsupported: exceptions are not modeled + +int assembly(int x) + cv::post(true) +{ + asm("nop"); + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:3: error: assembly: inline assembly is unsupported + +int statement_expression(int x) + cv::post(true) +{ + int y = ({ int z = x; z; }); + return y + 1; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:11: error: statement_expression: a statement expression is unsupported + +int compound(int x) + cv::post(true) +{ + int y = (int){3}; + return x + y; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:11: error: compound: a compound literal is unsupported + +int closure(int x) + cv::post(true) +{ + auto twice = [](int y) { return 2 * y; }; + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:16: error: closure: lambdas are unsupported + +int text(int x) + cv::post(true) +{ + const char *s = "abc"; + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:19: error: text: a pointer into a string literal is unsupported; read its characters by index + +int name(int x) + cv::post(true) +{ + const char *s = __func__; + return x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-3]]:19: error: name: __func__ as a pointer is unsupported + +int helper(int x); + +int uncontracted(int x) + cv::post(true) +{ + return helper(x) + 1; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-2]]:10: error: uncontracted: call to function without a verification contract: helper + +// A construct without a name of its own is named by Clang's class. +int simd_sum(int x) + cv::post(true) +{ + int s = 0; +#pragma omp simd + for (int i = 0; i < 4; i = i + 1) + s = s + 1; + return s + x; +} +// CHECK: unsupported_constructs.cpp:[[@LINE-5]]:1: error: simd_sum: this statement is unsupported (OMPSimdDirective) From 5d7cd36561fd8d1525a5504a75878407cacd730e Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:37 +0530 Subject: [PATCH 067/119] test: Expressions that evaluate nothing at run time, with a wrong twin. --- .../Verify/suite/constant_expressions.cpp | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 clang/test/Verify/suite/constant_expressions.cpp diff --git a/clang/test/Verify/suite/constant_expressions.cpp b/clang/test/Verify/suite/constant_expressions.cpp new file mode 100644 index 0000000000..3d4507fffa --- /dev/null +++ b/clang/test/Verify/suite/constant_expressions.cpp @@ -0,0 +1,35 @@ +// RUN: not %cpp-verify --jobs=1 %s -- -std=c++20 2>&1 | FileCheck %s +// +// An expression that evaluates nothing at run time (noexcept, a +// requires-expression, a concept, a type trait, offsetof, __builtin_LINE) is +// the value Clang computes for it. Every correct function has a wrong twin. + +#include + +namespace cv = cppverify; + +struct Pair { + int first; + int second; +}; + +template +concept Small = sizeof(T) <= 4; + +int facts(int x) + cv::post(cv::result == 6) +{ + return noexcept(x + 1) + requires { x + 1; } + Small + + __is_same(int, int) + (offsetof(Pair, second) == 4) + + (__builtin_LINE() == __LINE__); +} +// CHECK-DAG: Verified: facts [backend=z3] + +int facts_wrong(int x) + cv::post(cv::result == 6) +{ + return noexcept(x + 1) + requires { x + 1; } + Small + + __is_same(int, long) + (offsetof(Pair, second) == 0) + + (__builtin_LINE() == __LINE__ + 1); +} +// CHECK-DAG: error: verification failed: facts_wrong [{{.*}}::postcondition@{{.*}}result [ssa=__result_1] [type=i32] = 2{{.*}}[reason=counterexample] From ae4920ed10624973247b935acf66e18ed8b956e5 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:37 +0530 Subject: [PATCH 068/119] docs: Describe how unsupported constructs are named, once. --- website/source/language/tooling.rst | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/website/source/language/tooling.rst b/website/source/language/tooling.rst index 3cc2b75747..73f339824b 100644 --- a/website/source/language/tooling.rst +++ b/website/source/language/tooling.rst @@ -104,7 +104,7 @@ the file is verified, and ``cpp-verify`` exits with status 1 .. code-block:: text - pick.cpp:4:14: error: pick: unsupported statement: CXXThrowExpr + pick.cpp:4:14: error: pick: throw is unsupported: exceptions are not modeled 4 | if (x < 0) throw x; | ^~~~~~~ Verified: bump [backend=z3] @@ -134,6 +134,21 @@ a function's first error carries its verdict and the range, here error stopped the file, so that no function of it got a verdict, and named only the function: ``error: pick: unsupported statement: CXXThrowExpr``. +The message names the construct as C++ does: ``goto``, labels, a computed +``goto``, ``throw`` and ``try``, inline assembly, a statement expression, a +compound literal, a lambda, a pointer into a string literal or to +``__func__``. A construct without a name of its own is ``this expression is +unsupported`` or ``this statement is unsupported``, with Clang's class name +in parentheses for a bug report: an OpenMP directive is ``this statement is +unsupported (OMPSimdDirective)``. Each is reported once: a variable whose +initializer was rejected, and a call that was, report nothing more where +they are used. Before 0.2 the message gave Clang's class name alone +(``unsupported expression: StmtExpr``), a lambda was ``aggregate +initialization and copying are unsupported``, a string literal used as a +pointer was ``array-to-pointer decay of automatic storage is unsupported``, +and each use of a variable whose initializer was rejected added ``read of +uninitialized local value``. + Stopping a run with Ctrl-C ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 0a8b98666998da491b92705effd48df1aa4f27ee Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:38 +0530 Subject: [PATCH 069/119] docs: Expressions that evaluate nothing at run time are their values. --- website/source/language/functions-loops.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index b8faad1890..3ffc9c7bbe 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -32,7 +32,10 @@ unsupported``. A default argument is evaluated at the call, with the checks of its operations there. A ``consteval`` call, like any constant Clang evaluates, is -its value. ``__builtin_expect(e, c)`` is ``e``. ``static_assert``, type +its value, and so is an expression that evaluates nothing at run time: +``noexcept(e)``, a requires-expression, a concept such as ``Small``, a +type trait such as ``__is_same(T, U)``, ``offsetof``, and +``__builtin_LINE()``. ``__builtin_expect(e, c)`` is ``e``. ``static_assert``, type aliases, using-declarations, and local classes and enumerations declare names and do nothing at run time. From 4b2c799b1059071a691f715f071058a06156a0e2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:38 +0530 Subject: [PATCH 070/119] docs: List the expressions that evaluate nothing at run time as supported. --- website/source/language/limitations.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index d05a669bd3..db8af88024 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -118,7 +118,9 @@ C++ feature boundaries not reached), ``[[assume(e)]]`` and ``__builtin_assume(e)`` (proved, then assumed), discarded values (``x + 1;``, ``c && f(x);``), character literals (``'0'``, ``L'A'``), constants Clang evaluates (a - ``consteval`` call, an ``if constexpr`` condition), template value + ``consteval`` call, an ``if constexpr`` condition, ``noexcept(e)``, a + requires-expression, a concept, a type trait, ``offsetof``, + ``__builtin_LINE()``), template value parameters, default arguments and default member initializers, ``__builtin_expect``, a record returned from an initializer list (``return Pt{a, b};``), structured bindings that copy a record, and From d94e39fd701fc2ccd96e9b36c3fd1a7952092e19 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:39 +0530 Subject: [PATCH 071/119] docs: Note named errors and constant expressions in the 0.2.0 release notes. --- website/source/release-notes.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index e1e707fe81..b7e2f1f5aa 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -451,6 +451,12 @@ in order, and a structured binding a copy whose fields the bindings name (:doc:`language/integers`, :doc:`language/structs`, :doc:`language/functions-loops`). +**Expressions that evaluate nothing at run time.** ``noexcept(e)``, a +requires-expression, a concept, a type trait such as ``__is_same(T, U)``, +``offsetof``, and ``__builtin_LINE()`` stopped their function +(``unsupported expression: CXXNoexceptExpr``). Each is now the value Clang +computes for it (:doc:`language/functions-loops`). + **Pointer steps and reused names.** ``++p``, ``p--``, ``p += n``, and ``p -= n`` on a pointer were errors (``pointer increment/decrement is unsupported``, ``pointer compound assignment is unsupported``), and so was a @@ -520,6 +526,16 @@ SwitchStmt``. It now gives the file, line, and column, and shows the line with a caret under the construct, as a compiler error does; its JSON record carries the range (:doc:`language/tooling`). +**Errors name the construct, once.** The message gave Clang's class name +(``unsupported statement: CXXThrowExpr``), called a lambda ``aggregate +initialization and copying``, and called a string literal used as a pointer +``array-to-pointer decay of automatic storage``; each use of a variable whose +initializer was rejected added ``read of uninitialized local value``, and a +call without a contract was reported twice. The message now says what C++ +calls the construct (``throw is unsupported: exceptions are not modeled``, +``lambdas are unsupported``), and each is reported once +(:doc:`language/tooling`). + **An unsupported construct costs its function alone.** One construct the verifier does not represent, such as a ``switch``, stopped the file: no function of it was verified. Now that function is ``Unresolved`` with reason From e0430bf2dd91d79d9e6ebce7675ddd126e399fce Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:39 +0530 Subject: [PATCH 072/119] docs: Describe how conversion errors name constructs and are raised once. --- docs/ARCHITECTURE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fc849ae21f..ddbf06fc9c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2384,6 +2384,12 @@ expression being converted (`ASTConverter::CurrentRange`), unless the site names a place of its own, such as a function for the checks made after every function is converted. It is printed with Clang's `TextDiagnostic`, line and caret included, as a compiler error is, and its JSON record carries the range. +Its message names the construct as C++ does (`unsupportedConstruct` for the +statements and expressions without a site of their own; Clang's class name +only in parentheses for those it has no name for), and each error is raised +once: a declaration whose initializer was rejected counts as initialized, and +a rejected call returns before the generic message, so neither is reported +again where it is used. ## Modular Verification Protocol From 8741bea51993cb7d08984c5fb0a41b5eea723e65 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 14:27:39 +0530 Subject: [PATCH 073/119] docs: Design expressions that evaluate nothing at run time as their values. --- docs/DESIGN.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 3ee726e776..8bdb1cc29c 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -910,7 +910,11 @@ write_data(buf, 512); // definition. ``` -This is unique to being inside the compiler. +This is unique to being inside the compiler. Expressions that evaluate +nothing at run time are their values the same way: `noexcept(e)`, a +requires-expression, a concept, a type trait, `offsetof`, and +`__builtin_LINE()`, each an integer Clang computes for the expression as +written. ## Proof Functions From 9f4660d4958f83491777881eed0f5dc78e4b5687 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:39 +0530 Subject: [PATCH 074/119] verify: Count a run's verdicts, print under --quiet only what needs attention, list functions, and list spec axioms only under --verbose. --- clang/lib/Verify/Driver/Verifier.cpp | 93 ++++++++++++++++++++++++++-- clang/lib/Verify/Driver/Verifier.h | 24 +++++++ 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/clang/lib/Verify/Driver/Verifier.cpp b/clang/lib/Verify/Driver/Verifier.cpp index ce2fd0a830..4583445424 100644 --- a/clang/lib/Verify/Driver/Verifier.cpp +++ b/clang/lib/Verify/Driver/Verifier.cpp @@ -1099,6 +1099,8 @@ struct VerifyDiagnostic { /// For an error about source the verifier cannot represent: that source, /// shown with its line as a compiler error is. SourceRange Span; + /// A spec function that needs no check, listed with --verbose: no verdict. + bool Axiom = false; }; static std::string backendSuffix(const VerifyResult &Result) { @@ -2790,6 +2792,8 @@ class Verifier { VerifyOptions Opts; llvm::raw_ostream *DumpOS = nullptr; std::vector Diags; + /// What --list-functions prints: each function's location and kind. + std::vector> Listing; std::optional Lean; /// Where each template specialization verified is instantiated, and what /// it is, by its function's name. @@ -3139,6 +3143,20 @@ class Verifier { {VerifyDiagnostic::Warning, "no verifiable functions found"}); return true; } + if (Opts.ListFunctions) { + for (const auto &Fn : Functions) { + if (Fn->IsBuiltin || Fn->IsChoice || !Fn->InductiveStepOf.empty() || + !Fn->InductiveRuleOf.empty()) + continue; + const char *Kind = Fn->IsConstexprSpec ? "constexpr spec" + : Fn->IsSpec ? "spec" + : Fn->IsProof ? "proof" + : Fn->IsTrusted ? "trusted" + : "function"; + Listing.push_back({Fn->DeclLoc, std::string(Kind) + " " + Fn->Name}); + } + return true; + } inferFreshOwnedReturns(Functions); inferOutParameters(Functions); @@ -3903,8 +3921,11 @@ class Verifier { } if (Fn->IsSpec && !Fn->NeedsDecreasesCheck && !Step) { - // Nothing is solved for a spec axiom. + // Nothing is solved for a spec axiom, which is listed only with + // --verbose. MarkInterrupted.release(); + if (!Opts.Verbose) + continue; const VerifyDiagnostic::Kind Kind = Opts.LowerOnly ? VerifyDiagnostic::Lowered : (Opts.Backend == BackendKind::Lean @@ -3914,6 +3935,9 @@ class Verifier { Diags.push_back({Kind, "constexpr spec axiom: " + Fn->Name}); else if (!Fn->Unfolding) Diags.push_back({Kind, "spec axiom: " + Fn->Name}); + else + continue; + Diags.back().Axiom = true; continue; } @@ -4955,10 +4979,12 @@ class Verifier { } Diagnostic.Message += " [reason=" + verifyReasonCode(Reason).str() + "] (" + Why + ")"; - if (Diagnostic.Result) { - Diagnostic.Result->Status = VerifyStatus::Unresolved; - Diagnostic.Result->Reason = Reason; - } + // A line with no result of its own, as an inductive predicate's, gets + // one: the summary counts it and JSON gives its reason. + if (!Diagnostic.Result) + Diagnostic.Result.emplace(); + Diagnostic.Result->Status = VerifyStatus::Unresolved; + Diagnostic.Result->Reason = Reason; if (auto It = CheckOf.find(Index); It != CheckOf.end()) { const SpecReliance &Check = *It->second; if (Check.Facts & DefinitionFact) @@ -5754,7 +5780,57 @@ class Verifier { OS << llvm::formatv("{0}\n", llvm::json::Value(std::move(Record))); } + /// Counts the verdict D reports, if it reports one: an error reports a + /// verdict only when it carries one, as the first error of a function the + /// verifier does not represent does. + static void count(const VerifyDiagnostic &D, VerifySummary &Summary) { + if (D.Axiom) + return; + switch (D.K) { + case VerifyDiagnostic::Verified: + case VerifyDiagnostic::Certified: + case VerifyDiagnostic::MixedProof: + ++Summary.Verified; + return; + case VerifyDiagnostic::Error: + case VerifyDiagnostic::Unresolved: + if (!D.Result) + return; + if (D.Result->Status == VerifyStatus::Failed) { + ++Summary.Failed; + return; + } + ++Summary.Unresolved; + ++Summary.Reasons[verifyReasonCode(D.Result->Reason).str()]; + return; + case VerifyDiagnostic::BoundedSafe: + ++Summary.BoundedSafe; + return; + case VerifyDiagnostic::Trusted: + ++Summary.Trusted; + return; + case VerifyDiagnostic::Lowered: + ++Summary.Lowered; + return; + case VerifyDiagnostic::Exported: + ++Summary.Exported; + return; + case VerifyDiagnostic::Interrupted: + ++Summary.Interrupted; + return; + case VerifyDiagnostic::Warning: + return; + } + } + void printDiagnostics(llvm::raw_ostream &OS) const { + for (const auto &[Loc, Line] : Listing) { + PresumedLoc PLoc = Ctx.getSourceManager().getPresumedLoc(Loc); + if (PLoc.isValid()) + OS << PLoc.getFilename() << ":" << PLoc.getLine() << ":" + << PLoc.getColumn() << ": "; + OS << Line << "\n"; + } for (const auto &D : Diags) { if (D.Hidden) continue; @@ -5763,6 +5839,13 @@ class Verifier { D.K == VerifyDiagnostic::Certified || D.K == VerifyDiagnostic::Exported)) continue; + if (Opts.Summary) + count(D, *Opts.Summary); + // --quiet leaves out what needs no attention. + if (Opts.Quiet && (D.isProof() || D.K == VerifyDiagnostic::Lowered || + D.K == VerifyDiagnostic::Exported || + D.K == VerifyDiagnostic::Trusted)) + continue; if (Opts.Diagnostics == DiagnosticFormat::Json) { printJSONDiagnostic(D, OS); continue; diff --git a/clang/lib/Verify/Driver/Verifier.h b/clang/lib/Verify/Driver/Verifier.h index 2407637712..2d77453436 100644 --- a/clang/lib/Verify/Driver/Verifier.h +++ b/clang/lib/Verify/Driver/Verifier.h @@ -4,6 +4,7 @@ #include "../Backend/VerifyBackend.h" #include "llvm/Support/raw_ostream.h" +#include #include namespace clang { @@ -30,6 +31,20 @@ enum class LeanFallbackScope { All }; +/// The verdicts a run reported, counted for its summary line. +struct VerifySummary { + unsigned Verified = 0; + unsigned Failed = 0; + unsigned Unresolved = 0; + unsigned BoundedSafe = 0; + unsigned Trusted = 0; + unsigned Lowered = 0; + unsigned Exported = 0; + unsigned Interrupted = 0; + /// The reason code of each unresolved verdict, counted. + std::map Reasons; +}; + struct VerifyOptions { unsigned DumpIRLayers = 0; /// Build and encode verification conditions without invoking a solver. @@ -87,6 +102,15 @@ struct VerifyOptions { DiagnosticFormat Diagnostics = DiagnosticFormat::Text; /// Optional versioned binary archive for backend-neutral obligation modules. llvm::raw_ostream *ObligationOut = nullptr; + /// Print only what is not a proof: failures, unresolved results, errors, + /// and warnings. + bool Quiet = false; + /// Print the spec functions that need no check (`spec axiom`) too. + bool Verbose = false; + /// List the functions the run would verify, and verify none. + bool ListFunctions = false; + /// Where the run counts the verdicts it prints, if anywhere. + VerifySummary *Summary = nullptr; }; bool verifyTranslationUnit(ASTContext &Ctx, llvm::raw_ostream &OS, From f1d17cb8ddb25405923c3e50de25750603c05787 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:39 +0530 Subject: [PATCH 075/119] verify: Add --quiet, --verbose, and --list-functions, end a run with a summary line, and show the default timeout. --- clang/tools/cpp-verify/Main.cpp | 74 ++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/clang/tools/cpp-verify/Main.cpp b/clang/tools/cpp-verify/Main.cpp index 4b99460ea2..1947b7920d 100644 --- a/clang/tools/cpp-verify/Main.cpp +++ b/clang/tools/cpp-verify/Main.cpp @@ -16,6 +16,7 @@ #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" @@ -26,6 +27,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #if defined(__GLIBC__) @@ -114,12 +116,15 @@ static cl::opt BMCUnroll("unroll", cl::desc("Maximum loop unroll bound for --backend=bmc"), cl::init(10), cl::cat(CppVerifyCategory)); -static cl::opt SolverTimeout( - "timeout", - cl::desc( - "Per-query solver timeout in milliseconds (0 = no limit). A query that " - "exceeds it is reported as unresolved instead of hanging"), - cl::init(verify::DefaultSolverTimeoutMs), cl::cat(CppVerifyCategory)); +static const std::string SolverTimeoutDescription = + "Per-query solver timeout in milliseconds (default " + + std::to_string(verify::DefaultSolverTimeoutMs) + + ", 0 = no limit). A query that exceeds it is reported as unresolved " + "instead of hanging"; +static cl::opt SolverTimeout("timeout", + cl::desc(SolverTimeoutDescription), + cl::init(verify::DefaultSolverTimeoutMs), + cl::cat(CppVerifyCategory)); static cl::opt CollectionTimeout( "collection-timeout", @@ -261,9 +266,59 @@ static cl::opt DiagnosticsFormat( "Versioned JSON Lines")), cl::init(verify::DiagnosticFormat::Text), cl::cat(CppVerifyCategory)); +static cl::opt + Quiet("quiet", + cl::desc("Print only what is not a proof (failures, unresolved " + "results, errors, warnings) and the summary"), + cl::cat(CppVerifyCategory)); + +static cl::opt + Verbose("verbose", + cl::desc("Also print each spec function that needs no check " + "(spec axiom)"), + cl::cat(CppVerifyCategory)); + +static cl::opt ListFunctions( + "list-functions", + cl::desc("List the functions a run would verify, with their kinds and " + "locations, and verify none"), + cl::cat(CppVerifyCategory)); + namespace { static int gVerifyFailures = 0; +static verify::VerifySummary gSummary; + +/// One line for the run: how many verdicts of each kind it reported, the +/// reasons of the unresolved ones, and the time it took. +static void printSummary(std::chrono::steady_clock::time_point Start) { + std::vector Parts; + auto add = [&](unsigned N, llvm::StringRef What) { + if (N) + Parts.push_back(std::to_string(N) + " " + What.str()); + }; + add(gSummary.Verified, "verified"); + add(gSummary.Failed, "failed"); + if (gSummary.Unresolved) { + std::vector Reasons; + for (const auto &[Reason, N] : gSummary.Reasons) + Reasons.push_back(Reason + " " + std::to_string(N)); + Parts.push_back(std::to_string(gSummary.Unresolved) + " unresolved (" + + llvm::join(Reasons, ", ") + ")"); + } + add(gSummary.BoundedSafe, "bounded-safe"); + add(gSummary.Trusted, "trusted"); + add(gSummary.Lowered, "lowered"); + add(gSummary.Exported, "exported"); + add(gSummary.Interrupted, "interrupted"); + const double Seconds = + std::chrono::duration(std::chrono::steady_clock::now() - Start) + .count(); + llvm::outs() << "Summary: " + << (Parts.empty() ? std::string("no verdicts") + : llvm::join(Parts, ", ")) + << llvm::formatv(" in {0:f1} s", Seconds) << "\n"; +} static llvm::raw_ostream *gObligationOut = nullptr; static uint64_t proofCacheMaxBytes() { @@ -379,6 +434,10 @@ class VerifyConsumer : public ASTConsumer { VOpts.ProfileQuantifiers = ProfileQuantifiers.getValue(); VOpts.Diagnostics = DiagnosticsFormat.getValue(); VOpts.ObligationOut = gObligationOut; + VOpts.Quiet = Quiet.getValue(); + VOpts.Verbose = Verbose.getValue(); + VOpts.ListFunctions = ListFunctions.getValue(); + VOpts.Summary = &gSummary; if (!verify::verifyTranslationUnit(Ctx, llvm::outs(), VOpts)) ++gVerifyFailures; } @@ -947,6 +1006,7 @@ static int replayObligationArchive() { } // namespace int main(int argc, const char **argv) { + const auto Start = std::chrono::steady_clock::now(); llvm::InitLLVM X(argc, argv); // The first Ctrl-C stops the solvers and reports what is not finished; a // second one ends the process. @@ -1063,6 +1123,8 @@ int main(int argc, const char **argv) { Tool.setDiagnosticConsumer(&ArgumentDiagnostics); VerifyActionFactory Factory(ArgumentDiagnostics); int RC = Tool.run(&Factory); + if (DiagnosticsFormat == verify::DiagnosticFormat::Text && !ListFunctions) + printSummary(Start); if (verify::interruptRequested()) return 130; if (RC != 0) From 48a99be7fc671b861c84bfca0de958edabe11265 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:40 +0530 Subject: [PATCH 076/119] test: The summary line, --quiet, --verbose, --list-functions, and the default timeout in --help. --- clang/test/Verify/suite/command_line.cpp | 99 ++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 clang/test/Verify/suite/command_line.cpp diff --git a/clang/test/Verify/suite/command_line.cpp b/clang/test/Verify/suite/command_line.cpp new file mode 100644 index 0000000000..9c7b6a5579 --- /dev/null +++ b/clang/test/Verify/suite/command_line.cpp @@ -0,0 +1,99 @@ +// RUN: not %cpp-verify --jobs=1 %s -- 2>&1 | FileCheck %s --check-prefix=DEFAULT --implicit-check-not='spec axiom' +// RUN: not %cpp-verify --jobs=1 --quiet %s -- 2>&1 | FileCheck %s --check-prefix=QUIET --implicit-check-not=Verified --implicit-check-not=Trusted +// RUN: not %cpp-verify --jobs=1 --verbose %s -- 2>&1 | FileCheck %s --check-prefix=VERBOSE +// RUN: %cpp-verify --list-functions %s -- 2>&1 | FileCheck %s --check-prefix=LIST +// RUN: not %cpp-verify --jobs=1 --diagnostics-format=json %s -- 2>/dev/null | FileCheck %s --check-prefix=JSON +// RUN: %cpp-verify --help | FileCheck %s --check-prefix=HELP +// +// A run ends with one line that counts its verdicts by kind, with the +// reasons of the unresolved ones. --quiet prints only what needs attention, +// --verbose also the spec functions that need no check, and +// --list-functions what a run would verify, without verifying it. + +namespace cv = cppverify; + +cv::spec int twice(int x) { return 2 * x; } + +cv::spec int down(int n) + cv::decreases(n) +{ + return n <= 0 ? 0 : down(n - 1); +} + +cv::proof void down_zero(int n) + cv::pre(n <= 0) + cv::post(down(n) == 0) +{ +} + +int add_one(int x) + cv::pre(x < 100) + cv::post(cv::result == x + 1) +{ + return x + 1; +} + +int add_two(int x) + cv::pre(x < 100) + cv::post(cv::result == twice(x)) +{ + return x + 2; +} + +int lookup(int x) + cv::post(true) +{ + goto done; +done: + return x; +} + +[[cppverify::trusted]] int clamp(int x) + cv::post(0 <= cv::result && cv::result <= 255); + +int first(const int *p) + cv::pre(cv::valid(p, 1)) + cv::post(cv::result == p[0]) +{ + return p[0]; +} + +// DEFAULT: error: lookup: goto is unsupported +// DEFAULT: error: lookup: labels are unsupported +// DEFAULT: Verified: spec decreases: down +// DEFAULT-NEXT: Verified: down_zero [backend=z3] +// DEFAULT-NEXT: Verified: add_one [backend=z3] +// DEFAULT-NEXT: error: verification failed: add_two +// DEFAULT-NEXT: Trusted: clamp +// DEFAULT-NEXT: Verified: first [backend=z3] +// DEFAULT-NEXT: Summary: 4 verified, 1 failed, 1 unresolved (construct.unsupported 1), 1 trusted in {{[0-9]+\.[0-9]}} s +// DEFAULT-NOT: {{.}} + +// QUIET: error: lookup: goto is unsupported +// QUIET: error: lookup: labels are unsupported +// QUIET: error: verification failed: add_two +// QUIET-NEXT: Summary: 4 verified, 1 failed, 1 unresolved (construct.unsupported 1), 1 trusted in + +// VERBOSE: Verified: spec axiom: twice +// VERBOSE-NEXT: Verified: spec decreases: down +// VERBOSE-NEXT: Verified: down_zero [backend=z3] +// VERBOSE: Summary: 4 verified, 1 failed + +// LIST: command_line.cpp:15:14: spec twice +// LIST-NEXT: command_line.cpp:17:14: spec down +// LIST-NEXT: command_line.cpp:23:16: proof down_zero +// LIST-NEXT: command_line.cpp:29:5: function add_one +// LIST-NEXT: command_line.cpp:36:5: function add_two +// LIST-NEXT: command_line.cpp:43:5: function lookup +// LIST-NEXT: command_line.cpp:51:28: trusted clamp +// LIST-NEXT: command_line.cpp:54:5: function first +// LIST-NEXT: command_line.cpp:46:3: error: lookup: goto is unsupported +// LIST-NOT: Verified +// LIST-NOT: Summary + +// JSON-NOT: Summary + +// HELP: --list-functions +// HELP: --quiet +// HELP: --timeout= {{.*}}(default 30000, 0 = no limit) +// HELP: --verbose From 6c9bcd3f618b3630a855822a828146e0db90e375 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 18:22:28 +0530 Subject: [PATCH 077/119] test: A predicate whose rules are not established counts in the summary, with a JSON reason. --- clang/test/Verify/suite/inductive_rules.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/test/Verify/suite/inductive_rules.cpp b/clang/test/Verify/suite/inductive_rules.cpp index f436a24063..d70a2edff8 100644 --- a/clang/test/Verify/suite/inductive_rules.cpp +++ b/clang/test/Verify/suite/inductive_rules.cpp @@ -119,3 +119,7 @@ void not_empty(node *a) // A rule proof that fails is reported, and the predicate with it. // UNPROVED-DAG: Unresolved: inductive predicate: reach [reason=spec.inductive] (its rules are not established: {{.*}}) // UNPROVED-DAG: Unresolved: reach (monotonicity) +// UNPROVED: Summary: 5 unresolved ({{.*}}spec.inductive 1) +// RUN: not %cpp-verify --solver-rlimit=1 --diagnostics-format=json %S/Inputs/inductive_unproved.cpp -- 2>/dev/null \ +// RUN: | FileCheck %s --check-prefix=UNPROVED-JSON +// UNPROVED-JSON: {"message":"inductive predicate: reach [reason=spec.inductive] {{.*}}","reason":"spec.inductive","schema":"cppverify.diagnostic/1","severity":"error","status":"unresolved"} From fca5ee5f7cff387bb1ef904b2a13add10434d0c2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:41 +0530 Subject: [PATCH 078/119] test: Expect no spec axiom lines in choose_reads.cpp. --- clang/test/Verify/suite/choose_reads.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/choose_reads.cpp b/clang/test/Verify/suite/choose_reads.cpp index aecf91d746..86f72c1d4d 100644 --- a/clang/test/Verify/suite/choose_reads.cpp +++ b/clang/test/Verify/suite/choose_reads.cpp @@ -13,7 +13,6 @@ cv::spec int index_of(const int *a, int n, int x) return cv::choose(k, 0, n, a[k] == x); } // CHECK-DAG: Verified: spec reads: index_of -// CHECK-DAG: Verified: spec axiom: index_of // a[n] lies outside the declared cells. cv::spec int index_past(const int *a, int n, int x) @@ -22,7 +21,6 @@ cv::spec int index_past(const int *a, int n, int x) return cv::choose(k, 0, n + 1, a[k] == x); } // CHECK-DAG: error: spec reads failed: index_past [backend=z3] [reason=counterexample] -// CHECK-DAG: Verified: spec axiom: index_past // Once the reads clause holds, a write outside it keeps the value. void set_other(const int *a, int n, int *b, int x) From c61ef8c8eaf2110bec18daa0e131526cf3f1db34 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:41 +0530 Subject: [PATCH 079/119] test: Expect no spec axiom lines in class_templates.cpp. --- clang/test/Verify/suite/class_templates.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/class_templates.cpp b/clang/test/Verify/suite/class_templates.cpp index 63be7ecb10..c59e3aaa2b 100644 --- a/clang/test/Verify/suite/class_templates.cpp +++ b/clang/test/Verify/suite/class_templates.cpp @@ -176,7 +176,6 @@ int digit() int v = x.get(); return v; } -// CHECK-DAG: Verified: spec axiom: small // CHECK-DAG: Verified: Digit::Digit [backend=z3] // CHECK-DAG: Verified: Digit::get [backend=z3] // CHECK-DAG: Verified: digit [backend=z3] From 9d812a4d031f05c8758da924a2d66969dfba4a67 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:41 +0530 Subject: [PATCH 080/119] test: Expect no spec axiom lines in cov_multi_spec_z3.cpp. --- clang/test/Verify/suite/cov_multi_spec_z3.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/cov_multi_spec_z3.cpp b/clang/test/Verify/suite/cov_multi_spec_z3.cpp index 5e12ffb026..c570ae691e 100644 --- a/clang/test/Verify/suite/cov_multi_spec_z3.cpp +++ b/clang/test/Verify/suite/cov_multi_spec_z3.cpp @@ -10,6 +10,4 @@ int chain(int x) return 2 * x + 1; } -// VERIFY-DAG: spec axiom: inc_s -// VERIFY-DAG: spec axiom: add_s // VERIFY-DAG: Verified: chain \ No newline at end of file From 3dff3c5f12c05b402189eb78ac06e96cf7f3900c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:42 +0530 Subject: [PATCH 081/119] test: Expect no spec axiom lines in cov_spec_fuel_exec.cpp. --- clang/test/Verify/suite/cov_spec_fuel_exec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/cov_spec_fuel_exec.cpp b/clang/test/Verify/suite/cov_spec_fuel_exec.cpp index c25c58c7ca..28b54a1d9d 100644 --- a/clang/test/Verify/suite/cov_spec_fuel_exec.cpp +++ b/clang/test/Verify/suite/cov_spec_fuel_exec.cpp @@ -11,5 +11,4 @@ int use_with_reveal(int x) return 2 * x; } -// VERIFY-DAG: spec axiom: double_spec // VERIFY-DAG: Verified: use_with_reveal \ No newline at end of file From 0c1fc02b16f4b3af19dff0419e58add5bbabc351 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:42 +0530 Subject: [PATCH 082/119] test: Expect the summary line at the end of demoted_vacuity.cpp. --- clang/test/Verify/suite/demoted_vacuity.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Verify/suite/demoted_vacuity.cpp b/clang/test/Verify/suite/demoted_vacuity.cpp index fff2903c93..223c45fa56 100644 --- a/clang/test/Verify/suite/demoted_vacuity.cpp +++ b/clang/test/Verify/suite/demoted_vacuity.cpp @@ -96,6 +96,7 @@ int unsatisfiable(int x) } // CHECK-NEXT: Verified: unsatisfiable [backend=z3] [vacuous] // CHECK-NEXT: warning: unsatisfiable: the precondition is unsatisfiable, so every claim about it holds vacuously +// CHECK-NEXT: Summary: {{.*}} // CHECK-NOT: {{.}} // JSON: "function":"uses_loop" From 714eb8f00380f9bd33040638de21c1745587b030 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:43 +0530 Subject: [PATCH 083/119] test: Expect no spec axiom lines in function_templates.cpp. --- clang/test/Verify/suite/function_templates.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/function_templates.cpp b/clang/test/Verify/suite/function_templates.cpp index 48606a2948..5c7b797aea 100644 --- a/clang/test/Verify/suite/function_templates.cpp +++ b/clang/test/Verify/suite/function_templates.cpp @@ -194,7 +194,6 @@ int clamped(int x) int c = clamp(x, 0, 10); return c; } -// CHECK-DAG: Verified: spec axiom: between // CHECK-DAG: Verified: clamp [backend=z3] // CHECK-DAG: Verified: clamped [backend=z3] @@ -360,7 +359,6 @@ int clamp3(int x) return x < 3 ? 3 : x; } // CHECK-DAG: Verified: clamp3 [backend=z3] -// CHECK-DAG: Verified: constexpr spec axiom: bigger_of // A contract written on the definition, after a declaration that names its // parameter otherwise and before which the template is used. From fce44aa997d793713792e0b7ec9df9793a564753 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:43 +0530 Subject: [PATCH 084/119] test: Expect no spec axiom lines, and the summary line, in interrupt.cpp. --- clang/test/Verify/suite/interrupt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/interrupt.cpp b/clang/test/Verify/suite/interrupt.cpp index a70c339157..714a333f05 100644 --- a/clang/test/Verify/suite/interrupt.cpp +++ b/clang/test/Verify/suite/interrupt.cpp @@ -63,7 +63,7 @@ cppverify::spec int cube(int x) { return x * x * x; } // CHECK-NEXT: Interrupted: small_power // CHECK-NEXT: Interrupted: cubes // CHECK-NEXT: Interrupted: fourth_powers -// CHECK-NEXT: Verified: spec axiom: cube +// CHECK-NEXT: Summary: {{.*}} // CHECK-NEXT: exit: 130 // JSON: "function":"cubes","message":"cubes","reason":"interrupted","schema":"cppverify.diagnostic/1","severity":"error","source":{{.*}}"status":"unresolved" From e3cb706d914b8cba32b6f03d8b065aae514f95fe Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:43 +0530 Subject: [PATCH 085/119] test: Expect the summary line at the end of interrupt_certify.cpp. --- clang/test/Verify/suite/interrupt_certify.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Verify/suite/interrupt_certify.cpp b/clang/test/Verify/suite/interrupt_certify.cpp index dd75e9b66b..01b92b626f 100644 --- a/clang/test/Verify/suite/interrupt_certify.cpp +++ b/clang/test/Verify/suite/interrupt_certify.cpp @@ -18,4 +18,5 @@ cppverify::proof void large_sum() } // CHECK: Interrupted: large_sum +// CHECK-NEXT: Summary: {{.*}} // CHECK-NEXT: exit: 130 From 05b6d4738d62a5bc790bf900c5541ca92dcaefaf Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:44 +0530 Subject: [PATCH 086/119] test: Expect the summary line at the end of interrupt_cvc5.cpp. --- clang/test/Verify/suite/interrupt_cvc5.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Verify/suite/interrupt_cvc5.cpp b/clang/test/Verify/suite/interrupt_cvc5.cpp index 49cc05024f..1c92fa0bb6 100644 --- a/clang/test/Verify/suite/interrupt_cvc5.cpp +++ b/clang/test/Verify/suite/interrupt_cvc5.cpp @@ -19,4 +19,5 @@ cppverify::proof void cubes(int x, int y, int z) // CHECK: Verified: fast // CHECK-NEXT: Interrupted: cubes +// CHECK-NEXT: Summary: {{.*}} // CHECK-NEXT: exit: 130 From b9f96edfcb02a54015c4b267be68c1108bef8f18 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:44 +0530 Subject: [PATCH 087/119] test: Expect the summary line at the end of lean_certifier_memory.test. --- clang/test/Verify/suite/lean_certifier_memory.test | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Verify/suite/lean_certifier_memory.test b/clang/test/Verify/suite/lean_certifier_memory.test index c892c86670..9b0b8dbcd7 100644 --- a/clang/test/Verify/suite/lean_certifier_memory.test +++ b/clang/test/Verify/suite/lean_certifier_memory.test @@ -17,4 +17,5 @@ # CHECK: Certified: fine [backend=Lean] # CHECK-NEXT: Unresolved: lean obligation: one (Lean certification failed: the proof of fn_5f5a336f6e6576::postcondition@8:37: the kernel rejects CppVerify.Lemmas: while replaying declaration 'cheat' +# CHECK-NEXT: Summary: {{.*}} # CHECK-NEXT: peak memory below 6144 MB From 6d8d320904841b8a568446223c82100a9af7d7a6 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:44 +0530 Subject: [PATCH 088/119] test: Expect no spec axiom lines in lean_fallback_fast.cpp. --- clang/test/Verify/suite/lean_fallback_fast.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/lean_fallback_fast.cpp b/clang/test/Verify/suite/lean_fallback_fast.cpp index 43fca7ac94..b1730ff2b5 100644 --- a/clang/test/Verify/suite/lean_fallback_fast.cpp +++ b/clang/test/Verify/suite/lean_fallback_fast.cpp @@ -15,7 +15,6 @@ cppverify::proof void lemma_double_value(int value) { } -// CHECK-DAG: Verified: spec axiom: double_value // CHECK-DAG: Exported: lean fallback: lemma_double_value // CHECK-DAG: Unresolved: lemma_double_value [backend=z3] [reason=solver.resource-limit] (proof obligation // GOALS: 2 From 3bf049980ef48e9a52681dcb21b17243476af811 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:45 +0530 Subject: [PATCH 089/119] test: Expect no spec axiom lines in logic_function_ir.cpp. --- clang/test/Verify/suite/logic_function_ir.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/logic_function_ir.cpp b/clang/test/Verify/suite/logic_function_ir.cpp index 3394190773..cef7efdc94 100644 --- a/clang/test/Verify/suite/logic_function_ir.cpp +++ b/clang/test/Verify/suite/logic_function_ir.cpp @@ -52,7 +52,6 @@ cppverify::proof void opaque_logic() // LOGIC-NEXT: fuel 0 // LOGIC-NEXT: step // LOGIC-NOT: definition -// LOGIC: Lowered: spec axiom: hidden // Z3-DAG: Verified: recursive_logic // Z3-DAG: Verified: opaque_logic From 2dbc679611236e0f213e8db7ad74652b1f88c17f Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:45 +0530 Subject: [PATCH 090/119] test: Expect no spec axiom lines in lower_only_layers.cpp. --- clang/test/Verify/suite/lower_only_layers.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/lower_only_layers.cpp b/clang/test/Verify/suite/lower_only_layers.cpp index 5ea3ec3b73..d49a06ff2e 100644 --- a/clang/test/Verify/suite/lower_only_layers.cpp +++ b/clang/test/Verify/suite/lower_only_layers.cpp @@ -20,12 +20,10 @@ int machine_identity(int x) return x; } -// LOWER-DAG: Lowered: spec axiom: mathematical_successor // LOWER-DAG: Lowered: machine_identity // LOWER-NOT: Verified: // LOWER-NOT: error: -// VERIFY: Verified: spec axiom: mathematical_successor // VERIFY: error: verification failed: machine_identity // LEAN: error: --lower-only supports Z3, cvc5, portfolio, and BMC From 498a0739ec9e590f4780a77c8534a7ffd4e4c372 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:45 +0530 Subject: [PATCH 091/119] test: Expect no spec axiom lines in mvp_factorial.cpp. --- clang/test/Verify/suite/mvp_factorial.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/mvp_factorial.cpp b/clang/test/Verify/suite/mvp_factorial.cpp index a4409d1573..d4cc53ba43 100644 --- a/clang/test/Verify/suite/mvp_factorial.cpp +++ b/clang/test/Verify/suite/mvp_factorial.cpp @@ -121,4 +121,3 @@ int factorial_thirteen_overflows() // VERIFY-DAG: Verified: factorial_twelve // VERIFY-DAG: Verified: write_factorial // VERIFY-DAG: error: verification failed: factorial_thirteen_overflows -// VERIFY-DAG: Verified: constexpr spec axiom: machine_factorial_value From df34fe2cd030f7ec52d49250a55e855561c1f6de Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:46 +0530 Subject: [PATCH 092/119] test: Expect no spec axiom lines, and the summary line, in per_function_errors.test. --- clang/test/Verify/suite/per_function_errors.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/per_function_errors.test b/clang/test/Verify/suite/per_function_errors.test index 8ced5b94cc..65e02a63e7 100644 --- a/clang/test/Verify/suite/per_function_errors.test +++ b/clang/test/Verify/suite/per_function_errors.test @@ -30,8 +30,8 @@ TEXT-NEXT: per_function_errors.cpp:55:23: Unresolved: choice_lemma [reason=const TEXT-NEXT: Unresolved: uses_zero_lemma [backend=z3] [reason=callee.contract] (relies on the contract of zero_lemma (it uses a construct the verifier does not support), which is not established) TEXT-NEXT: per_function_errors.cpp:87:5: Unresolved: uses_step [reason=construct.unsupported] (relies on step, which uses a construct the verifier does not support) TEXT-NEXT: Verified: inc [backend=z3] -TEXT-NEXT: Verified: spec axiom: twice TEXT-NEXT: Verified: uses_twice [backend=z3] +TEXT-NEXT: Summary: {{.*}} TEXT-NOT: {{.}} JSON: {"function":"pick","message":"pick: throw is unsupported: exceptions are not modeled","reason":"construct.unsupported",{{.*}}"line":11},"status":"unresolved"} From 3b0fedad130d46db3069153bb1fe64c4521d8d13 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:46 +0530 Subject: [PATCH 093/119] test: Expect no spec axiom lines in safe_fib.cpp. --- clang/test/Verify/suite/safe_fib.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/safe_fib.cpp b/clang/test/Verify/suite/safe_fib.cpp index b8148acf6b..e1629c1cf5 100644 --- a/clang/test/Verify/suite/safe_fib.cpp +++ b/clang/test/Verify/suite/safe_fib.cpp @@ -185,7 +185,6 @@ int fibonacci_forty_seven_overflows() #endif // VERIFY-DAG: Verified: spec decreases: math_fibonacci -// VERIFY-DAG: Verified: spec axiom: math_value // VERIFY-DAG: Verified: lemma_fibonacci_step // VERIFY-DAG: Verified: lemma_fibonacci_base // VERIFY-DAG: Verified: lemma_fibonacci_machine_step @@ -193,4 +192,3 @@ int fibonacci_forty_seven_overflows() // VERIFY-DAG: Verified: iterative_fibonacci // VERIFY-DAG: Verified: fibonacci_forty_six // VERIFY-DAG: error: verification failed: fibonacci_forty_seven_overflows -// VERIFY-DAG: Verified: constexpr spec axiom: machine_fibonacci_value From 7584243e3978d0c6564a4801da9e14911aa1d813 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:47 +0530 Subject: [PATCH 094/119] test: Expect no spec axiom lines in solver_portfolio.cpp. --- clang/test/Verify/suite/solver_portfolio.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang/test/Verify/suite/solver_portfolio.cpp b/clang/test/Verify/suite/solver_portfolio.cpp index a7295add33..0fb5af47bc 100644 --- a/clang/test/Verify/suite/solver_portfolio.cpp +++ b/clang/test/Verify/suite/solver_portfolio.cpp @@ -87,9 +87,6 @@ int invalid_spec_arithmetic() return 0; } -// CVC5-DAG: Verified: spec axiom: quotient -// CVC5-DAG: Verified: spec axiom: remainder -// CVC5-DAG: Verified: spec axiom: answer // CVC5-DAG: Verified: valid_spec_arithmetic [backend=cvc5] // CVC5-DAG: Verified: valid_zero_arity_spec [backend=cvc5] // CVC5-DAG: Verified: valid_unsigned [backend=cvc5] From 3e140da229e9a85b061e17d1cc3ff53d1e9e7fac Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:47 +0530 Subject: [PATCH 095/119] test: Expect no spec axiom lines in spec_inline_simple.cpp. --- clang/test/Verify/suite/spec_inline_simple.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/spec_inline_simple.cpp b/clang/test/Verify/suite/spec_inline_simple.cpp index 00bbb99a29..f7cea8ac2a 100644 --- a/clang/test/Verify/suite/spec_inline_simple.cpp +++ b/clang/test/Verify/suite/spec_inline_simple.cpp @@ -14,5 +14,4 @@ int use_double(int x) return 2 * x; } -// VERIFY-DAG: spec axiom: double_spec // VERIFY-DAG: Verified: use_double \ No newline at end of file From 48412972cc95087336a712de2ea5820817fff672 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:47 +0530 Subject: [PATCH 096/119] test: Expect no spec axiom lines in spec_reads_exact.cpp. --- clang/test/Verify/suite/spec_reads_exact.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/spec_reads_exact.cpp b/clang/test/Verify/suite/spec_reads_exact.cpp index c6ee976987..da83352a99 100644 --- a/clang/test/Verify/suite/spec_reads_exact.cpp +++ b/clang/test/Verify/suite/spec_reads_exact.cpp @@ -68,7 +68,6 @@ unsigned next(const unsigned *p) // CHECK-NEXT: Verified: spec reads: negated // CHECK-NEXT: Verified: spec post: negated // CHECK-NEXT: Verified: spec reads: first -// CHECK-NEXT: Verified: spec axiom: first // CHECK-NEXT: Verified: large_sum // CHECK-NEXT: error: verification failed: negative_sum {{.*}}[reason=counterexample] // CHECK-NEXT: Verified: smallest_negated From 488c7e6fbdbb5760a0e8d8b44c7d177b6d44cd4b Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:48 +0530 Subject: [PATCH 097/119] test: Expect no spec axiom lines in std_min_max.cpp. --- clang/test/Verify/suite/std_min_max.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/std_min_max.cpp b/clang/test/Verify/suite/std_min_max.cpp index b97cf14c83..fe13d8e7ec 100644 --- a/clang/test/Verify/suite/std_min_max.cpp +++ b/clang/test/Verify/suite/std_min_max.cpp @@ -59,7 +59,6 @@ int mine(int x) { return x < 3 ? 3 : x; } -// CHECK-DAG: Verified: constexpr spec axiom: mymax // CHECK-DAG: Verified: mine [backend=z3] constexpr int first(const int *p) { return p[0]; } @@ -108,7 +107,6 @@ int distance(int a, int b) { return a < b ? b - a : a - b; } -// CHECK-DAG: Verified: spec axiom: spread // CHECK-DAG: Verified: distance [backend=z3] // A reference to the result is not a value: it fails closed. From 187b1267adf9b1348958680dafa72a657fd2f10e Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:48 +0530 Subject: [PATCH 098/119] test: Expect no spec axiom lines in switch_statements.cpp. --- clang/test/Verify/suite/switch_statements.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/switch_statements.cpp b/clang/test/Verify/suite/switch_statements.cpp index 774370e165..b22ec47786 100644 --- a/clang/test/Verify/suite/switch_statements.cpp +++ b/clang/test/Verify/suite/switch_statements.cpp @@ -516,7 +516,6 @@ cv::spec int select(int x) return 6; } } -// CHECK-DAG: Verified: spec axiom: select cv::spec int select_break(int x) { @@ -530,7 +529,6 @@ cv::spec int select_break(int x) } return r; } -// CHECK-DAG: Verified: spec axiom: select_break int uses_select(int x) cv::post(cv::result == select(x) && cv::result == select_break(x)) From 869b1c4c24a7f2130e9313a59c17c8f4bafcebc0 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:48 +0530 Subject: [PATCH 099/119] test: Expect no spec axiom lines in z3_constexpr_spec.cpp. --- clang/test/Verify/suite/z3_constexpr_spec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/z3_constexpr_spec.cpp b/clang/test/Verify/suite/z3_constexpr_spec.cpp index 471b6d760b..a0d7af2dce 100644 --- a/clang/test/Verify/suite/z3_constexpr_spec.cpp +++ b/clang/test/Verify/suite/z3_constexpr_spec.cpp @@ -10,5 +10,4 @@ int use_sq(int x) return sq(x); } -// VERIFY-DAG: spec axiom: sq // VERIFY-DAG: Verified: use_sq \ No newline at end of file From 179ca82daf20560a970e55aaf8ae9a02db0473fa Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:49 +0530 Subject: [PATCH 100/119] test: Expect no spec axiom lines in z3_hide_reveal_fuel.cpp. --- clang/test/Verify/suite/z3_hide_reveal_fuel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/z3_hide_reveal_fuel.cpp b/clang/test/Verify/suite/z3_hide_reveal_fuel.cpp index a33250a8b8..e416f0eec3 100644 --- a/clang/test/Verify/suite/z3_hide_reveal_fuel.cpp +++ b/clang/test/Verify/suite/z3_hide_reveal_fuel.cpp @@ -40,7 +40,6 @@ cppverify::proof void lemma_fact_base(int n) cppverify::ghost { cppverify::reveal_with_fuel(fact, 2); } } -// VERIFY-DAG: spec axiom: triple // VERIFY-DAG: Verified: lemma_triple // VERIFY-DAG: Verified: client_reveal // VERIFY-DAG: Verified: client_hide From ddacaa1841e8336a3e4bee6f1f083b9042897ca7 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:49 +0530 Subject: [PATCH 101/119] test: Expect no spec axiom lines in z3_integer_widths.cpp. --- clang/test/Verify/suite/z3_integer_widths.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/z3_integer_widths.cpp b/clang/test/Verify/suite/z3_integer_widths.cpp index b548566c43..c85e60c8ce 100644 --- a/clang/test/Verify/suite/z3_integer_widths.cpp +++ b/clang/test/Verify/suite/z3_integer_widths.cpp @@ -90,8 +90,6 @@ void invalid_i64_heap_truncation(long long *p) *p = 4294967296LL; } -// VERIFY-DAG: spec axiom: math_u32_max -// VERIFY-DAG: spec axiom: math_u64_max // VERIFY-DAG: Verified: valid_unsigned_literals_in_math_specs // VERIFY-DAG: Verified: valid_u64_literal_bit_pattern // VERIFY-DAG: Verified: valid_i64_high_value From def615d0fb5b94a7d38e7f13a06097abd498e03a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:49 +0530 Subject: [PATCH 102/119] test: Expect no spec axiom lines in z3_overloads.cpp. --- clang/test/Verify/suite/z3_overloads.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/suite/z3_overloads.cpp b/clang/test/Verify/suite/z3_overloads.cpp index 678231494b..c4af0f06e4 100644 --- a/clang/test/Verify/suite/z3_overloads.cpp +++ b/clang/test/Verify/suite/z3_overloads.cpp @@ -63,6 +63,5 @@ int use_long_spec_overload() // VERIFY-DAG: Verified: call_int_overload // VERIFY-DAG: Verified: call_long_overload // VERIFY-DAG: Verified: spec decreases: overloaded_countdown -// VERIFY-DAG: Verified: spec axiom: overloaded_countdown // VERIFY-DAG: Verified: use_int_spec_overload // VERIFY-DAG: Verified: use_long_spec_overload From 91797b830e358905352c2019afbd34d78436426a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:50 +0530 Subject: [PATCH 103/119] test: Expect no spec axiom lines in z3_quantified_specs.cpp. --- clang/test/Verify/suite/z3_quantified_specs.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clang/test/Verify/suite/z3_quantified_specs.cpp b/clang/test/Verify/suite/z3_quantified_specs.cpp index c3c05db942..66740b84b9 100644 --- a/clang/test/Verify/suite/z3_quantified_specs.cpp +++ b/clang/test/Verify/suite/z3_quantified_specs.cpp @@ -67,11 +67,7 @@ int invalid_quantifier_capture(int n) return n; } -// VERIFY-DAG: spec axiom: bump // VERIFY-DAG: spec decreases: countdown -// VERIFY-DAG: spec axiom: contains_last -// VERIFY-DAG: spec axiom: binder_shadows_parameter -// VERIFY-DAG: spec axiom: exists_below_ten // VERIFY-DAG: Verified: valid_quantified_nonrecursive_spec // VERIFY-DAG: Verified: valid_nested_quantified_bound // VERIFY-DAG: Verified: valid_quantified_binder_shadowing From 5bd551f1166dcd3c8507a941e156da38bca2844c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:50 +0530 Subject: [PATCH 104/119] test: Expect no spec axiom lines in z3_spec_division.cpp. --- clang/test/Verify/suite/z3_spec_division.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/z3_spec_division.cpp b/clang/test/Verify/suite/z3_spec_division.cpp index 0714706828..f9d902f583 100644 --- a/clang/test/Verify/suite/z3_spec_division.cpp +++ b/clang/test/Verify/suite/z3_spec_division.cpp @@ -33,8 +33,6 @@ int invalid_euclidean_quotient() return 0; } -// VERIFY-DAG: Verified: spec axiom: quotient -// VERIFY-DAG: Verified: spec axiom: remainder // VERIFY-DAG: Verified: valid_spec_division // VERIFY-DAG: Verified: valid_spec_zero_extension // VERIFY-DAG: error: verification failed: invalid_euclidean_quotient From 2f4b73601aa985739846d5e08edc488eb993c40e Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:51 +0530 Subject: [PATCH 105/119] test: Expect no spec axiom lines in z3_spec_proof_axioms.cpp. --- clang/test/Verify/suite/z3_spec_proof_axioms.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/test/Verify/suite/z3_spec_proof_axioms.cpp b/clang/test/Verify/suite/z3_spec_proof_axioms.cpp index 176d06e19f..aa01f8a56e 100644 --- a/clang/test/Verify/suite/z3_spec_proof_axioms.cpp +++ b/clang/test/Verify/suite/z3_spec_proof_axioms.cpp @@ -19,7 +19,5 @@ int use_specs(int x) return 2 * x + 3; } -// VERIFY-DAG: spec axiom: double_it // VERIFY-DAG: Verified: lemma_double -// VERIFY-DAG: spec axiom: add_three // VERIFY-DAG: Verified: use_specs \ No newline at end of file From 6b1e7b5f9a2563acaa4b4f813df902992b9d91a6 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:51 +0530 Subject: [PATCH 106/119] test: Expect no spec axiom lines in verify_constexpr_spec_e2e.cpp. --- clang/test/Verify/verify_constexpr_spec_e2e.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/verify_constexpr_spec_e2e.cpp b/clang/test/Verify/verify_constexpr_spec_e2e.cpp index c60299f949..90f314f092 100644 --- a/clang/test/Verify/verify_constexpr_spec_e2e.cpp +++ b/clang/test/Verify/verify_constexpr_spec_e2e.cpp @@ -12,5 +12,4 @@ int use_constexpr_spec(int x) return x + x; } -// VERIFY-DAG: constexpr spec axiom: double_it // VERIFY-DAG: Verified: use_constexpr_spec \ No newline at end of file From caec56e984877dfe944105a9c4f332a41c004ce5 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:51 +0530 Subject: [PATCH 107/119] test: Expect no spec axiom lines in verify_hide_reveal_e2e.cpp. --- clang/test/Verify/verify_hide_reveal_e2e.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Verify/verify_hide_reveal_e2e.cpp b/clang/test/Verify/verify_hide_reveal_e2e.cpp index 44a984c97f..58570c0c4d 100644 --- a/clang/test/Verify/verify_hide_reveal_e2e.cpp +++ b/clang/test/Verify/verify_hide_reveal_e2e.cpp @@ -26,7 +26,6 @@ int client_hide(int x) return x + x + x; } -// VERIFY: spec axiom: triple // VERIFY: Verified: lemma_triple // VERIFY: Verified: client_reveal // VERIFY: Verified: client_hide \ No newline at end of file From b3c236e36280644b6b690a9d4745c2fecbbf8a1d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:52 +0530 Subject: [PATCH 108/119] docs: Describe the summary line, --quiet, --verbose, and --list-functions. --- website/source/language/tooling.rst | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/website/source/language/tooling.rst b/website/source/language/tooling.rst index 73f339824b..ce079e7dce 100644 --- a/website/source/language/tooling.rst +++ b/website/source/language/tooling.rst @@ -8,6 +8,9 @@ Standalone verifier cpp-verify --version cpp-verify file.cpp + cpp-verify --quiet file.cpp + cpp-verify --verbose file.cpp + cpp-verify --list-functions file.cpp cpp-verify file.cpp -- -I include -DNDEBUG cpp-verify --backend=z3 file.cpp cpp-verify --backend=cvc5 file.cpp @@ -149,6 +152,69 @@ pointer was ``array-to-pointer decay of automatic storage is unsupported``, and each use of a variable whose initializer was rejected added ``read of uninitialized local value``. +What a run prints +~~~~~~~~~~~~~~~~~ + +A run prints a verdict for each function it verifies, in source order, then +one line that counts them: + +.. code-block:: cpp + + namespace cv = cppverify; + + cv::spec int twice(int x) { return 2 * x; } + + int add_one(int x) + cv::pre(x < 100) + cv::post(cv::result == x + 1) + { + return x + 1; + } + + int add_two(int x) + cv::pre(x < 100) + cv::post(cv::result == twice(x)) + { + return x + 2; + } + +.. code-block:: text + + Verified: add_one [backend=z3] + summary.cpp:14:23: error: verification failed: add_two [fn_...::postcondition@14:23] (counterexample: result [ssa=__result_1] [type=i32] = 5, x [ssa=x_0] [type=i32] = 3; trace: return.return@16:3 value=5) [backend=z3] [reason=counterexample] + Summary: 1 verified, 1 failed in 0.1 s + +The summary counts each kind of verdict the run reported (verified, which +includes ``Certified`` and ``Proved (z3+lean)``, failed, unresolved, +bounded-safe, trusted, lowered, exported, interrupted), gives the reasons of +the unresolved ones (``2 unresolved (spec.fuel 1, solver.timeout 1)``), and +the time the run took. A function the verifier does not represent counts as +unresolved, ``construct.unsupported``. The summary covers every file of the +run, and is left out of ``--diagnostics-format=json``, whose records are the +whole output, of ``--list-functions``, and of archive replay +(``--obligation-in``), which prints a line per module. + +- ``--quiet`` prints only what needs attention: failures, unresolved results, + errors, warnings, bounded results, and interrupted functions, then the + summary. For the example, the error line and the summary. +- ``--verbose`` also lists each spec function that needs no check, a + non-recursive spec without a postcondition: ``Verified: spec axiom: twice``. + Such a line is not a verdict and the summary does not count it. Before 0.2 + it was printed always. +- ``--list-functions`` lists the functions a run would verify, each with its + location and kind (``function``, ``spec``, ``constexpr spec``, ``proof``, + ``trusted``), and verifies none; errors about functions the verifier does + not represent are printed as a run prints them: + +.. code-block:: text + + summary.cpp:3:14: spec twice + summary.cpp:5:5: function add_one + summary.cpp:12:5: function add_two + +``cpp-verify --help`` shows each time limit's default (``--timeout``, +30000 ms). + Stopping a run with Ctrl-C ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 5086808728f3189d60ccb64c4897dea7599ec3b3 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:52 +0530 Subject: [PATCH 109/119] docs: List --quiet, --verbose, and --list-functions in the book. --- website/source/book/part-ii/ch15-toolchain-and-flags.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/book/part-ii/ch15-toolchain-and-flags.rst b/website/source/book/part-ii/ch15-toolchain-and-flags.rst index f3156afc23..a495f77f29 100644 --- a/website/source/book/part-ii/ch15-toolchain-and-flags.rst +++ b/website/source/book/part-ii/ch15-toolchain-and-flags.rst @@ -37,6 +37,13 @@ CI pre-flight, and LLM/agent loops. Other flags ----------- +- ``cpp-verify --quiet`` — print only failures, unresolved results, errors, + and warnings, then the summary line that every text run ends with + (``Summary: 12 verified, 1 failed in 3.2 s``) +- ``cpp-verify --verbose`` — also list each spec function that needs no + check (``Verified: spec axiom: twice``), which the default leaves out +- ``cpp-verify --list-functions`` — list the functions a run would verify, + with their locations and kinds, and verify none - ``cpp-verify --backend={z3,cvc5,portfolio,race,bmc,lean}`` — verification engine: Z3 (the default), cvc5, both with agreement required (``portfolio``), both at once with the first proof or certified From 16ccfb664e72701559b1426b637d81332b251844 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:52 +0530 Subject: [PATCH 110/119] docs: Drop the spec axiom line from an example's output. --- website/source/language/functions-loops.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index 3ffc9c7bbe..4c69734279 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -86,7 +86,6 @@ outside the verified subset, or a lemma you decide to assume. trusted.cpp:2:28: Trusted: clamp_byte (contract assumed, not verified) trusted.cpp:6:28: Trusted: read_sensor (contract assumed, not verified) - Verified: spec axiom: sq trusted.cpp:16:39: Trusted: sq_nonnegative (contract assumed, not verified) Verified: sample [backend=z3] [trusts=clamp_byte,read_sensor,sq_nonnegative] From c8ffeeca262f5515eca6fcf4c2bfc9e787d7d731 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:53 +0530 Subject: [PATCH 111/119] docs: Drop the spec axiom lines from the examples' output. --- website/source/language/ghost-proofs.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/website/source/language/ghost-proofs.rst b/website/source/language/ghost-proofs.rst index a3c59e1be0..260db63f3a 100644 --- a/website/source/language/ghost-proofs.rst +++ b/website/source/language/ghost-proofs.rst @@ -170,7 +170,6 @@ integer, and adding to it never wraps. .. code-block:: text - Verified: spec axiom: pair_sum Verified: large [backend=z3] reads.cpp:13:24: error: verification failed: wraps [...::postcondition@13:24] (counterexample: ...) [backend=z3] [reason=counterexample] @@ -285,9 +284,7 @@ among ``a[0]`` to ``a[n - 1]``, inside its frame; ``index_past`` also reads .. code-block:: text Verified: spec reads: index_of - Verified: spec axiom: index_of reads.cpp:10:34: error: spec reads failed: index_past [backend=z3] [reason=counterexample] (a [type=pointer] = 0, n [type=math-i32] = 0) - Verified: spec axiom: index_past The counterexample is the empty buffer: with ``n = 0``, ``index_past`` reads ``a[0]``, and ``cppverify::reads(a, 0)`` declares no cell. From 816f6297ce6d3882811a15b67e2f636c9f50b36a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:53 +0530 Subject: [PATCH 112/119] docs: Drop the spec axiom line from the book's inductive example. --- website/source/book/part-ii/ch13-spec-and-proof-functions.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/website/source/book/part-ii/ch13-spec-and-proof-functions.rst b/website/source/book/part-ii/ch13-spec-and-proof-functions.rst index c8462ceabf..57033d1c10 100644 --- a/website/source/book/part-ii/ch13-spec-and-proof-functions.rst +++ b/website/source/book/part-ii/ch13-spec-and-proof-functions.rst @@ -1170,7 +1170,6 @@ where a finite derivation shows it. .. code-block:: text - Verified: spec axiom: edge Verified: inductive predicate: reach Verified: spec post: reach Verified: three_reaches_twelve [backend=z3] From 3e93e5ae5430ca240ad499f38a0cf9f9c5f42da3 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:53 +0530 Subject: [PATCH 113/119] docs: Note the summary line and the new output options in the 0.2.0 release notes. --- website/source/release-notes.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index b7e2f1f5aa..6ab5290185 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -483,6 +483,15 @@ are errors at the statement (:doc:`language/functions-loops`). Running cpp-verify ~~~~~~~~~~~~~~~~~~ +**A summary, and what to print.** A run now ends with one line that counts +its verdicts by kind, with the reasons of the unresolved ones and the time it +took (``Summary: 12 verified, 1 failed, 2 unresolved (spec.fuel 1, +solver.timeout 1) in 3.2 s``). ``--quiet`` prints only what needs attention, +``--list-functions`` lists what a run would verify without verifying it, and +the ``Verified: spec axiom: f`` line of a spec that needs no check is printed +only with ``--verbose``. ``--help`` shows the default of ``--timeout`` +(:doc:`language/tooling`). + **Ctrl-C stops the run.** Ctrl-C printed an LLVM crash report and the run went on, or crashed. It now stops every solver at once, reports each unfinished function as ``Interrupted``, and exits with status 130 From dd4c4211898782b68e0ad2973e19beb6adeb4406 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Tue, 27 Oct 2026 16:44:54 +0530 Subject: [PATCH 114/119] docs: Describe how a run counts its verdicts for the summary. --- docs/ARCHITECTURE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ddbf06fc9c..6471c56e45 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2384,6 +2384,10 @@ expression being converted (`ASTConverter::CurrentRange`), unless the site names a place of its own, such as a function for the checks made after every function is converted. It is printed with Clang's `TextDiagnostic`, line and caret included, as a compiler error is, and its JSON record carries the range. +A text run ends with a summary line: `printDiagnostics` counts each verdict +it prints into the run's `VerifySummary` (a first error that carries a +verdict counts, a `--verbose` spec axiom line does not), and the driver +prints the counts of every file of the run with its time. Its message names the construct as C++ does (`unsupportedConstruct` for the statements and expressions without a site of their own; Clang's class name only in parentheses for those it has no name for), and each error is raised From 92ae2ea41d8d307ee8013d89e3bc79b2f82c80ee Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 28 Oct 2026 00:11:35 +0530 Subject: [PATCH 115/119] test: Compare serial and parallel output without the summary's time in backend_execution.test. --- clang/test/Verify/suite/backend_execution.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/backend_execution.test b/clang/test/Verify/suite/backend_execution.test index 76370c4652..17e92cb3f7 100644 --- a/clang/test/Verify/suite/backend_execution.test +++ b/clang/test/Verify/suite/backend_execution.test @@ -49,7 +49,10 @@ # # RUN: not %cpp-verify --jobs=1 %S/Inputs/backend_parallel_fail.cpp -- > %t.serial 2>&1 # RUN: not %cpp-verify --jobs=4 %S/Inputs/backend_parallel_fail.cpp -- > %t.parallel 2>&1 -# RUN: diff %t.serial %t.parallel +# The summary's time may differ between runs. +# RUN: sed 's/ in [0-9.]* s$//' %t.serial > %t.serial.untimed +# RUN: sed 's/ in [0-9.]* s$//' %t.parallel > %t.parallel.untimed +# RUN: diff %t.serial.untimed %t.parallel.untimed # # RUN: %cpp-verify --obligation-out=%t.obligations \ # RUN: %S/Inputs/backend_cache_base.cpp -- From f093c262aaf34f493b9f4063e16498c16a89707c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 28 Oct 2026 00:11:35 +0530 Subject: [PATCH 116/119] test: Compare serial and parallel output without the summary's time in parallel_functions.test. --- clang/test/Verify/suite/parallel_functions.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/parallel_functions.test b/clang/test/Verify/suite/parallel_functions.test index 680968f0df..5af7fd5464 100644 --- a/clang/test/Verify/suite/parallel_functions.test +++ b/clang/test/Verify/suite/parallel_functions.test @@ -5,7 +5,10 @@ # # RUN: not %cpp-verify --jobs=1 %S/Inputs/parallel_functions.cpp -- > %t.serial 2>&1 # RUN: not %cpp-verify --jobs=4 %S/Inputs/parallel_functions.cpp -- > %t.parallel 2>&1 -# RUN: diff %t.serial %t.parallel +# The summary's time may differ between runs. +# RUN: sed 's/ in [0-9.]* s$//' %t.serial > %t.serial.untimed +# RUN: sed 's/ in [0-9.]* s$//' %t.parallel > %t.parallel.untimed +# RUN: diff %t.serial.untimed %t.parallel.untimed # RUN: FileCheck %s < %t.parallel # # RUN: not %cpp-verify --jobs=1 --diagnostics-format=json \ From e31885b8508f898fc3eaa5e8ca495cda0c477dd7 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 28 Oct 2026 00:11:35 +0530 Subject: [PATCH 117/119] test: Compare two runs without the summary's time in obligation_ir.cpp. --- clang/test/Verify/suite/obligation_ir.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/obligation_ir.cpp b/clang/test/Verify/suite/obligation_ir.cpp index f8f2fddeab..275fc316d6 100644 --- a/clang/test/Verify/suite/obligation_ir.cpp +++ b/clang/test/Verify/suite/obligation_ir.cpp @@ -1,7 +1,9 @@ // RUN: %clang -std=c++17 -fverify-contracts -fsyntax-only %s // RUN: %cpp-verify --lower-only --dump-ir=3 %s -- > %t.first 2>&1 // RUN: %cpp-verify --lower-only --dump-ir=3 %s -- > %t.second 2>&1 -// RUN: diff %t.first %t.second +// RUN: sed 's/ in [0-9.]* s$//' %t.first > %t.first.untimed +// RUN: sed 's/ in [0-9.]* s$//' %t.second > %t.second.untimed +// RUN: diff %t.first.untimed %t.second.untimed // RUN: FileCheck %s --check-prefix=OBLIGATION < %t.first // RUN: %cpp-verify --int-encoding=bitvector --lower-only --dump-ir=3,4 %s -- 2>&1 | FileCheck %s --check-prefix=LAYERS // RUN: %cpp-verify %s -- 2>&1 | FileCheck %s --check-prefix=Z3 From a7bac1d38d570e04a6c673ef3e847655e2150272 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 28 Oct 2026 00:11:36 +0530 Subject: [PATCH 118/119] test: Compare two runs without the summary's time in logic_function_ir.cpp. --- clang/test/Verify/suite/logic_function_ir.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/logic_function_ir.cpp b/clang/test/Verify/suite/logic_function_ir.cpp index cef7efdc94..ed0e65f337 100644 --- a/clang/test/Verify/suite/logic_function_ir.cpp +++ b/clang/test/Verify/suite/logic_function_ir.cpp @@ -1,7 +1,9 @@ // RUN: %clang -std=c++17 -fverify-contracts -fsyntax-only %s // RUN: %cpp-verify --lower-only --dump-ir=3 %s -- > %t.first 2>&1 // RUN: %cpp-verify --lower-only --dump-ir=3 %s -- > %t.second 2>&1 -// RUN: diff %t.first %t.second +// RUN: sed 's/ in [0-9.]* s$//' %t.first > %t.first.untimed +// RUN: sed 's/ in [0-9.]* s$//' %t.second > %t.second.untimed +// RUN: diff %t.first.untimed %t.second.untimed // RUN: FileCheck %s --input-file=%t.first --check-prefix=LOGIC // RUN: %cpp-verify %s -- 2>&1 | FileCheck %s --check-prefix=Z3 // RUN: %cpp-verify --backend=lean --lean-out=%t.lean %s -- From 3b3a1cdab896a94a0a3f11da3f586e42ce0ea3fe Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 28 Oct 2026 03:56:06 +0530 Subject: [PATCH 119/119] test: Compare the IR dumps of two runs without the summary's time in parallel_functions.test. --- clang/test/Verify/suite/parallel_functions.test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/test/Verify/suite/parallel_functions.test b/clang/test/Verify/suite/parallel_functions.test index 5af7fd5464..2934017920 100644 --- a/clang/test/Verify/suite/parallel_functions.test +++ b/clang/test/Verify/suite/parallel_functions.test @@ -27,7 +27,9 @@ # RUN: %S/Inputs/parallel_functions.cpp -- > %t.serial.ir 2>&1 # RUN: %cpp-verify --jobs=4 --lower-only --dump-ir=all \ # RUN: %S/Inputs/parallel_functions.cpp -- > %t.parallel.ir 2>&1 -# RUN: cmp %t.serial.ir %t.parallel.ir +# RUN: sed 's/ in [0-9.]* s$//' %t.serial.ir > %t.serial.ir.untimed +# RUN: sed 's/ in [0-9.]* s$//' %t.parallel.ir > %t.parallel.ir.untimed +# RUN: cmp %t.serial.ir.untimed %t.parallel.ir.untimed # # One budget for all queries of a function. # RUN: not %cpp-verify --jobs=4 --function-timeout=1 \