Repository navigation
E3: verification failures as Clang diagnostics - #104
Merged
Merged
Conversation
…peration after its own checks.
…in obligation modules.
…xpression begins.
… calls a removed function.
…nd reference flags, in results.
This was referenced Oct 28, 2026
…S runner can meet.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A failure printed as one line: the obligation's internal identity, then every SSA version of every variable with its sort, then a trace in internal event names. On
abswithout its precondition, the base (#99) prints:Across the suite such lines had a median of 365 characters (finding F31 of the 0.2 plan). The compiler path was worse:
clang++ -fverify-contractswrote the verifier's text to stderr and then reported one genericcppverify: verification failederror, so-w,-Werror, colors, and-fdiagnostics-format=sarif(which printed"results": []) did not apply to it.Now a failure is a Clang error at the failing check, and its counterexample and path are notes:
This is plan item E3.
The format
[postcondition],[overflow], ...), not its internal ID; the location already says where the check is. The source line is shown, the failing expression underlined and the caret at its operator, as Clang underlines an expression:~~~~~~~~~~~^~~~undercv::result < 3. An expression that runs to another line gets the caret alone. A failure explained by more than its model, such as one confirmed by a proof, keeps its explanation in parentheses.resultlast.->(y = 0 -> 1 -> 4).i (line 8) = 0 -> 1, i (line 14) = 0 -> 1).&r = 4, since its value is the address it refers to.takes the true branch,enters the loop,starts an arbitrary iteration of the loop, where its condition is true,calls add(9),stores 0 at address 1,creates a local object at address 4,ends the lifetime of a local object,deletes the object at address 4,returns 4. A step the model does not decide readsperhaps ....-ftemplate-backtrace-limitdoes.expanded from macro 'assert'notes for oneassert).Verified: f [backend=z3]) stay one line each, as before.messageis now the error's text: the values and the path stay inmodelandtrace.Three faithfulness fixes the new format needed
The old line listed what the solver's model held, with SSA names that hinted at how to read it. Printed as a sequence of values, three things in it would have been wrong, so this PR fixes them at their source.
emitInactiveFrame). Listing every version therefore showedy = 0 -> 1 -> 0 -> 1 -> 4 -> 4for aythat never returned to 0.PassiveModelVariable::Guard).DiagnosticVariable::Guard, the literaltruefor a version defined on every path. It is a shared, immutable term, validated like a trace guard; simplification drops it if it calls a removed function, which leaves the version's path unknown.cppverify.obligation/4adds it to the diagnostic metadata, never hashed, and reads/3to/1, whose versions have no guard. A replayed counterexample therefore lists the same values as the source run; one from an older archive lists every version, as before.VerifyModelValue::OnPath). A version whose condition is false is left out of the text, and JSON marks it"on_path": false.y = 0 -> 1 -> 4.[type=pointer].VSourceVariable::Referenceis recorded from the declaration's type and carried toVerifyModelValue::Reference, and the text writes&r = 4, which is what&revaluates to in C++.delete, and areturnemitted their trace event before the checks of their own operation. So a failing overflow check inreturn -x;listedreturn.return@12:3 value=-2147483648, the wrapped value, and one inr = r + 1;listedheap-write.store@6:5 address=4 value=-2147483648.Ranges
To underline the whole expression, every
VExprnow carries the start of its source (BeginLoc, Clang'sgetBeginLoc) besideLoc(getExprLoc, the operator) andEndLoc. Every copy ofEndLocin conversion, passivization, spec inlining, lowering, simplification, induction, and the Z3 and cvc5 adapters copiesBeginLoctoo. The result isVerifyResult::BeginLocation. Before, the range ran from the caret:cv::result >= 0 && cv::result <= 100was underlined from&&, which suggested that only the right conjunct had failed.The compiler
-fverify-contractsreports every result through the compiler'sDiagnosticsEngine. The verifier's thread collectsVerifyDiagnosticParts (VerifyOptions::Parts) instead of printing them, andfinishCppVerifyreports them on the main thread, which the engine requires:err_cppverify_resultwarn_cppverify_result, in-Wcppverifyremark_cppverify_result, in-Rcppverify, at the function's declarationnote_cppverify_resultSo
-Wno-cppverify,-Werror=cppverify,-w, colors, and SARIF apply, and clangd can show them.err_fe_cppverify_failedremains only for a run that fails without a result of its own.DIAG_SIZE_FRONTENDgrows from 200 to 250 for the four diagnostics.Where the design comes from
TextDiagnosticfor the error, the caret at the operator inside the expression's range, notes, macro expansion notes,-ftemplate-backtrace-limitfor long paths.Tests
clang/test/Verify/compile_diagnostics.cpp(new): the compiler path, covering the error with its range and notes,-Rcppverifyremarks at their functions, the-Wcppverifywarning under-Wno-cppverifyand-Werror=cppverify, an unresolved result as an error, the error count, SARIF results with their rule IDs, and-fno-verify.clang/test/Verify/suite/failure_notes.cpp(new), under--strict-whitespace, so the underline's columns are checked:&r;on_path;contracts_link_check.testexpects the proofs of a compilation as remarks at their functions.obligation_archive.testreplays a record of schema v3 and rejects an invalid guard flag; its Python helper reads schema v4 and writes v3.The documentation's failures were regenerated from the verifier's output: 61 failures on 14 pages and the README. The prose that explains the format was rewritten in the reference (commands and flags, functions and loops) and the book (chapters 12 and 16), with entries in the release notes,
docs/ARCHITECTURE.md, anddocs/DESIGN.md.website/scripts/check-doc-examples.pypasses on the pages (261 examples checked, 105 against the verdicts their pages show, 0 failed).The whole Verify suite passes on a release build: 490 tests, 1 unsupported (1396 s at 48 workers).
On the base (#99),
compile_diagnostics.cppandfailure_notes.cppfail from their first check: the compiler has no-Wcppverifygroup, reports one generic error, and every failure is the one-line format.Each mutant below, applied alone and rebuilt, is caught by
compile_diagnostics.cpp,failure_notes.cpp,local_names.cpp, orinductive_counterexamples.cpp(31 of 31):&resultis not listed lastperhapson_pathStacked on #99.
Also: two test budgets
confirmation_invariants.cpp(from #97) andextent_separation.cpptimed out on macOS runners in the last round (#95, #97, #98), each looking for a failure the solver finds locally in 0.4 s and 1.7 s. Run 24 at a time beside each other here, they took up to 3.1 s and 4.9 s, against budgets of 5 s and 30 s; the runners' slower cores cross them. Their runs now get 20 s and 120 s.confirmation_invariants.cppstill checks that the confirmation stops in under a second, which is what it is about.