diff --git a/README.md b/README.md index 7e6c81fcbb..edb0712e04 100644 --- a/README.md +++ b/README.md @@ -157,17 +157,26 @@ and `docs/UB-CHECKING.md`. ./build/bin/cpp-verify --timeout=10000 --solver-rlimit=2000000 file.cpp ./build/bin/cpp-verify --int-encoding=bitvector file.cpp # auto (default) | integer | bitvector ./build/bin/cpp-verify --diagnostics-format=json file.cpp # JSON Lines, for editors/CI +./build/bin/cpp-verify --diagnostics-format=sarif file.cpp # SARIF 2.1.0, for code scanning ./build/bin/cpp-verify --obligation-out=goals.cpv file.cpp # backend-neutral archive ./build/bin/cpp-verify --dump-ir=1,2,3,4 file.cpp # VCR, passive, Obligation IR, Z3 +./build/bin/cpp-verify --dump-smt2=queries file.cpp # each solver query as an SMT-LIB script ``` Z3 runs support deterministic isolated solving (`--jobs`) and positive-proof reuse (`--proof-cache`). A query past `--timeout` or `--solver-rlimit` is reported as *unresolved* rather than hanging — never as verified. `--int-encoding` only chooses how machine integers reach the solver; every choice is exact, so it can change speed -but never a verdict. A failure names the function and the obligation that failed, -`::@line:column`, e.g. -`verification failed: abs [fn_5f5a3361627369::overflow@5:3]`. +but never a verdict. A failure is a compiler error at the failing source, naming +the function and the kind of check, with its counterexample and path as notes; +without its precondition, `abs` gives: + +```text +abs.cpp:4:3: error: verification failed: abs [overflow] [backend=z3] [reason=counterexample] + 4 | return x < 0 ? -x : x; + | ^ +abs.cpp:4:3: note: counterexample: x = -2147483648 +``` Chained modular calls (e.g. `return inc(inc(x))`) are lowered to temporaries automatically. See [Chapter 17](https://swayaminsync.github.io/cpp-verify/book/part-ii/ch17-backends-modular-calls.html). diff --git a/clang/cmake/modules/CppVerifyZ3.cmake b/clang/cmake/modules/CppVerifyZ3.cmake index d8e3ee9af0..d0c3b5c787 100644 --- a/clang/cmake/modules/CppVerifyZ3.cmake +++ b/clang/cmake/modules/CppVerifyZ3.cmake @@ -85,7 +85,7 @@ function(cppverify_z3_build_external) -DCMAKE_INSTALL_PREFIX:PATH=${_install} -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON -DZ3_BUILD_LIBZ3_SHARED:BOOL=OFF - -DZ3_BUILD_EXECUTABLE:BOOL=OFF + -DZ3_BUILD_EXECUTABLE:BOOL=ON -DZ3_BUILD_TEST_EXECUTABLES:BOOL=OFF -DZ3_BUILD_DOCUMENTATION:BOOL=OFF -DZ3_ENABLE_EXAMPLE_TARGETS:BOOL=OFF @@ -107,6 +107,9 @@ function(cppverify_z3_build_external) # sources include the headers must depend on this target as well. set_property(GLOBAL PROPERTY CPPVERIFY_Z3_EP_TARGET cppverify_z3_ep) set_property(GLOBAL PROPERTY CPPVERIFY_Z3_ORIGIN vendored) + # Z3's own shell, which replays the scripts --dump-smt2 writes in tests. + set_property(GLOBAL PROPERTY CPPVERIFY_Z3_SHELL + "${_install}/bin/z3${CMAKE_EXECUTABLE_SUFFIX}") endfunction() function(cppverify_z3_try_system out_var) @@ -143,6 +146,11 @@ function(cppverify_z3_try_system out_var) if(_target) message(STATUS "CppVerify: using system Z3 (${_target})") set_property(GLOBAL PROPERTY CPPVERIFY_Z3_ORIGIN system) + find_program(CPPVERIFY_SYSTEM_Z3_SHELL z3) + if(CPPVERIFY_SYSTEM_Z3_SHELL) + set_property(GLOBAL PROPERTY CPPVERIFY_Z3_SHELL + "${CPPVERIFY_SYSTEM_Z3_SHELL}") + endif() endif() set(${out_var} "${_target}" PARENT_SCOPE) endfunction() diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index b001556966..c0b5b1587f 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -116,6 +116,10 @@ def err_fe_invalid_plugin_name : Error< "unable to find plugin '%0'">; def err_fe_cppverify_failed : Error< "cppverify: %0">; +def err_cppverify_result : Error<"%0">; +def warn_cppverify_result : Warning<"%0">, InGroup; +def remark_cppverify_result : Remark<"%0">, InGroup; +def note_cppverify_result : Note<"%0">; def err_fe_expected_compiler_job : Error< "unable to handle compilation, expected exactly one compiler job in '%0'">; def err_fe_expected_clang_command : Error< diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 698f2cf108..82e548a976 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1798,6 +1798,9 @@ Results can be filtered by function name by passing def : DiagGroup<"frame-larger-than=", [BackendFrameLargerThan]>; def BackendPlugin : DiagGroup<"backend-plugin">; def RemarkBackendPlugin : DiagGroup<"remark-backend-plugin">; +// cpp-verify's results in a compilation: its warnings, and its proofs as +// remarks. +def CppVerify : DiagGroup<"cppverify">; def BackendOptimizationRemark : DiagGroup<"pass">; def BackendOptimizationRemarkMissed : DiagGroup<"pass-missed">; def BackendOptimizationRemarkAnalysis : DiagGroup<"pass-analysis">; diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h index 63b5e6a28a..2d7e32579b 100644 --- a/clang/include/clang/Basic/DiagnosticIDs.h +++ b/clang/include/clang/Basic/DiagnosticIDs.h @@ -36,7 +36,7 @@ enum class Group; enum { DIAG_SIZE_COMMON = 300, DIAG_SIZE_DRIVER = 400, - DIAG_SIZE_FRONTEND = 200, + DIAG_SIZE_FRONTEND = 250, DIAG_SIZE_SERIALIZATION = 120, DIAG_SIZE_LEX = 500, DIAG_SIZE_PARSE = 800, diff --git a/clang/lib/Parse/ParseCppVerify.cpp b/clang/lib/Parse/ParseCppVerify.cpp index c50ce935dd..cd165319d1 100644 --- a/clang/lib/Parse/ParseCppVerify.cpp +++ b/clang/lib/Parse/ParseCppVerify.cpp @@ -1146,6 +1146,16 @@ StmtResult Parser::ParseContractAssert() { ContractAssertStmt(CALoc, LParenLoc, RParenLoc, Cond.get(), By); } +/// \p Fn, the function a hide, reveal, or reveal_with_fuel names: a spec +/// template specialization such as `at<4>` is an overload set until it is +/// resolved to the one specialization it names. +static ExprResult resolveNamedFunction(Sema &Actions, ExprResult Fn) { + if (Fn.isUsable() && + Fn.get()->getType() == Actions.getASTContext().OverloadTy) + return Actions.CheckPlaceholderExpr(Fn.get()); + return Fn; +} + /// Parse cppverify::reveal_with_fuel(fn, depth); StmtResult Parser::ParseRevealWithFuel() { SourceLocation Loc = ConsumeCppVerify(); @@ -1157,7 +1167,8 @@ StmtResult Parser::ParseRevealWithFuel() { } SourceLocation LParenLoc = ConsumeParen(); - ExprResult Fn = finishCppVerifyExpression(ParseAssignmentExpression()); + ExprResult Fn = resolveNamedFunction( + Actions, finishCppVerifyExpression(ParseAssignmentExpression())); if (Fn.isInvalid() || Tok.isNot(tok::comma)) { if (!Fn.isInvalid()) Diag(Tok, diag::err_contract_expected_comma) @@ -1193,7 +1204,8 @@ StmtResult Parser::ParseHideSpec() { return StmtError(); } SourceLocation LParenLoc = ConsumeParen(); - ExprResult Fn = finishCppVerifyExpression(ParseAssignmentExpression()); + ExprResult Fn = resolveNamedFunction( + Actions, finishCppVerifyExpression(ParseAssignmentExpression())); if (Fn.isInvalid()) { SkipUntil(tok::r_paren, StopAtSemi); return StmtError(); @@ -1216,7 +1228,8 @@ StmtResult Parser::ParseRevealSpec() { return StmtError(); } SourceLocation LParenLoc = ConsumeParen(); - ExprResult Fn = finishCppVerifyExpression(ParseAssignmentExpression()); + ExprResult Fn = resolveNamedFunction( + Actions, finishCppVerifyExpression(ParseAssignmentExpression())); if (Fn.isInvalid()) { SkipUntil(tok::r_paren, StopAtSemi); return StmtError(); diff --git a/clang/lib/Verify/Backend/CVC5Backend.cpp b/clang/lib/Verify/Backend/CVC5Backend.cpp index bc98d9970b..a81f96cbd6 100644 --- a/clang/lib/Verify/Backend/CVC5Backend.cpp +++ b/clang/lib/Verify/Backend/CVC5Backend.cpp @@ -4,6 +4,7 @@ #include "ChildProcess.h" #include "ObligationSerialization.h" #include "ObligationSimplify.h" +#include "QueryDump.h" #include "TaskGroup.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/STLFunctionalExtras.h" @@ -2406,6 +2407,18 @@ static bool printsSequencesReversed(const std::string &SolverPath) { return Reversed; } +/// The options cvc5 runs with, as names and values; a flag has no value. +static std::vector> +cvc5Options(unsigned TimeoutMs, unsigned ResourceLimit) { + std::vector> Options = { + {"seed", "0"}, {"sat-random-seed", "0"}, {"dump-models", ""}}; + if (TimeoutMs != 0) + Options.push_back({"tlimit-per", std::to_string(TimeoutMs)}); + if (ResourceLimit != 0) + Options.push_back({"rlimit-per", std::to_string(ResourceLimit)}); + return Options; +} + static SolverRun runCVC5(const std::string &SolverPath, llvm::StringRef Script, unsigned TimeoutMs, unsigned ResourceLimit, uint64_t MemoryLimitMB, @@ -2452,12 +2465,9 @@ static SolverRun runCVC5(const std::string &SolverPath, llvm::StringRef Script, } llvm::FileRemover RemoveError(ErrorPath); - std::vector OwnedArgs = {SolverPath, "--lang=smt2", "--seed=0", - "--sat-random-seed=0", "--dump-models"}; - if (TimeoutMs != 0) - OwnedArgs.push_back("--tlimit-per=" + std::to_string(TimeoutMs)); - if (ResourceLimit != 0) - OwnedArgs.push_back("--rlimit-per=" + std::to_string(ResourceLimit)); + std::vector OwnedArgs = {SolverPath, "--lang=smt2"}; + for (const auto &[Name, Value] : cvc5Options(TimeoutMs, ResourceLimit)) + OwnedArgs.push_back("--" + Name + (Value.empty() ? "" : "=" + Value)); OwnedArgs.push_back(InputPath.str().str()); llvm::SmallVector Args; for (const std::string &Argument : OwnedArgs) @@ -2777,8 +2787,21 @@ CVC5VerifyBackend::verifyQuery(const ObligationModule &Module, } Budget = static_cast(Remaining); } + QueryDump Dump; + if (queryDumpEnabled()) { + // The command line's options as commands, so that `cvc5 FILE` runs + // the query as cpp-verify did. + std::string Options; + for (const auto &[Name, Value] : cvc5Options(Budget, ResourceLimit)) + Options += "(set-option :" + Name + " " + + (Value.empty() ? "true" : Value) + ")\n"; + Dump.start(&Module, Query, "cvc5", "at " + SolverPath, Options, *Script); + } SolverRun Run = runCVC5(SolverPath, *Script, Budget, ResourceLimit, MemoryLimitMB, &Cancelled); + Dump.finish( + Run.Output ? llvm::StringRef(*Run.Output).split('\n').first.trim().str() + : "no answer (" + Run.Failure.Message + ")"); auto giveUp = [&](llvm::StringRef Outcome) { RefinementDecision Exhausted = Refinement.exhausted(Outcome); Result.Reason = Exhausted.Reason; @@ -2809,6 +2832,8 @@ CVC5VerifyBackend::verifyQuery(const ObligationModule &Module, Result.Status = VerifyStatus::Failed; Result.Reason = VerifyReason::Counterexample; Result.CertifiedWith = std::move(Certified.Evidence); + describeCounterexample(Module, *Query, Candidate, Result, + checkLimits()); return Result; } } else { @@ -2876,6 +2901,8 @@ CVC5VerifyBackend::verifyQuery(const ObligationModule &Module, Result.Status = VerifyStatus::Failed; Result.Reason = VerifyReason::Counterexample; Result.CertifiedWith = Certified.Evidence; + describeCounterexample(Module, *Query, Candidate, Result, + checkLimits()); return Result; } if (Decision.Next == RefinementDecision::Action::Stop) { @@ -2897,6 +2924,8 @@ VerifyResult CVC5VerifyBackend::verifyObligation(const ObligationModule &Module, Result.ObligationId = Item.StableId.empty() ? Item.Id : Item.StableId; Result.ObligationType = Item.Kind; Result.Location = Item.Loc; + Result.BeginLocation = Item.BeginLoc; + Result.EndLocation = Item.EndLoc; Result.Source = Item.Source; if (Result.Status == VerifyStatus::Unresolved) Result.Message = "proof obligation " + Result.ObligationId + diff --git a/clang/lib/Verify/Backend/Certify.cpp b/clang/lib/Verify/Backend/Certify.cpp index 66a8254347..5f759cb8cd 100644 --- a/clang/lib/Verify/Backend/Certify.cpp +++ b/clang/lib/Verify/Backend/Certify.cpp @@ -4187,8 +4187,10 @@ verify::candidateValues(const ObligationModule &Module, const LogicExpr &Query, It != Module.DiagnosticVariables.end() ? It->second.DisplayName : Name; Value.InternalName = Name; Value.Sort = Sort; - if (It != Module.DiagnosticVariables.end()) + if (It != Module.DiagnosticVariables.end()) { Value.Source = It->second.Source; + Value.Reference = It->second.Reference; + } if (std::optional Given = Model.constant(Name, Sort)) Value.Value = formatLogicValue(*Given); Values.push_back(std::move(Value)); @@ -4196,6 +4198,68 @@ verify::candidateValues(const ObligationModule &Module, const LogicExpr &Query, return Values; } +void verify::describeCounterexample(const ObligationModule &Module, + const LogicExpr &Query, + CandidateModel &Model, VerifyResult &Result, + const CertifyLimits &Limits) { + auto evaluated = [&](const LogicExpr *Term) -> std::optional { + if (!Term) + return std::nullopt; + return evaluateTerm(Module, *Term, Model, Limits); + }; + auto truth = [&](const LogicExpr *Term) -> std::optional { + std::optional Value = evaluated(Term); + if (!Value || Value->K != LogicValue::Kind::Bool) + return std::nullopt; + return Value->Truth; + }; + const Obligation *Failing = nullptr; + for (const Obligation &Item : Module.Obligations) + if (Item.CounterexampleQuery.get() == &Query) { + Failing = &Item; + break; + } + if (Failing) + Result.Assignment = counterexampleAssignment(*Failing, Model); + + // In the order Z3's models list them, by internal name. + Result.Model = candidateValues(Module, Query, Model); + llvm::sort(Result.Model, + [](const VerifyModelValue &A, const VerifyModelValue &B) { + return A.InternalName < B.InternalName; + }); + for (VerifyModelValue &Value : Result.Model) + if (auto It = Module.DiagnosticVariables.find(Value.InternalName); + It != Module.DiagnosticVariables.end() && It->second.Guard) + Value.OnPath = truth(It->second.Guard.get()); + + Result.Trace.clear(); + const uint64_t Count = + Failing ? std::min(Failing->TraceEventCount, + Module.TraceEvents.size()) + : 0; + for (uint64_t I = 0; I != Count; ++I) { + const DiagnosticTraceEvent &Event = Module.TraceEvents[I]; + std::optional Active = truth(Event.Guard.get()); + if (Active == false) + continue; + VerifyTraceEvent Step; + Step.Kind = Event.Kind; + Step.Message = Event.Message; + Step.Loc = Event.Loc; + Step.Source = Event.Source; + Step.Active = Active; + for (const DiagnosticTraceValue &Value : Event.Values) { + std::optional Given = evaluated(Value.Value.get()); + Step.Values.push_back( + {Value.Label, Value.Value->Sort, + Given ? std::optional(formatLogicValue(*Given)) + : std::nullopt}); + } + Result.Trace.push_back(std::move(Step)); + } +} + std::string verify::shownApplication(const ObligationModule &Module, const LogicExpr &Application, CandidateModel &Model, diff --git a/clang/lib/Verify/Backend/Certify.h b/clang/lib/Verify/Backend/Certify.h index 807761b552..9b4dafc860 100644 --- a/clang/lib/Verify/Backend/Certify.h +++ b/clang/lib/Verify/Backend/Certify.h @@ -252,6 +252,16 @@ std::vector candidateValues(const ObligationModule &Module, const LogicExpr &Query, CandidateModel &Model); +/// Describes in \p Result the counterexample \p Model gives \p Query, an +/// obligation's counterexample query, as a failure shows it: the source +/// variables' values, each version marked on the path or off it, and the +/// steps of the obligation's path, each with its values. What the model +/// leaves open stays undetermined. +void describeCounterexample(const ObligationModule &Module, + const LogicExpr &Query, CandidateModel &Model, + VerifyResult &Result, + const CertifyLimits &Limits = {}); + /// \p Application with its arguments' values under \p Model, as /// "fibo(1000000000)". std::string shownApplication(const ObligationModule &Module, diff --git a/clang/lib/Verify/Backend/Induction.cpp b/clang/lib/Verify/Backend/Induction.cpp index c91b84bdec..2f422fc21e 100644 --- a/clang/lib/Verify/Backend/Induction.cpp +++ b/clang/lib/Verify/Backend/Induction.cpp @@ -21,6 +21,7 @@ std::unique_ptr node(LogicExpr::Kind K, LogicSort Sort, Node->Sort = Sort; if (At) { Node->Loc = At->Loc; + Node->BeginLoc = At->BeginLoc; Node->EndLoc = At->EndLoc; Node->Source = At->Source; } @@ -722,6 +723,7 @@ ObligationModule copyObligationModule(const ObligationModule &Module) { Same.StableId = Item.StableId; Same.Kind = Item.Kind; Same.Loc = Item.Loc; + Same.BeginLoc = Item.BeginLoc; Same.EndLoc = Item.EndLoc; Same.Source = Item.Source; Same.TraceEventCount = Item.TraceEventCount; diff --git a/clang/lib/Verify/Backend/Obligation.cpp b/clang/lib/Verify/Backend/Obligation.cpp index f5f5ad7167..68945a6a5a 100644 --- a/clang/lib/Verify/Backend/Obligation.cpp +++ b/clang/lib/Verify/Backend/Obligation.cpp @@ -558,6 +558,15 @@ static bool validateSource(const ObligationSource &Source, std::string &Error) { Error = "source range end precedes its start"; return false; } + if ((Source.BeginLine == 0) != (Source.BeginColumn == 0)) { + Error = "source range has an incomplete begin"; + return false; + } + if (Source.BeginLine > Source.Line || + (Source.BeginLine == Source.Line && Source.BeginColumn > Source.Column)) { + Error = "source range begins after its anchor"; + return false; + } return true; } @@ -1960,8 +1969,10 @@ class ObligationBuilder { std::unique_ptr fromVExpr(const VExpr *E) { auto Result = fromVExprImpl(E); - if (Result && E) + if (Result && E) { + Result->BeginLoc = E->BeginLoc; Result->EndLoc = E->EndLoc; + } return Result; } @@ -2389,8 +2400,9 @@ class ObligationBuilder { fail("invalid diagnostic variable sort in obligation lowering"); continue; } - M.DiagnosticVariables[InternalName] = { - Variable.DisplayName, Sort, Variable.Loc, Variable.EndLoc, {}}; + DiagnosticVariable &Diagnostic = M.DiagnosticVariables[InternalName]; + Diagnostic = {Variable.DisplayName, Sort, Variable.Loc, Variable.EndLoc}; + Diagnostic.Reference = Variable.Reference; } auto traceKind = [](PassiveTraceKind Kind) { switch (Kind) { @@ -2432,6 +2444,17 @@ class ObligationBuilder { {Value.Label, lowerDiagnosticExpr(Value.Value.get())}); M.TraceEvents.push_back(std::move(Lowered)); } + auto Everywhere = std::make_unique(VCExpr::BoolLit); + setBoolSort(*Everywhere); + Everywhere->BoolVal = true; + const LogicExprRef OnEveryPath(std::move(Everywhere)); + for (const auto &[InternalName, Variable] : P.ModelVariables) + if (auto It = M.DiagnosticVariables.find(InternalName); + It != M.DiagnosticVariables.end()) + It->second.Guard = + Variable.Guard + ? LogicExprRef(lowerDiagnosticExpr(Variable.Guard.get())) + : OnEveryPath; std::vector> EntryAssumes; for (const auto &Entry : P.EntryAssumes) @@ -2523,6 +2546,7 @@ class ObligationBuilder { ConditionWP->Premise = It->second.empty() ? "fact" : It->second; } ConditionWP->Loc = Condition->Loc; + ConditionWP->BeginLoc = Condition->BeginLoc; ConditionWP->EndLoc = Condition->EndLoc; return ConditionWP; }; @@ -2539,12 +2563,14 @@ class ObligationBuilder { Item.Kind = Kind; Item.Note = Note; Item.Loc = Condition->Loc; + Item.BeginLoc = Condition->BeginLoc; Item.EndLoc = Condition->EndLoc; Item.TraceEventCount = TraceEventCount; Item.Id = Identity + "::obligation:" + std::to_string(++ObligationIndex); Item.Goal = makeGoal(Condition); Item.CounterexampleQuery = vcNot(cloneLogicExpr(Item.Goal.get())); Item.CounterexampleQuery->Loc = Condition->Loc; + Item.CounterexampleQuery->BeginLoc = Condition->BeginLoc; Item.CounterexampleQuery->EndLoc = Condition->EndLoc; M.Obligations.push_back(std::move(Item)); }; @@ -2612,6 +2638,9 @@ verify::validateObligationModule(const ObligationModule &M) { for (const DiagnosticTraceValue &Value : Event.Values) Roots.push_back(Value.Value.get()); } + for (const auto &[InternalName, Variable] : M.DiagnosticVariables) + if (Variable.Guard) + Roots.push_back(Variable.Guard.get()); Roots.push_back(M.CorrectnessGoal.get()); Roots.push_back(M.CounterexampleQuery.get()); for (const Obligation &Item : M.Obligations) { @@ -2678,6 +2707,17 @@ verify::validateObligationModule(const ObligationModule &M) { llvm::inconvertibleErrorCode(), "obligation module has invalid diagnostic trace value"); } + for (const auto &[InternalName, Variable] : M.DiagnosticVariables) + if (Variable.Guard && + (!validateLogicExpr(Variable.Guard.get(), ValidationError, + &WellFormed) || + !validateLogicCalls(Variable.Guard.get(), M.LogicFunctions, + ValidationError, &CallsChecked) || + !ModuleScopes.validate(Variable.Guard.get(), ValidationError) || + Variable.Guard->Sort.Kind != LogicSortKind::Bool)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "obligation module has an invalid diagnostic variable guard"); if (!validateLogicExpr(M.CorrectnessGoal.get(), ValidationError, &WellFormed)) return llvm::createStringError(llvm::inconvertibleErrorCode(), "%s", ValidationError.c_str()); diff --git a/clang/lib/Verify/Backend/Obligation.h b/clang/lib/Verify/Backend/Obligation.h index c813108828..7d42300eb5 100644 --- a/clang/lib/Verify/Backend/Obligation.h +++ b/clang/lib/Verify/Backend/Obligation.h @@ -140,15 +140,24 @@ std::string formatLogicFeatures(LogicFeatureSet Features); const char *logicSortName(LogicSortKind Kind); std::string formatLogicSort(const LogicSort &Sort); +/// Where a term comes from: its anchor (Line, Column), where a diagnostic +/// points and a stable ID is built, in the source it spans, from its begin to +/// the column after its last character. struct ObligationSource { std::string File; unsigned Line = 0; unsigned Column = 0; unsigned EndLine = 0; unsigned EndColumn = 0; + /// The start of the span, before the anchor (an operator's operands); + /// zero when it is the anchor. + unsigned BeginLine = 0; + unsigned BeginColumn = 0; bool isValid() const { return !File.empty() && Line != 0; } bool hasRange() const { return isValid() && EndLine != 0; } + unsigned beginLine() const { return BeginLine ? BeginLine : Line; } + unsigned beginColumn() const { return BeginLine ? BeginColumn : Column; } }; class LogicExpr; @@ -210,6 +219,7 @@ class LogicExpr : public llvm::ThreadSafeRefCountedBase { Kind K; LogicSort Sort; SourceLocation Loc; + SourceLocation BeginLoc; SourceLocation EndLoc; /// Where Loc lies, filled in by the driver once the term is built /// (annotateObligationSources): the one field written after a term is @@ -313,6 +323,7 @@ struct Obligation { std::string StableId; ObligationKind Kind = ObligationKind::Assertion; SourceLocation Loc; + SourceLocation BeginLoc; SourceLocation EndLoc; ObligationSource Source; uint64_t TraceEventCount = 0; @@ -329,6 +340,11 @@ struct DiagnosticVariable { SourceLocation Loc; SourceLocation EndLoc; ObligationSource Source; + /// A reference: its value is the address it refers to. Not archived. + bool Reference = false; + /// Where this version is defined, as PassiveModelVariable::Guard (true on + /// every path); null when unknown, as in an archive before version 4. + LogicExprRef Guard = nullptr; }; struct LogicFunctionParameter { diff --git a/clang/lib/Verify/Backend/ObligationSerialization.cpp b/clang/lib/Verify/Backend/ObligationSerialization.cpp index 5cd365ff37..46b0dd7ade 100644 --- a/clang/lib/Verify/Backend/ObligationSerialization.cpp +++ b/clang/lib/Verify/Backend/ObligationSerialization.cpp @@ -444,6 +444,7 @@ std::optional traceFromTag(uint8_t Tag) { class ArchiveWriter { std::string Data; + uint32_t FormatVersion = ObligationSerializationVersion; bool IncludeSource; bool IncludeSourceRanges; bool IncludeDiagnostics; @@ -487,6 +488,10 @@ class ArchiveWriter { if (IncludeSourceRanges) { writeU32(Source.EndLine); writeU32(Source.EndColumn); + if (FormatVersion >= 5) { + writeU32(Source.BeginLine); + writeU32(Source.BeginColumn); + } } } @@ -560,6 +565,7 @@ class ArchiveWriter { void writeModule(const ObligationModule &Module, uint32_t FormatVersion = ObligationSerializationVersion) { + this->FormatVersion = FormatVersion; Data.append(ArchiveMagic.data(), ArchiveMagic.size()); writeU32(FormatVersion); uint32_t Flags = IncludeSource ? HasSourceMetadata : 0; @@ -584,6 +590,11 @@ class ArchiveWriter { writeString(Variable.DisplayName); writeSort(Variable.Sort); writeSource(Variable.Source); + if (FormatVersion >= 4) { + writeU8(Variable.Guard ? 1 : 0); + if (Variable.Guard) + writeExpr(Variable.Guard.get()); + } } writeU64(Module.TraceEvents.size()); for (const DiagnosticTraceEvent &Event : Module.TraceEvents) { @@ -783,6 +794,10 @@ class ArchiveReader { if (IncludeSourceRanges) { Source.EndLine = readU32(); Source.EndColumn = readU32(); + if (Version >= 5) { + Source.BeginLine = readU32(); + Source.BeginColumn = readU32(); + } } return Source; } @@ -899,6 +914,13 @@ class ArchiveReader { Variable.DisplayName = readString(); Variable.Sort = readSort(); Variable.Source = readSource(IncludeSource); + if (Version >= 4) { + uint8_t HasGuard = readU8(); + if (HasGuard > 1) + fail("invalid diagnostic variable guard in obligation archive"); + if (HasGuard == 1) + Variable.Guard = LogicExprRef(readExpr()); + } if (!Module.DiagnosticVariables .emplace(std::move(InternalName), std::move(Variable)) .second) diff --git a/clang/lib/Verify/Backend/ObligationSerialization.h b/clang/lib/Verify/Backend/ObligationSerialization.h index 944dcd85e8..51dc220056 100644 --- a/clang/lib/Verify/Backend/ObligationSerialization.h +++ b/clang/lib/Verify/Backend/ObligationSerialization.h @@ -15,7 +15,7 @@ namespace verify { /// Version 2 adds the precise obligation kinds; version 3 each logical /// function's termination measures and, with source metadata, where each /// assumption of a goal comes from. Readers read every earlier version. -inline constexpr uint32_t ObligationSerializationVersion = 3; +inline constexpr uint32_t ObligationSerializationVersion = 5; inline constexpr uint32_t ObligationSemanticHashVersion = 4; /// Serialize one module as a deterministic, versioned binary record. Records diff --git a/clang/lib/Verify/Backend/ObligationSimplify.cpp b/clang/lib/Verify/Backend/ObligationSimplify.cpp index f856216cd5..13b0e34f04 100644 --- a/clang/lib/Verify/Backend/ObligationSimplify.cpp +++ b/clang/lib/Verify/Backend/ObligationSimplify.cpp @@ -21,6 +21,7 @@ std::unique_ptr cloneLogicExpr(const LogicExpr *Expr) { auto Copy = std::make_unique(Expr->K); Copy->Sort = Expr->Sort; Copy->Loc = Expr->Loc; + Copy->BeginLoc = Expr->BeginLoc; Copy->EndLoc = Expr->EndLoc; Copy->Source = Expr->Source; Copy->IntVal = Expr->IntVal; @@ -116,6 +117,7 @@ std::unique_ptr boolLiteral(bool Value, const LogicExpr &Source) { std::make_unique(Value ? LogicExpr::True : LogicExpr::False); Result->Sort = LogicSort::boolSort(); Result->Loc = Source.Loc; + Result->BeginLoc = Source.BeginLoc; Result->EndLoc = Source.EndLoc; Result->Source = Source.Source; return Result; @@ -157,6 +159,7 @@ LogicExprRef lowerUnsignedMathArithmetic(const LogicExprRef &Expr, auto Divisor = std::make_unique(LogicExpr::IntLit); Divisor->Sort = SourceSort; Divisor->Loc = Expr->Children[1]->Loc; + Divisor->BeginLoc = Expr->Children[1]->BeginLoc; Divisor->EndLoc = Expr->Children[1]->EndLoc; Divisor->Source = Expr->Children[1]->Source; Divisor->IntVal = Expr->Children[1]->IntVal; @@ -164,6 +167,7 @@ LogicExprRef lowerUnsignedMathArithmetic(const LogicExprRef &Expr, auto MachineArithmetic = std::make_unique(Expr->K); MachineArithmetic->Sort = SourceSort; MachineArithmetic->Loc = Expr->Loc; + MachineArithmetic->BeginLoc = Expr->BeginLoc; MachineArithmetic->EndLoc = Expr->EndLoc; MachineArithmetic->Source = Expr->Source; MachineArithmetic->Children.push_back( @@ -199,6 +203,7 @@ simplifyUnsignedMathToBitVector(const LogicExprRef &Expr, auto Resize = std::make_unique(LogicExpr::BvResize); Resize->Sort = Expr->Sort; Resize->Loc = Expr->Loc; + Resize->BeginLoc = Expr->BeginLoc; Resize->EndLoc = Expr->EndLoc; Resize->Source = Expr->Source; Resize->Children.push_back(std::move(MachineArithmetic)); @@ -314,6 +319,7 @@ std::unique_ptr negate(std::unique_ptr Expr) { Result->Sort = LogicSort::boolSort(); if (Expr) { Result->Loc = Expr->Loc; + Result->BeginLoc = Expr->BeginLoc; Result->EndLoc = Expr->EndLoc; Result->Source = Expr->Source; } @@ -333,6 +339,7 @@ buildCompleteGoal(const std::vector &Obligations) { auto Conjunction = std::make_unique(LogicExpr::And); Conjunction->Sort = LogicSort::boolSort(); Conjunction->Loc = Complete->Loc; + Conjunction->BeginLoc = Complete->BeginLoc; Conjunction->EndLoc = Complete->EndLoc; Conjunction->Source = Complete->Source; Conjunction->Children.push_back(std::move(Complete)); @@ -406,6 +413,10 @@ void simplifyFunctions(ObligationModule &Module, }), Event.Values.end()); } + for (auto &[InternalName, Variable] : Module.DiagnosticVariables) + if (Variable.Guard && + !callsOnlyReachableFunctions(Variable.Guard.get(), Reachable)) + Variable.Guard = nullptr; for (auto It = Module.LogicFunctions.begin(); It != Module.LogicFunctions.end();) { @@ -445,6 +456,7 @@ std::unique_ptr substituteVariable(const LogicExpr *Expr, auto Copy = std::make_unique(Expr->K); Copy->Sort = Expr->Sort; Copy->Loc = Expr->Loc; + Copy->BeginLoc = Expr->BeginLoc; Copy->EndLoc = Expr->EndLoc; Copy->Source = Expr->Source; Copy->IntVal = Expr->IntVal; @@ -467,6 +479,7 @@ std::unique_ptr logicNode(LogicExpr::Kind K, LogicSort Sort, auto Node = std::make_unique(K); Node->Sort = Sort; Node->Loc = At.Loc; + Node->BeginLoc = At.BeginLoc; Node->EndLoc = At.EndLoc; Node->Source = At.Source; return Node; diff --git a/clang/lib/Verify/Backend/QueryDump.cpp b/clang/lib/Verify/Backend/QueryDump.cpp new file mode 100644 index 0000000000..913541cca4 --- /dev/null +++ b/clang/lib/Verify/Backend/QueryDump.cpp @@ -0,0 +1,204 @@ +//===--- QueryDump.cpp - Solver queries as SMT-LIB scripts ----------------===// +#include "QueryDump.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/SHA256.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang; +using namespace verify; + +namespace { +struct DumpSettings { + std::mutex Lock; + std::string Directory; + std::string Tool; + std::string Error; +}; + +DumpSettings &settings() { + static DumpSettings Settings; + return Settings; +} + +void recordError(std::string Message) { + DumpSettings &S = settings(); + std::lock_guard Guard(S.Lock); + if (S.Error.empty()) + S.Error = std::move(Message); +} + +std::string digest(llvm::StringRef Text, size_t Digits) { + llvm::SHA256 Hash; + Hash.update(Text); + return llvm::toHex(Hash.final(), /*LowerCase=*/true).substr(0, Digits); +} + +/// \p Name with every character but letters, digits, `_`, and \p Kept +/// replaced by `_`, so that it names a file on every system. +std::string fileName(llvm::StringRef Name, llvm::StringRef Kept = "") { + std::string Out; + for (char C : Name.take_front(64)) + Out += llvm::isAlnum(C) || C == '_' || Kept.contains(C) ? C : '_'; + return Out; +} + +const Obligation *obligationOf(const ObligationModule *Module, + const LogicExpr *Query) { + if (Module && Query) + for (const Obligation &Item : Module->Obligations) + if (Item.CounterexampleQuery.get() == Query) + return &Item; + return nullptr; +} + +bool wholeQuery(const ObligationModule *Module, const LogicExpr *Query) { + return Module && (!Query || Query == Module->CounterexampleQuery.get()); +} + +llvm::StringRef stableId(const Obligation &Item) { + return Item.StableId.empty() ? Item.Id : Item.StableId; +} +} // namespace + +void verify::setQueryDumpDirectory(std::string Directory, std::string Tool) { + DumpSettings &S = settings(); + std::lock_guard Guard(S.Lock); + S.Directory = std::move(Directory); + S.Tool = std::move(Tool); + S.Error.clear(); +} + +bool verify::queryDumpEnabled() { + DumpSettings &S = settings(); + std::lock_guard Guard(S.Lock); + return !S.Directory.empty(); +} + +std::string verify::queryDumpError() { + DumpSettings &S = settings(); + std::lock_guard Guard(S.Lock); + return S.Error; +} + +std::string verify::queryDumpPath(const ObligationModule *Module, + const LogicExpr *Query, + llvm::StringRef Solver, + llvm::StringRef Script) { + std::string Function = "query"; + if (Module) { + Function = fileName(Module->FunctionName); + Function += "-" + digest(Module->FunctionIdentity, 6); + } + // An obligation by its stable id, `postcondition@11:23` as + // `postcondition-11-23`. + std::string Name = "query"; + if (wholeQuery(Module, Query)) { + Name = "whole"; + } else if (const Obligation *Item = obligationOf(Module, Query)) { + llvm::StringRef Id = stableId(*Item); + std::string Local = + Id.contains("::") ? Id.rsplit("::").second.str() : Id.str(); + for (char &C : Local) + if (C == '@' || C == ':' || C == '#') + C = '-'; + Name = fileName(Local, "-"); + } + llvm::SmallString<128> Path(Function); + llvm::sys::path::append(Path, Name + "." + Solver.str() + "-" + + digest(Script, 8) + ".smt2"); + return std::string(Path); +} + +void QueryDump::start(const ObligationModule *Module, const LogicExpr *Query, + llvm::StringRef Solver, llvm::StringRef SolverVersion, + llvm::StringRef Options, llvm::StringRef Script) { + std::string Directory, Tool; + { + DumpSettings &S = settings(); + std::lock_guard Guard(S.Lock); + Directory = S.Directory; + Tool = S.Tool; + } + if (Directory.empty()) + return; + const std::string Relative = queryDumpPath(Module, Query, Solver, Script); + llvm::SmallString<256> Full(Directory); + llvm::sys::path::append(Full, Relative); + if (std::error_code EC = llvm::sys::fs::create_directories( + llvm::sys::path::parent_path(Full))) { + recordError("cannot create " + llvm::sys::path::parent_path(Full).str() + + ": " + EC.message()); + return; + } + + std::string Text; + llvm::raw_string_ostream OS(Text); + OS << "; " << Tool << ", " << Solver << ' ' << SolverVersion << "\n"; + if (Module) { + OS << "; " << Module->FunctionName << ": "; + if (wholeQuery(Module, Query)) { + OS << "every obligation at once"; + } else if (const Obligation *Item = obligationOf(Module, Query)) { + OS << "the " << obligationKindName(Item->Kind) << " check"; + if (Item->Source.isValid()) + OS << " at " << Item->Source.File << ':' << Item->Source.Line << ':' + << Item->Source.Column; + OS << " (" << stableId(*Item) << ")"; + } else { + OS << "a query built from its obligations"; + } + OS << "\n"; + } + OS << "; replay: " << Solver << ' ' << Relative << "\n"; + OS << Options << Script; + + // Written whole under a temporary name, so that a reader never sees a + // part of it. + int FD = -1; + llvm::SmallString<256> Temporary; + if (std::error_code EC = llvm::sys::fs::createUniqueFile(Full + ".%%%%%%.tmp", + FD, Temporary)) { + recordError("cannot write " + Full.str().str() + ": " + EC.message()); + return; + } + { + llvm::raw_fd_ostream Out(FD, /*shouldClose=*/true); + Out << Text; + Out.close(); + if (Out.has_error()) { + recordError("cannot write " + Full.str().str() + ": " + + Out.error().message()); + Out.clear_error(); + llvm::sys::fs::remove(Temporary); + return; + } + } + if (std::error_code EC = llvm::sys::fs::rename(Temporary, Full)) { + recordError("cannot write " + Full.str().str() + ": " + EC.message()); + llvm::sys::fs::remove(Temporary); + return; + } + Path = std::string(Full); +} + +void QueryDump::finish(llvm::StringRef Answer, llvm::StringRef Commands) { + if (Path.empty()) + return; + std::error_code EC; + llvm::raw_fd_ostream Out(Path, EC, llvm::sys::fs::OF_Append); + if (!EC) { + Out << Commands << "; cpp-verify: " << Answer << "\n"; + Out.close(); + if (Out.has_error()) { + EC = Out.error(); + Out.clear_error(); + } + } + if (EC) + recordError("cannot write " + Path + ": " + EC.message()); + Path.clear(); +} diff --git a/clang/lib/Verify/Backend/QueryDump.h b/clang/lib/Verify/Backend/QueryDump.h new file mode 100644 index 0000000000..0f9ac38ef6 --- /dev/null +++ b/clang/lib/Verify/Backend/QueryDump.h @@ -0,0 +1,58 @@ +//===--- QueryDump.h - Solver queries as SMT-LIB scripts --------*- C++ -*-===// +#ifndef LLVM_CLANG_VERIFY_BACKEND_QUERYDUMP_H +#define LLVM_CLANG_VERIFY_BACKEND_QUERYDUMP_H + +#include "Obligation.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace verify { + +/// Writes every query sent to a solver under \p Directory, each as a script +/// the solver replays on its own (--dump-smt2); an empty directory turns it +/// off. \p Tool names the program in each script's header. +void setQueryDumpDirectory(std::string Directory, std::string Tool); + +/// Whether queries are being written. +bool queryDumpEnabled(); + +/// The first error writing a query, empty when there was none. +std::string queryDumpError(); + +/// The file of \p Query of \p Module solved by \p Solver whose script is +/// \p Script, relative to the dump directory: +/// `-
/.-.smt2`. +std::string queryDumpPath(const ObligationModule *Module, + const LogicExpr *Query, llvm::StringRef Solver, + llvm::StringRef Script); + +/// One query's script: written when the dump starts, before the check, so +/// that a check that never returns leaves its query, and finished with the +/// solver's answer. +class QueryDump { +public: + QueryDump() = default; + QueryDump(const QueryDump &) = delete; + QueryDump &operator=(const QueryDump &) = delete; + + /// Writes \p Options, the solver's settings as commands, then \p Script, + /// which ends with its check, after a header that names \p Solver + /// (\p SolverVersion) and what \p Query of \p Module is. Does nothing + /// unless dumping is on. + void start(const ObligationModule *Module, const LogicExpr *Query, + llvm::StringRef Solver, llvm::StringRef SolverVersion, + llvm::StringRef Options, llvm::StringRef Script); + + /// Appends \p Commands, run after the check (such as `(get-model)`), and + /// the answer, `; cpp-verify: sat`. + void finish(llvm::StringRef Answer, llvm::StringRef Commands = ""); + +private: + std::string Path; +}; + +} // namespace verify +} // namespace clang + +#endif diff --git a/clang/lib/Verify/Backend/VerifyBackend.cpp b/clang/lib/Verify/Backend/VerifyBackend.cpp index cd7afa36e2..d1023899ba 100644 --- a/clang/lib/Verify/Backend/VerifyBackend.cpp +++ b/clang/lib/Verify/Backend/VerifyBackend.cpp @@ -640,6 +640,8 @@ VerifyResult VerifyBackend::verifyDirect(const ObligationModule &Module) { Result.ObligationId.clear(); Result.ObligationType.reset(); Result.Location = SourceLocation(); + Result.BeginLocation = SourceLocation(); + Result.EndLocation = SourceLocation(); Result.Source = {}; } else if (Capabilities.ProducesVerificationVerdict && (Result.Status == VerifyStatus::Exported || @@ -653,6 +655,8 @@ VerifyResult VerifyBackend::verifyDirect(const ObligationModule &Module) { Result.ObligationId.clear(); Result.ObligationType.reset(); Result.Location = SourceLocation(); + Result.BeginLocation = SourceLocation(); + Result.EndLocation = SourceLocation(); Result.Source = {}; } // A model of `false` says nothing about the program. diff --git a/clang/lib/Verify/Backend/VerifyBackend.h b/clang/lib/Verify/Backend/VerifyBackend.h index 2a8235e10e..ba6f7eaded 100644 --- a/clang/lib/Verify/Backend/VerifyBackend.h +++ b/clang/lib/Verify/Backend/VerifyBackend.h @@ -99,6 +99,12 @@ struct VerifyModelValue { /// Empty when the solver model does not determine this value. Backends must /// not use model completion to manufacture a diagnostic value. std::optional Value; + /// A reference: Value is the address it refers to. + bool Reference = false; + /// Whether the counterexample's path reaches where this version is + /// defined: false for a version of a branch it does not take, empty when + /// the model does not decide it or nothing records it. + std::optional OnPath; }; struct VerifyTraceValue { @@ -110,6 +116,7 @@ struct VerifyTraceValue { struct VerifyTraceEvent { DiagnosticTraceKind Kind = DiagnosticTraceKind::Branch; std::string Message; + SourceLocation Loc; ObligationSource Source; /// Empty when the model does not determine whether this event is on the /// counterexample path. @@ -156,6 +163,10 @@ struct VerifyResult { /// goal of the obligation it fails, when the model gives each one. std::shared_ptr Assignment; SourceLocation Location; + /// The source the failing obligation's expression spans; Location lies + /// in it. + SourceLocation BeginLocation; + SourceLocation EndLocation; ObligationSource Source; uint64_t CacheHits = 0; uint64_t CacheMisses = 0; diff --git a/clang/lib/Verify/Backend/Z3Encode.cpp b/clang/lib/Verify/Backend/Z3Encode.cpp index 7256b8e7fc..56ffd0eba8 100644 --- a/clang/lib/Verify/Backend/Z3Encode.cpp +++ b/clang/lib/Verify/Backend/Z3Encode.cpp @@ -3,6 +3,7 @@ #include "ExtremeSearch.h" #include "ObligationSerialization.h" #include "ObligationSimplify.h" +#include "QueryDump.h" #include "TaskGroup.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/ScopeExit.h" @@ -1628,7 +1629,9 @@ Z3Encoder::profileQuantifiers(const z3::expr_vector &Assertions) { Rerun.set(Params); for (unsigned I = 0; I != Assertions.size(); ++I) Rerun.add(Assertions[I]); + Profiling = true; (void)check(Rerun, TimeoutMs == 0 ? 10000U : std::min(TimeoutMs, 10000U)); + Profiling = false; } std::cerr.flush(); std::fflush(stderr); @@ -1856,6 +1859,16 @@ z3::check_result Z3Encoder::check(z3::solver &S, unsigned Ms) { bool Interrupted = false; bool OverLimit = false; z3::check_result Result = z3::unknown; + QueryDump Dump; + if (queryDumpEnabled()) { + unsigned Major = 0, Minor = 0, Build = 0, Revision = 0; + Z3_get_version(&Major, &Minor, &Build, &Revision); + auto [Options, Script] = dumpScript(S, Ms); + Dump.start(DumpModule, DumpQuery, "z3", + std::to_string(Major) + "." + std::to_string(Minor) + "." + + std::to_string(Build), + Options, Script); + } std::thread Watchdog([&] { std::unique_lock Lock(CheckLock); const auto Limit = @@ -1893,9 +1906,47 @@ z3::check_result Z3Encoder::check(z3::solver &S, unsigned Ms) { OutOfMemory = true; }); Result = S.check(); + Dump.finish(Result == z3::sat ? "sat" + : Result == z3::unsat ? "unsat" + : "unknown (" + S.reason_unknown() + ")", + Result == z3::sat ? "(get-model)\n" : ""); return Result; } +std::pair Z3Encoder::dumpScript(z3::solver &S, + unsigned Ms) { + // The settings freshSolver gives a query's solver, and the profiling + // rerun's own. + std::string Options; + const unsigned Timeout = Ms != 0 ? Ms : TimeoutMs; + if (Timeout != 0) + Options += "(set-option :timeout " + std::to_string(Timeout) + ")\n"; + if (Profiling) { + Options += "(set-option :smt.qi.profile true)\n"; + } else { + if (ResourceLimit != 0) + Options += "(set-option :rlimit " + std::to_string(ResourceLimit) + ")\n"; + Options += "(set-option :smt.mbqi true)\n" + "(set-option :smt.qi.eager_threshold 0.0)\n"; + } + // The solver's own printer: unlike the benchmark printer (to_smt2), it + // quotes symbols that are not SMT-LIB symbols, such as the ids + // `q@line:col` of quantifiers. A solver that has checked before also + // prints the model converter its preprocessing left (`model-add` of + // symbols it never declares), so a solver of the assertions alone prints + // the query. + z3::solver Assertions(Ctx); + for (const z3::expr &Assertion : S.assertions()) + Assertions.add(Assertion); + std::string Script = Z3_solver_to_string(Ctx, Assertions); + if (!Script.empty() && Script.back() != '\n') + Script += '\n'; + // A quantified query's solver is the smt tactic's. + Script += QuantifiedQuery || Profiling ? "(check-sat-using smt)\n" + : "(check-sat)\n"; + return {Options, Script}; +} + std::optional Z3Encoder::encodeModuleAs(const ObligationModule &Module, const LogicExpr *Query, VerifyResult &Result) { @@ -3234,6 +3285,8 @@ VerifyResult Z3Encoder::decideModule(const ObligationModule &Module, std::optional TraceEventCount) { VerifyResult Out; QueryStart = std::chrono::steady_clock::now(); + DumpModule = &Module; + DumpQuery = Query; // A step toward settling another module wants a proof or a certified // counterexample within its slice: no domain probing, whose thousands of // ground instances can keep the solver from noticing its timeout. @@ -3405,6 +3458,8 @@ VerifyResult Z3Encoder::decideModule(const ObligationModule &Module, Out.ObligationId = Item.StableId.empty() ? Item.Id : Item.StableId; Out.ObligationType = Item.Kind; Out.Location = Item.Loc; + Out.BeginLocation = Item.BeginLoc; + Out.EndLocation = Item.EndLoc; Out.Source = Item.Source; Out.Assignment = assignment(Item); TraceEventCount = Item.TraceEventCount; @@ -3441,6 +3496,11 @@ VerifyResult Z3Encoder::decideModule(const ObligationModule &Module, return Out; } Value.Source = Metadata->second.Source; + Value.Reference = Metadata->second.Reference; + if (Metadata->second.Guard) + if (std::optional Guard = + evaluate(Metadata->second.Guard.get())) + Value.OnPath = *Guard == "true"; } if (auto It = Vars.find(Name); It != Vars.end()) { z3::expr Evaluated = modelValue(It->second); @@ -3460,6 +3520,7 @@ VerifyResult Z3Encoder::decideModule(const ObligationModule &Module, VerifyTraceEvent Trace; Trace.Kind = Event.Kind; Trace.Message = Event.Message; + Trace.Loc = Event.Loc; Trace.Source = Event.Source; if (Guard) Trace.Active = *Guard == "true"; @@ -3537,62 +3598,11 @@ static VerifyResult timeSpent() { return Result; } +/// A failure's values and path are its Model and Trace, which the driver +/// prints; its Message only explains what they do not say. static VerifyResult finishZ3Result(VerifyResult Result) { - if (Result.Status != VerifyStatus::Failed) - return Result; - std::string Message; - for (const VerifyModelValue &Value : Result.Model) { - if (!Message.empty()) - Message += ", "; - Message += Value.DisplayName; - if (Value.DisplayName != Value.InternalName) - Message += " [ssa=" + Value.InternalName + "]"; - Message += " [type=" + formatLogicSort(Value.Sort) + - "] = " + Value.Value.value_or(""); - } - auto traceKind = [](DiagnosticTraceKind Kind) { - switch (Kind) { - case DiagnosticTraceKind::Branch: - return "branch"; - case DiagnosticTraceKind::Call: - return "call"; - case DiagnosticTraceKind::Loop: - return "loop"; - case DiagnosticTraceKind::HeapWrite: - return "heap-write"; - case DiagnosticTraceKind::Allocation: - return "allocation"; - case DiagnosticTraceKind::LifetimeEnd: - return "lifetime-end"; - case DiagnosticTraceKind::Deallocation: - return "deallocation"; - case DiagnosticTraceKind::Return: - return "return"; - } - return "branch"; - }; - if (!Result.Trace.empty()) { - if (!Message.empty()) - Message += "; "; - Message += "trace: "; - bool First = true; - for (const VerifyTraceEvent &Event : Result.Trace) { - if (!First) - Message += " -> "; - First = false; - Message += traceKind(Event.Kind); - if (!Event.Message.empty()) - Message += "." + Event.Message; - if (Event.Source.isValid()) - Message += "@" + std::to_string(Event.Source.Line) + ":" + - std::to_string(Event.Source.Column); - if (!Event.Active) - Message += "?"; - for (const VerifyTraceValue &Value : Event.Values) - Message += " " + Value.Label + "=" + Value.Value.value_or(""); - } - } - Result.Message = std::move(Message); + if (Result.Status == VerifyStatus::Failed) + Result.Message.clear(); return Result; } @@ -3833,6 +3843,8 @@ static VerifyResult attributed(VerifyResult Result, const Obligation &Item) { Result.ObligationId = Item.StableId.empty() ? Item.Id : Item.StableId; Result.ObligationType = Item.Kind; Result.Location = Item.Loc; + Result.BeginLocation = Item.BeginLoc; + Result.EndLocation = Item.EndLoc; Result.Source = Item.Source; if (Result.Status == VerifyStatus::Unresolved) Result.Message = "proof obligation " + Result.ObligationId + @@ -3900,6 +3912,7 @@ Z3VerifyBackend::verifyPastEarlier(const ObligationModule &Module, size_t I, auto Conjunction = std::make_unique(LogicExpr::And); Conjunction->Sort = LogicSort::boolSort(); Conjunction->Loc = Query->Loc; + Conjunction->BeginLoc = Query->BeginLoc; Conjunction->EndLoc = Query->EndLoc; Conjunction->Source = Query->Source; Conjunction->Children.push_back(std::move(Query)); diff --git a/clang/lib/Verify/Backend/Z3Encode.h b/clang/lib/Verify/Backend/Z3Encode.h index b64f9dd908..7afef37aee 100644 --- a/clang/lib/Verify/Backend/Z3Encode.h +++ b/clang/lib/Verify/Backend/Z3Encode.h @@ -73,6 +73,14 @@ class Z3Encoder { BackendTimes *Times = nullptr; bool SearchSlice = false; bool QuantifiedQuery = false; + /// What the checks solve, for --dump-smt2. + const ObligationModule *DumpModule = nullptr; + const LogicExpr *DumpQuery = nullptr; + /// The check is the rerun that profiles quantifier instances. + bool Profiling = false; + /// \p S as a standalone script: the options of its solver, as commands, + /// for a check of \p Ms milliseconds, then its assertions and the check. + std::pair dumpScript(z3::solver &S, unsigned Ms); z3::solver freshSolver(); /// \p S.check() within \p Ms milliseconds (0: no limit), unknown at once /// after interrupt(). diff --git a/clang/lib/Verify/CMakeLists.txt b/clang/lib/Verify/CMakeLists.txt index 59affb4165..969753ae79 100644 --- a/clang/lib/Verify/CMakeLists.txt +++ b/clang/lib/Verify/CMakeLists.txt @@ -42,6 +42,7 @@ add_clang_library(clangVerify Backend/ObligationSimplify.cpp Backend/ObligationSerialization.cpp Backend/ProofCache.cpp + Backend/QueryDump.cpp Backend/ChildProcess.cpp Backend/CVC5Backend.cpp Backend/Z3Encode.cpp @@ -49,6 +50,8 @@ add_clang_library(clangVerify Backend/VerifyBackend.cpp Backend/LeanBackend.cpp Driver/Verifier.cpp + Driver/Counterexample.cpp + Driver/StructuredOutput.cpp Driver/DumpIR.cpp Driver/CppVerifyIntegration.cpp Driver/CppVerifyVersion.cpp diff --git a/clang/lib/Verify/Driver/Counterexample.cpp b/clang/lib/Verify/Driver/Counterexample.cpp new file mode 100644 index 0000000000..8469cea8bb --- /dev/null +++ b/clang/lib/Verify/Driver/Counterexample.cpp @@ -0,0 +1,154 @@ +//===--- Counterexample.cpp -----------------------------------------------===// +#include "Counterexample.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +using namespace clang; +using namespace clang::verify; + +namespace { +/// An internal name's variable and SSA version: "i_2" is version 2 of "i", +/// "y#2_1" version 1 of the local that shadows y. +std::pair versionOf(llvm::StringRef InternalName) { + auto [Base, Suffix] = InternalName.rsplit('_'); + unsigned Version = 0; + if (Base.empty() || Suffix.getAsInteger(10, Version)) + return {InternalName, 0}; + return {Base, Version}; +} + +std::string valueText(const std::optional &Value) { + return Value.value_or(""); +} +} // namespace + +std::string verify::counterexampleText(const VerifyResult &Result) { + struct Variable { + const VerifyModelValue *First; + std::vector Versions; + }; + std::vector Variables; + std::map IndexOf; + std::map Named; + for (const VerifyModelValue &Value : Result.Model) { + // A version of a branch the path does not take is no value the variable + // takes; it equals the one before it there. + if (Value.OnPath == false) + continue; + auto [It, Inserted] = IndexOf.try_emplace( + versionOf(Value.InternalName).first, Variables.size()); + if (Inserted) { + Variables.push_back({&Value, {}}); + ++Named[Value.DisplayName]; + } + Variables[It->second].Versions.push_back(&Value); + } + auto position = [](const Variable &V) { + const ObligationSource &Source = V.First->Source; + return std::make_tuple(V.First->DisplayName == "result", !Source.isValid(), + Source.Line, Source.Column); + }; + std::stable_sort(Variables.begin(), Variables.end(), + [&](const Variable &A, const Variable &B) { + return position(A) < position(B); + }); + std::string Text; + for (Variable &V : Variables) { + std::stable_sort(V.Versions.begin(), V.Versions.end(), + [](const VerifyModelValue *A, const VerifyModelValue *B) { + return versionOf(A->InternalName).second < + versionOf(B->InternalName).second; + }); + // A reference's value is the address of what it refers to, which C++ + // writes &r. + Text += Text.empty() ? "" : ", "; + Text += (V.First->Reference ? "&" : "") + V.First->DisplayName; + // Two variables of one name, as a local that shadows another. + if (Named[V.First->DisplayName] > 1 && V.First->Source.isValid()) + Text += " (line " + std::to_string(V.First->Source.Line) + ")"; + Text += " = "; + // The values it takes in turn: a version that keeps the value before it, + // as one that merges branches, adds none. + const std::optional *Previous = nullptr; + for (const VerifyModelValue *Version : V.Versions) { + if (Previous && *Previous && *Previous == Version->Value) + continue; + Text += (Previous ? " -> " : "") + valueText(Version->Value); + Previous = &Version->Value; + } + } + return Text; +} + +std::string verify::traceStepText(const VerifyTraceEvent &Event) { + auto value = [&](llvm::StringRef Label) { + for (const VerifyTraceValue &Value : Event.Values) + if (Value.Label == Label) + return valueText(Value.Value); + return std::string(""); + }; + llvm::StringRef Message = Event.Message; + std::string Text; + switch (Event.Kind) { + case DiagnosticTraceKind::Branch: + Text = Message == "then" ? "takes the true branch" + : Message == "else" ? "takes the false branch" + : "takes the branch " + Message.str(); + break; + case DiagnosticTraceKind::Loop: + if (Message == "entry") + Text = "enters the loop"; + else if (Message == "inductive iteration") + Text = "starts an arbitrary iteration of the loop"; + else if (Message == "exit") + Text = "leaves the loop"; + else if (Message == "break") + Text = "breaks out of the loop"; + else if (Message == "continue") + Text = "continues the loop"; + else if (Message.consume_front("iteration ")) + Text = "runs iteration " + Message.str() + " of the loop"; + else if (Message.consume_front("exit after ")) + Text = "leaves the loop after " + Message.str() + + (Message == "1" ? " iteration" : " iterations"); + else + Text = "loop: " + Message.str(); + for (const VerifyTraceValue &Value : Event.Values) + if (Value.Label == "condition") + Text += ", where its condition is " + valueText(Value.Value); + break; + case DiagnosticTraceKind::Call: { + std::vector Arguments; + for (const VerifyTraceValue &Value : Event.Values) + Arguments.push_back(valueText(Value.Value)); + Text = "calls " + Message.str() + "(" + llvm::join(Arguments, ", ") + ")"; + break; + } + case DiagnosticTraceKind::HeapWrite: + Text = "stores " + value("value") + " at address " + value("address"); + break; + case DiagnosticTraceKind::Allocation: + Text = (Message == "automatic allocation" + ? "creates a local object at address " + : "allocates an object at address ") + + value("address"); + break; + case DiagnosticTraceKind::LifetimeEnd: + Text = "ends the lifetime of a local object"; + break; + case DiagnosticTraceKind::Deallocation: + Text = "deletes the object at address " + value("address"); + break; + case DiagnosticTraceKind::Return: + Text = Event.Values.empty() ? "returns" : "returns " + value("value"); + break; + } + if (!Event.Active) + Text = "perhaps " + Text + + " (the counterexample does not decide whether " + "this step is taken)"; + return Text; +} diff --git a/clang/lib/Verify/Driver/Counterexample.h b/clang/lib/Verify/Driver/Counterexample.h new file mode 100644 index 0000000000..9661471c3f --- /dev/null +++ b/clang/lib/Verify/Driver/Counterexample.h @@ -0,0 +1,27 @@ +//===--- Counterexample.h -------------------------------------------------===// +#ifndef LLVM_CLANG_VERIFY_DRIVER_COUNTEREXAMPLE_H +#define LLVM_CLANG_VERIFY_DRIVER_COUNTEREXAMPLE_H + +#include "../Backend/VerifyBackend.h" +#include + +namespace clang { +namespace verify { + +/// A counterexample's values by source name, in the order the source +/// declares them, each variable's versions in program order: +/// "a = 100, i = 0 -> 1, result = 151". Empty when it has none. +std::string counterexampleText(const VerifyResult &Result); + +/// One step of a counterexample's path, as the static analyzer words its +/// path notes: "takes the false branch", "returns 151". +std::string traceStepText(const VerifyTraceEvent &Event); + +/// How many steps of a path are shown: the first and last half of them, as +/// -ftemplate-backtrace-limit shows instantiation contexts. +inline constexpr size_t TraceStepLimit = 10; + +} // namespace verify +} // namespace clang + +#endif diff --git a/clang/lib/Verify/Driver/CppVerifyIntegration.cpp b/clang/lib/Verify/Driver/CppVerifyIntegration.cpp index 497bcfe7c3..b664448748 100644 --- a/clang/lib/Verify/Driver/CppVerifyIntegration.cpp +++ b/clang/lib/Verify/Driver/CppVerifyIntegration.cpp @@ -22,25 +22,45 @@ bool verify::shouldRunCppVerify(const CompilerInstance &CI) { std::future verify::startCppVerifyAsync(ASTContext &Ctx, std::string ContractsOut) { return std::async(std::launch::async, [&Ctx, ContractsOut]() { - std::string Buffer; - llvm::raw_string_ostream OS(Buffer); + CppVerifyAsyncResult Result; VerifyOptions Opts; Opts.ContractsOut = ContractsOut; - bool Ok = verifyTranslationUnit(Ctx, OS, Opts); - OS.flush(); - return CppVerifyAsyncResult{Ok, std::move(Buffer)}; + Opts.Parts = &Result.Parts; + Result.Ok = verifyTranslationUnit(Ctx, llvm::nulls(), Opts); + return Result; }); } bool verify::finishCppVerify(CompilerInstance &CI, std::future Future) { CppVerifyAsyncResult R = Future.get(); - if (!R.second.empty()) - llvm::errs() << R.second; - if (!R.first) { - CI.getDiagnostics().Report(diag::err_fe_cppverify_failed) - << "verification failed"; - return false; + DiagnosticsEngine &Diags = CI.getDiagnostics(); + bool Reported = false; + for (const VerifyDiagnosticPart &Part : R.Parts) { + unsigned ID = diag::note_cppverify_result; + switch (Part.Level) { + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + ID = diag::err_cppverify_result; + Reported = true; + break; + case DiagnosticsEngine::Warning: + ID = diag::warn_cppverify_result; + break; + case DiagnosticsEngine::Remark: + ID = diag::remark_cppverify_result; + break; + case DiagnosticsEngine::Note: + case DiagnosticsEngine::Ignored: + break; + } + DiagnosticBuilder Builder = Diags.Report(Part.Loc, ID); + Builder << Part.Message; + if (Part.Range.isValid()) + Builder << CharSourceRange::getTokenRange(Part.Range); } - return true; + // A failure without a result of its own still fails the compilation. + if (!R.Ok && !Reported) + Diags.Report(diag::err_fe_cppverify_failed) << "verification failed"; + return R.Ok; } \ No newline at end of file diff --git a/clang/lib/Verify/Driver/CppVerifyIntegration.h b/clang/lib/Verify/Driver/CppVerifyIntegration.h index bfee19996e..8d7ec4f770 100644 --- a/clang/lib/Verify/Driver/CppVerifyIntegration.h +++ b/clang/lib/Verify/Driver/CppVerifyIntegration.h @@ -2,9 +2,10 @@ #ifndef LLVM_CLANG_VERIFY_DRIVER_CPPVERIFY_INTEGRATION_H #define LLVM_CLANG_VERIFY_DRIVER_CPPVERIFY_INTEGRATION_H +#include "DiagnosticPart.h" #include #include -#include +#include namespace clang { class ASTContext; @@ -12,7 +13,11 @@ class CompilerInstance; namespace verify { -using CppVerifyAsyncResult = std::pair; +/// Whether verification succeeded, and its results as Clang diagnostics. +struct CppVerifyAsyncResult { + bool Ok = false; + std::vector Parts; +}; /// Returns true if CppVerify SMT should run for this compilation. bool shouldRunCppVerify(const CompilerInstance &CI); @@ -24,7 +29,9 @@ bool shouldRunCppVerify(const CompilerInstance &CI); std::future startCppVerifyAsync(ASTContext &Ctx, std::string ContractsOut = ""); -/// Join \p Future, print verifier output, and report diagnostics on failure. +/// Join \p Future and report its results through the compiler's diagnostics: +/// failures as errors with notes, warnings in -Wcppverify, and proofs as +/// remarks (-Rcppverify). bool finishCppVerify(CompilerInstance &CI, std::future Future); } // namespace verify diff --git a/clang/lib/Verify/Driver/CppVerifyVersion.cpp b/clang/lib/Verify/Driver/CppVerifyVersion.cpp index e3270dda73..ff36fe4ec3 100644 --- a/clang/lib/Verify/Driver/CppVerifyVersion.cpp +++ b/clang/lib/Verify/Driver/CppVerifyVersion.cpp @@ -123,6 +123,8 @@ void printLean(llvm::raw_ostream &OS) { } // namespace +llvm::StringRef verify::releaseVersion() { return CPPVERIFY_VERSION; } + void verify::printVersion(llvm::raw_ostream &OS) { OS << "cpp-verify " << CPPVERIFY_VERSION; std::string Repository = getClangFullRepositoryVersion(); diff --git a/clang/lib/Verify/Driver/CppVerifyVersion.h b/clang/lib/Verify/Driver/CppVerifyVersion.h index c325b28dc2..83a94240df 100644 --- a/clang/lib/Verify/Driver/CppVerifyVersion.h +++ b/clang/lib/Verify/Driver/CppVerifyVersion.h @@ -26,6 +26,9 @@ inline constexpr llvm::StringLiteral CVC5NewestTestedVersion = "1.4.2"; /// verification run would find on PATH. void printVersion(llvm::raw_ostream &OS); +/// The release, as --version names it: "0.2.0-dev". +llvm::StringRef releaseVersion(); + } // namespace verify } // namespace clang diff --git a/clang/lib/Verify/Driver/DiagnosticPart.h b/clang/lib/Verify/Driver/DiagnosticPart.h new file mode 100644 index 0000000000..7dd03f8a76 --- /dev/null +++ b/clang/lib/Verify/Driver/DiagnosticPart.h @@ -0,0 +1,23 @@ +//===--- DiagnosticPart.h -------------------------------------------------===// +#ifndef LLVM_CLANG_VERIFY_DRIVER_DIAGNOSTICPART_H +#define LLVM_CLANG_VERIFY_DRIVER_DIAGNOSTICPART_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include + +namespace clang { +namespace verify { + +/// One Clang diagnostic of a verification result: its line, or a note. +struct VerifyDiagnosticPart { + DiagnosticsEngine::Level Level = DiagnosticsEngine::Note; + SourceLocation Loc; + SourceRange Range; + std::string Message; +}; + +} // namespace verify +} // namespace clang + +#endif diff --git a/clang/lib/Verify/Driver/StructuredOutput.cpp b/clang/lib/Verify/Driver/StructuredOutput.cpp new file mode 100644 index 0000000000..60e651ab48 --- /dev/null +++ b/clang/lib/Verify/Driver/StructuredOutput.cpp @@ -0,0 +1,380 @@ +//===--- StructuredOutput.cpp - JSON, SARIF, and GitHub output ------------===// +#include "StructuredOutput.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include + +using namespace clang; +using namespace clang::verify; + +std::string verify::jsonText(llvm::StringRef Text) { + return llvm::json::isUTF8(Text) ? Text.str() : llvm::json::fixUTF8(Text); +} + +llvm::json::Object verify::sourceJSON(const ObligationSource &Source) { + llvm::json::Object Result; + if (!Source.isValid()) + return Result; + Result["file"] = jsonText(Source.File); + Result["line"] = Source.beginLine(); + Result["column"] = Source.beginColumn(); + Result["end_line"] = Source.EndLine != 0 ? Source.EndLine : Source.Line; + Result["end_column"] = + Source.EndColumn != 0 ? Source.EndColumn : Source.Column; + return Result; +} + +namespace { +/// The workflow command escapes of GitHub Actions: data escapes '%' and line +/// ends, a property also ':' and ','. +std::string escapeCommand(llvm::StringRef Text, bool Property) { + std::string Out; + for (char C : Text) { + switch (C) { + case '%': + Out += "%25"; + break; + case '\r': + Out += "%0D"; + break; + case '\n': + Out += "%0A"; + break; + case ':': + Out += Property ? "%3A" : ":"; + break; + case ',': + Out += Property ? "%2C" : ","; + break; + default: + Out += C; + } + } + return Out; +} + +/// A path as GitHub resolves it: relative to the working directory, the +/// checkout in a workflow, when it lies under it. +std::string workspacePath(llvm::StringRef File) { + llvm::SmallString<256> Directory; + if (llvm::sys::path::is_absolute(File) && + !llvm::sys::fs::current_path(Directory)) { + Directory += llvm::sys::path::get_separator(); + if (File.starts_with(Directory)) + return File.drop_front(Directory.size()).str(); + } + return File.str(); +} + +/// An absolute file URI, as Clang writes it in SARIF. +std::string fileURI(llvm::StringRef File) { + llvm::SmallString<256> Path(File); + llvm::sys::fs::make_absolute(Path); + llvm::sys::path::native(Path, llvm::sys::path::Style::posix); + std::string URI = "file://"; + if (!llvm::StringRef(Path).starts_with("/")) + URI += "/"; + for (char C : Path) { + if (llvm::isAlnum(C) || llvm::StringRef("-._~/:").contains(C)) + URI += C; + else + URI += "%" + llvm::utohexstr(static_cast(C), false, 2); + } + return URI; +} + +std::string text(const llvm::json::Object &Record, llvm::StringRef Key) { + if (std::optional Value = Record.getString(Key)) + return Value->str(); + return ""; +} + +struct Classification { + std::string Rule; + std::string Kind; + std::string Level; +}; + +/// The SARIF rule, kind, and level of a record, after Frama-C's mapping of +/// property statuses: a proof passes, a failure fails, what no verdict +/// settles stays open. +Classification classify(const llvm::json::Object &Record) { + const std::string Status = text(Record, "status"); + const std::string Severity = text(Record, "severity"); + if (Status == "failed") { + std::string Rule = "failed"; + if (const llvm::json::Object *Obligation = Record.getObject("obligation")) + if (std::optional Kind = Obligation->getString("kind")) + Rule = Kind->str(); + return {Rule, "fail", "error"}; + } + if (Status == "verified" || Status == "certified" || + Status == "mixed-proof" || Status == "proved-obligations") + return {"verified", "pass", "none"}; + if (Status == "unresolved" || Status == "interrupted") + return {Status, "open", "error"}; + if (Status == "bounded-safe") + return {Status, "open", "warning"}; + if (Status == "trusted") + return {Status, "review", "none"}; + if (Status == "exported") + return {Status, "open", "note"}; + if (Status == "lowered") + return {Status, "informational", "none"}; + if (Status == "frontend") + return {"frontend", + Severity == "error" || Severity == "warning" ? "fail" + : "informational", + Severity == "error" || Severity == "warning" ? Severity : "note"}; + if (Severity == "warning") + return {"warning", "fail", "warning"}; + return {Status.empty() ? "error" : Status, "fail", + Severity == "error" ? "error" : "note"}; +} + +std::string ruleDescription(llvm::StringRef Rule) { + if (Rule == "verified") + return "The function's contract is proved."; + if (Rule == "unresolved") + return "No verdict settles the function; the reason says why."; + if (Rule == "interrupted") + return "The run was interrupted before the function's verification ended."; + if (Rule == "bounded-safe") + return "No check fails within the unrolling bound."; + if (Rule == "trusted") + return "The contract is assumed, not verified."; + if (Rule == "exported") + return "The obligations are exported to Lean, not yet certified."; + if (Rule == "lowered") + return "The obligations are lowered; no solver ran."; + if (Rule == "frontend") + return "A diagnostic of the compiler."; + if (Rule == "warning") + return "A warning of the verifier."; + if (Rule == "failed" || Rule == "error") + return "Verification failed."; + return ("A " + Rule + " check fails.").str(); +} + +/// What the text output prints before a verdict's message, which a record +/// leaves to its status. +std::string verdictPrefix(llvm::StringRef Status) { + static const std::pair Prefixes[] = { + {"verified", "Verified: "}, + {"certified", "Certified: "}, + {"mixed-proof", "Proved: "}, + {"proved-obligations", "Proved obligations: "}, + {"unresolved", "Unresolved: "}, + {"interrupted", "Interrupted: "}, + {"bounded-safe", "BoundedSafe: "}, + {"trusted", "Trusted: "}, + {"exported", "Exported: "}, + {"lowered", "Lowered: "}}; + for (const auto &[Name, Prefix] : Prefixes) + if (Status == Name) + return Prefix; + return ""; +} + +llvm::json::Object region(const llvm::json::Object &Source) { + static const std::pair Fields[] = { + {"line", "startLine"}, + {"column", "startColumn"}, + {"end_line", "endLine"}, + {"end_column", "endColumn"}}; + llvm::json::Object Region; + for (const auto &[From, To] : Fields) + if (std::optional Value = Source.getInteger(From)) + Region[To] = *Value; + return Region; +} +} // namespace + +std::string verify::githubCommand(llvm::StringRef Level, + const ObligationSource &Source, + llvm::StringRef Title, + llvm::StringRef Message) { + std::vector Properties; + if (Source.isValid()) { + Properties.push_back("file=" + + escapeCommand(workspacePath(Source.File), true)); + Properties.push_back("line=" + std::to_string(Source.beginLine())); + Properties.push_back("col=" + std::to_string(Source.beginColumn())); + if (Source.EndLine) { + Properties.push_back("endLine=" + std::to_string(Source.EndLine)); + Properties.push_back("endColumn=" + std::to_string(Source.EndColumn)); + } + } + if (!Title.empty()) + Properties.push_back("title=" + escapeCommand(Title, true)); + std::string Command = "::" + Level.str(); + if (!Properties.empty()) + Command += " " + llvm::join(Properties, ","); + return Command + "::" + escapeCommand(Message, false) + "\n"; +} + +llvm::json::Value verify::sarifLog(llvm::ArrayRef Records, + const SarifInvocation &Invocation) { + std::vector> RuleLevels; + std::map RuleIndex; + std::vector Files; + std::map FileIndex; + auto physical = [&](const llvm::json::Object &Source) { + llvm::json::Object Location; + const std::string File = text(Source, "file"); + if (File.empty()) + return Location; + const std::string URI = fileURI(File); + auto [It, Inserted] = FileIndex.try_emplace(URI, Files.size()); + if (Inserted) + Files.push_back(URI); + Location["artifactLocation"] = llvm::json::Object{ + {"uri", URI}, {"index", static_cast(It->second)}}; + Location["region"] = region(Source); + return Location; + }; + + llvm::json::Array Results; + llvm::json::Object Properties; + llvm::json::Array Times; + for (const llvm::json::Value &Value : Records) { + const llvm::json::Object *Record = Value.getAsObject(); + if (!Record) + continue; + const std::string Schema = text(*Record, "schema"); + if (Schema == SummarySchema) { + Properties["summary"] = llvm::json::Value(llvm::json::Object(*Record)); + continue; + } + if (Schema != DiagnosticSchema) { + Times.push_back(llvm::json::Object(*Record)); + continue; + } + Classification Class = classify(*Record); + auto [It, Inserted] = RuleIndex.try_emplace(Class.Rule, RuleLevels.size()); + if (Inserted) + RuleLevels.emplace_back(Class.Rule, + Class.Level == "none" ? "note" : Class.Level); + + llvm::json::Object Result; + Result["ruleId"] = Class.Rule; + Result["ruleIndex"] = static_cast(It->second); + Result["kind"] = Class.Kind; + Result["level"] = Class.Level; + std::string Message = + verdictPrefix(text(*Record, "status")) + text(*Record, "message"); + if (std::string Values = text(*Record, "counterexample"); !Values.empty()) + Message += "\ncounterexample: " + Values; + if (const llvm::json::Array *Notes = Record->getArray("notes")) + for (const llvm::json::Value &Note : *Notes) + if (std::optional Line = Note.getAsString()) + Message += "\n" + Line->str(); + Result["message"] = llvm::json::Object{{"text", Message}}; + + const llvm::json::Object *Source = Record->getObject("source"); + if (!Source) + Source = Record->getObject("function_source"); + llvm::json::Object Location; + if (Source) + if (llvm::json::Object Physical = physical(*Source); !Physical.empty()) + Location["physicalLocation"] = std::move(Physical); + if (std::string Function = text(*Record, "function"); !Function.empty()) + Location["logicalLocations"] = llvm::json::Array{ + llvm::json::Object{{"name", Function}, {"kind", "function"}}}; + if (!Location.empty()) + Result["locations"] = llvm::json::Array{std::move(Location)}; + + if (const llvm::json::Array *Trace = Record->getArray("trace")) { + llvm::json::Array Steps; + for (const llvm::json::Value &Step : *Trace) { + const llvm::json::Object *Event = Step.getAsObject(); + if (!Event) + continue; + llvm::json::Object At; + if (const llvm::json::Object *StepSource = Event->getObject("source")) + if (llvm::json::Object Physical = physical(*StepSource); + !Physical.empty()) + At["physicalLocation"] = std::move(Physical); + At["message"] = llvm::json::Object{{"text", text(*Event, "text")}}; + Steps.push_back(llvm::json::Object{{"location", std::move(At)}}); + } + if (!Steps.empty()) + Result["codeFlows"] = llvm::json::Array{llvm::json::Object{ + {"threadFlows", llvm::json::Array{llvm::json::Object{ + {"locations", std::move(Steps)}}}}}}; + } + + if (const llvm::json::Array *Related = Record->getArray("related")) { + llvm::json::Array Locations; + for (const llvm::json::Value &Each : *Related) { + const llvm::json::Object *Note = Each.getAsObject(); + if (!Note) + continue; + llvm::json::Object At; + At["id"] = static_cast(Locations.size()); + if (const llvm::json::Object *NoteSource = Note->getObject("source")) + if (llvm::json::Object Physical = physical(*NoteSource); + !Physical.empty()) + At["physicalLocation"] = std::move(Physical); + At["message"] = llvm::json::Object{{"text", text(*Note, "message")}}; + Locations.push_back(std::move(At)); + } + if (!Locations.empty()) + Result["relatedLocations"] = std::move(Locations); + } + + llvm::json::Object Bag; + for (const char *Key : + {"status", "reason", "backend", "counterexample", "option"}) + if (std::string Value = text(*Record, Key); !Value.empty()) + Bag[Key] = Value; + if (const llvm::json::Object *Obligation = Record->getObject("obligation")) + if (std::optional Id = Obligation->getString("id")) + Bag["obligation"] = Id->str(); + if (!Bag.empty()) + Result["properties"] = std::move(Bag); + Results.push_back(std::move(Result)); + } + if (!Times.empty()) + Properties["times"] = std::move(Times); + + llvm::json::Array Rules; + for (const auto &[Id, Level] : RuleLevels) + Rules.push_back(llvm::json::Object{ + {"id", Id}, + {"shortDescription", llvm::json::Object{{"text", ruleDescription(Id)}}}, + {"helpUri", "https://swayaminsync.github.io/cpp-verify/language/" + "tooling.html#structured-diagnostics"}, + {"defaultConfiguration", llvm::json::Object{{"level", Level}}}}); + llvm::json::Array Artifacts; + for (const std::string &URI : Files) + Artifacts.push_back( + llvm::json::Object{{"location", llvm::json::Object{{"uri", URI}}}}); + llvm::json::Array Arguments; + for (const std::string &Argument : Invocation.Arguments) + Arguments.push_back(jsonText(Argument)); + + llvm::json::Object Run; + Run["tool"] = llvm::json::Object{ + {"driver", + llvm::json::Object{ + {"name", "cpp-verify"}, + {"version", Invocation.Version}, + {"informationUri", "https://swayaminsync.github.io/cpp-verify/"}, + {"rules", std::move(Rules)}}}}; + Run["invocations"] = llvm::json::Array{ + llvm::json::Object{{"arguments", std::move(Arguments)}, + {"executionSuccessful", Invocation.ExitCode != 130}, + {"exitCode", Invocation.ExitCode}}}; + Run["artifacts"] = std::move(Artifacts); + Run["columnKind"] = "unicodeCodePoints"; + Run["results"] = std::move(Results); + if (!Properties.empty()) + Run["properties"] = std::move(Properties); + return llvm::json::Object{ + {"$schema", "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/" + "schemas/sarif-schema-2.1.0.json"}, + {"version", "2.1.0"}, + {"runs", llvm::json::Array{std::move(Run)}}}; +} diff --git a/clang/lib/Verify/Driver/StructuredOutput.h b/clang/lib/Verify/Driver/StructuredOutput.h new file mode 100644 index 0000000000..8ca9d47460 --- /dev/null +++ b/clang/lib/Verify/Driver/StructuredOutput.h @@ -0,0 +1,44 @@ +//===--- StructuredOutput.h - JSON, SARIF, and GitHub output ----*- C++ -*-===// +#ifndef LLVM_CLANG_VERIFY_DRIVER_STRUCTUREDOUTPUT_H +#define LLVM_CLANG_VERIFY_DRIVER_STRUCTUREDOUTPUT_H + +#include "../Backend/Obligation.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include +#include + +namespace clang { +namespace verify { + +inline constexpr const char DiagnosticSchema[] = "cppverify.diagnostic/2"; +inline constexpr const char SummarySchema[] = "cppverify.summary/1"; + +/// Text as JSON takes it: invalid UTF-8 replaced. +std::string jsonText(llvm::StringRef Text); + +/// The source a term spans, from its first column to the one after its last. +llvm::json::Object sourceJSON(const ObligationSource &Source); + +/// A GitHub workflow command that annotates Source (an error, a warning, or a +/// notice) with Title and Message. +std::string githubCommand(llvm::StringRef Level, const ObligationSource &Source, + llvm::StringRef Title, llvm::StringRef Message); + +/// How the run that produced the records went. +struct SarifInvocation { + std::string Version; + std::vector Arguments; + int ExitCode = 0; +}; + +/// A SARIF 2.1.0 log of the records of a run: cppverify.diagnostic/2 records +/// become results, and the summary and time records the run's properties. +llvm::json::Value sarifLog(llvm::ArrayRef Records, + const SarifInvocation &Invocation); + +} // namespace verify +} // namespace clang + +#endif diff --git a/clang/lib/Verify/Driver/Verifier.cpp b/clang/lib/Verify/Driver/Verifier.cpp index ffbcefbafa..a258c10b89 100644 --- a/clang/lib/Verify/Driver/Verifier.cpp +++ b/clang/lib/Verify/Driver/Verifier.cpp @@ -23,13 +23,16 @@ #include "../Transform/Passivize.h" #include "../Transform/SpecInline.h" #include "../Transform/UBChecks.h" +#include "Counterexample.h" #include "CppVerifyVersion.h" #include "DumpIR.h" #include "LeanAutomation.h" #include "LeanCertifier.h" +#include "StructuredOutput.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/TextDiagnostic.h" +#include "clang/Lex/Lexer.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" @@ -847,6 +850,7 @@ std::optional confirmCounterexample( Item.StableId = Like.StableId; Item.Kind = Like.Kind; Item.Loc = Like.Loc; + Item.BeginLoc = Like.BeginLoc; Item.EndLoc = Like.EndLoc; Item.Source = Like.Source; Item.Goal = std::move(Goal); @@ -900,19 +904,11 @@ std::optional confirmCounterexample( for (const VerifyModelValue &Value : R.Unchecked) if (Fixed.count(Value.InternalName)) Failed.Model.push_back(Value); - std::string Message; - for (const VerifyModelValue &Value : Failed.Model) { - Message += (Message.empty() ? "" : ", ") + Value.DisplayName; - if (Value.DisplayName != Value.InternalName) - Message += " [ssa=" + Value.InternalName + "]"; - Message += " [type=" + formatLogicSort(Value.Sort) + "] = " + *Value.Value; - } std::string Using; for (const std::string &Identity : Contracts) if (auto It = Functions.find(Identity); It != Functions.end()) Using += (Using.empty() ? "" : ", ") + It->second->Name; Failed.Message = - Message + (Message.empty() ? "" : "; ") + "confirmed by a proof at this input" + (Using.empty() ? std::string() : " that uses the contract of " + Using) + ", since its check could not compute " + R.UncheckedApplication; @@ -1162,6 +1158,9 @@ struct VerifyDiagnostic { /// For an error about source the verifier cannot represent: that source, /// shown with its line as a compiler error is. SourceRange Span; + /// The declaration of the function the result is about, where a compiler + /// remark about a proof points. + SourceLocation FunctionLoc; /// A spec function that needs no check, listed with --verbose: no verdict. bool Axiom = false; /// What else the verifier found about the verdict, each printed as a note. @@ -1180,6 +1179,14 @@ static uint64_t nanosecondsSince(std::chrono::steady_clock::time_point Start) { .count(); } +/// The kind of check a failure is, "[postcondition]"; its location is the +/// diagnostic's, and JSON keeps the stable obligation ID. +static std::string obligationTag(const VerifyResult &Result) { + if (Result.ObligationType) + return std::string(" [") + obligationKindName(*Result.ObligationType) + "]"; + return Result.ObligationId.empty() ? "" : " [" + Result.ObligationId + "]"; +} + /// The backend a lowering reports, as lower-only names it. static std::string backendName(BackendKind Kind) { return Kind == BackendKind::CVC5 ? "cvc5" @@ -1314,22 +1321,6 @@ static llvm::StringRef traceKindCode(DiagnosticTraceKind Kind) { return "branch"; } -static std::string jsonText(llvm::StringRef Text) { - return llvm::json::isUTF8(Text) ? Text.str() : llvm::json::fixUTF8(Text); -} - -static llvm::json::Object sourceJSON(const ObligationSource &Source) { - llvm::json::Object Result; - if (!Source.isValid()) - return Result; - Result["file"] = jsonText(Source.File); - Result["line"] = Source.Line; - Result["column"] = Source.Column; - Result["end_line"] = Source.EndLine != 0 ? Source.EndLine : Source.Line; - Result["end_column"] = - Source.EndColumn != 0 ? Source.EndColumn : Source.Column; - return Result; -} static std::string leanProjectPath(llvm::StringRef Root, llvm::ArrayRef Components) { @@ -2980,35 +2971,67 @@ class Verifier { return llvm::Error::success(); } - void annotateObligationSources(ObligationModule &Module) { + /// The source from BeginLoc to the end of the token at EndLoc, anchored at + /// Loc; in a macro, the source of its use. Empty when Loc has none. + ObligationSource sourceOf(SourceLocation Loc, SourceLocation BeginLoc, + SourceLocation EndLoc) const { const SourceManager &SourceMgr = Ctx.getSourceManager(); - auto annotateSource = [&](SourceLocation Loc, SourceLocation EndLoc, - ObligationSource &Source) { - if (!Loc.isValid()) - return; - PresumedLoc Location = SourceMgr.getPresumedLoc(Loc); - if (!Location.isValid()) - return; - ObligationSource Found; - Found.File = Location.getFilename(); - Found.Line = Location.getLine(); - Found.Column = Location.getColumn(); - PresumedLoc End = - SourceMgr.getPresumedLoc(EndLoc.isValid() ? EndLoc : Loc); - if (End.isValid() && Found.File == End.getFilename() && - (End.getLine() > Found.Line || - (End.getLine() == Found.Line && End.getColumn() >= Found.Column))) { - Found.EndLine = End.getLine(); - Found.EndColumn = End.getColumn(); - } else { - Found.EndLine = Found.Line; - Found.EndColumn = Found.Column; + ObligationSource Found; + if (!Loc.isValid()) + return Found; + PresumedLoc Location = SourceMgr.getPresumedLoc(Loc); + if (!Location.isValid()) + return Found; + Found.File = Location.getFilename(); + Found.Line = Location.getLine(); + Found.Column = Location.getColumn(); + auto inFile = [&](const PresumedLoc &At) { + return At.isValid() && Found.File == At.getFilename(); + }; + auto before = [](unsigned Line, unsigned Column, unsigned OtherLine, + unsigned OtherColumn) { + return Line < OtherLine || (Line == OtherLine && Column < OtherColumn); + }; + SourceLocation Last = EndLoc.isValid() ? EndLoc : Loc; + if (Last.isMacroID()) + Last = SourceMgr.getExpansionRange(Last).getEnd(); + SourceLocation After = + Lexer::getLocForEndOfToken(Last, 0, SourceMgr, Ctx.getLangOpts()); + PresumedLoc End = SourceMgr.getPresumedLoc(After.isValid() ? After : Last); + if (inFile(End) && + !before(End.getLine(), End.getColumn(), Found.Line, Found.Column)) { + Found.EndLine = End.getLine(); + Found.EndColumn = End.getColumn(); + } else { + Found.EndLine = Found.Line; + Found.EndColumn = Found.Column; + } + if (BeginLoc.isValid()) { + if (BeginLoc.isMacroID()) + BeginLoc = SourceMgr.getExpansionRange(BeginLoc).getBegin(); + PresumedLoc Begin = SourceMgr.getPresumedLoc(BeginLoc); + if (inFile(Begin) && before(Begin.getLine(), Begin.getColumn(), + Found.Line, Found.Column)) { + Found.BeginLine = Begin.getLine(); + Found.BeginColumn = Begin.getColumn(); } + } + return Found; + } + + void annotateObligationSources(ObligationModule &Module) { + auto annotateSource = [&](SourceLocation Loc, SourceLocation BeginLoc, + SourceLocation EndLoc, ObligationSource &Source) { + ObligationSource Found = sourceOf(Loc, BeginLoc, EndLoc); + if (!Found.isValid()) + return; // A term shared with a module annotated before, which other threads // may be reading, is read and not written. if (Source.File != Found.File || Source.Line != Found.Line || Source.Column != Found.Column || Source.EndLine != Found.EndLine || - Source.EndColumn != Found.EndColumn) + Source.EndColumn != Found.EndColumn || + Source.BeginLine != Found.BeginLine || + Source.BeginColumn != Found.BeginColumn) Source = std::move(Found); }; // Terms share subterms: each node once. @@ -3022,7 +3045,7 @@ class Verifier { Pending.pop_back(); if (!Annotated.insert(Expr).second) continue; - annotateSource(Expr->Loc, Expr->EndLoc, Expr->Source); + annotateSource(Expr->Loc, Expr->BeginLoc, Expr->EndLoc, Expr->Source); for (const auto &Child : Expr->Children) if (Child) Pending.push_back(Child.get()); @@ -3033,7 +3056,7 @@ class Verifier { annotateExpr(Module.CounterexampleQuery.get()); std::map StableIdCounts; for (Obligation &Item : Module.Obligations) { - annotateSource(Item.Loc, Item.EndLoc, Item.Source); + annotateSource(Item.Loc, Item.BeginLoc, Item.EndLoc, Item.Source); std::string StableId = Module.FunctionIdentity + "::" + obligationKindName(Item.Kind) + "@"; StableId += Item.Source.isValid() @@ -3049,10 +3072,11 @@ class Verifier { } for (auto &[InternalName, Variable] : Module.DiagnosticVariables) { (void)InternalName; - annotateSource(Variable.Loc, Variable.EndLoc, Variable.Source); + annotateSource(Variable.Loc, Variable.Loc, Variable.EndLoc, + Variable.Source); } for (DiagnosticTraceEvent &Event : Module.TraceEvents) { - annotateSource(Event.Loc, Event.EndLoc, Event.Source); + annotateSource(Event.Loc, Event.Loc, Event.EndLoc, Event.Source); annotateExpr(Event.Guard.get()); for (DiagnosticTraceValue &Value : Event.Values) annotateExpr(Value.Value.get()); @@ -3272,6 +3296,7 @@ class Verifier { OutsideFunctions = true; else D.FunctionName = Err.FunctionName; + D.FunctionLoc = Err.FunctionLoc; D.Hidden = !Err.Function.empty() && !selects(Err.FunctionName); if (!Err.Function.empty() && UnsupportedConstructs.emplace(Err.Function, Err.Message).second) { @@ -3799,9 +3824,10 @@ class Verifier { (Bound == 0 ? std::string("with no iteration of any loop") : "within " + iterations(Bound) + " of each loop") + - (Result.Message.empty() + (counterexampleText(Result).empty() ? std::string() - : " (counterexample: " + Result.Message + ")")); + : " (counterexample: " + counterexampleText(Result) + + ")")); return Notes; } CopiesHold &= Result.Status == VerifyStatus::Verified; @@ -3984,11 +4010,10 @@ class Verifier { Interface.Name, R}); Run.ContractCheck = Diags.size() - 1; } else if (R.Status == VerifyStatus::Failed) { - std::string Message = "verification failed: " + Label; - if (!R.ObligationId.empty()) - Message += " [" + R.ObligationId + "]"; + std::string Message = + "verification failed: " + Label + obligationTag(R); if (!R.Message.empty()) - Message += " (counterexample: " + R.Message + ")"; + Message += " (" + R.Message + ")"; Message += backendSuffix(R); unsettled(VerifyDiagnostic::Error, std::move(Message), R); } else { @@ -5056,11 +5081,10 @@ class Verifier { // The failure, and those after it --max-failures reports, each // with its counterexample and notes. auto failure = [&](const VerifyResult &Failure) { - std::string Msg = "verification failed: " + Fn->Name; - if (!Failure.ObligationId.empty()) - Msg += " [" + Failure.ObligationId + "]"; + std::string Msg = + "verification failed: " + Fn->Name + obligationTag(Failure); if (!Failure.Message.empty()) - Msg += " (counterexample: " + Failure.Message + ")"; + Msg += " (" + Failure.Message + ")"; Msg += backendSuffix(Failure); Diags.push_back({VerifyDiagnostic::Error, Msg, Failure.Location, Fn->Name, Failure}); @@ -5213,6 +5237,7 @@ class Verifier { // What a function --function leaves out reports stays, hidden, for // what it establishes. D.Hidden |= !Selected[I]; + D.FunctionLoc = Functions[I]->DeclLoc; Diags.push_back(std::move(D)); } AllOk &= Run.AllOk; @@ -5930,11 +5955,9 @@ class Verifier { VerifyResult &Failed = *Outcomes[I]; std::string Message; if (Pending.Label == "verification") { - Message = "verification failed: " + Pending.Fn->Name; - if (!Failed.ObligationId.empty()) - Message += " [" + Failed.ObligationId + "]"; - Message += " (counterexample: " + Failed.Message + ")" + - backendSuffix(Failed); + Message = "verification failed: " + Pending.Fn->Name + + obligationTag(Failed) + " (" + Failed.Message + ")" + + backendSuffix(Failed); } else { Message = Pending.Label + " failed: " + Pending.Fn->Name + backendSuffix(Failed) + " (" + Failed.Message + ")"; @@ -6137,7 +6160,7 @@ class Verifier { void printJSONDiagnostic(const VerifyDiagnostic &D, llvm::raw_ostream &OS) const { llvm::json::Object Record; - Record["schema"] = "cppverify.diagnostic/1"; + Record["schema"] = "cppverify.diagnostic/2"; Record["status"] = D.Result ? verifyStatusCode(D.Result->Status) : diagnosticKindCode(D.K); Record["severity"] = @@ -6161,36 +6184,22 @@ class Verifier { ObligationSource Source; if (D.Result) Source = D.Result->Source; - if (!Source.isValid() && D.Loc.isValid()) { - PresumedLoc Location = Ctx.getSourceManager().getPresumedLoc(D.Loc); - if (Location.isValid()) { - Source.File = Location.getFilename(); - Source.Line = Location.getLine(); - Source.Column = Location.getColumn(); - Source.EndLine = Source.Line; - Source.EndColumn = Source.Column; - if (D.Span.isValid()) - if (PresumedLoc End = - Ctx.getSourceManager().getPresumedLoc(D.Span.getEnd()); - End.isValid()) { - Source.EndLine = End.getLine(); - Source.EndColumn = End.getColumn(); - } - } - } + if (!Source.isValid()) + Source = D.Span.isValid() + ? sourceOf(D.Loc, D.Span.getBegin(), D.Span.getEnd()) + : sourceOf(D.Loc, D.Loc, D.Loc); if (Source.isValid()) Record["source"] = sourceJSON(Source); + if (ObligationSource Function = + sourceOf(D.FunctionLoc, D.FunctionLoc, D.FunctionLoc); + Function.isValid()) + Record["function_source"] = sourceJSON(Function); if (const std::pair *Instantiation = instantiationOf(D)) - if (PresumedLoc Point = - Ctx.getSourceManager().getPresumedLoc(Instantiation->first); - Point.isValid()) { - ObligationSource At; - At.File = Point.getFilename(); - At.Line = At.EndLine = Point.getLine(); - At.Column = At.EndColumn = Point.getColumn(); + if (ObligationSource At = sourceOf( + Instantiation->first, Instantiation->first, Instantiation->first); + At.isValid()) Record["instantiated_at"] = sourceJSON(At); - } if (D.Vacuous) Record["vacuous"] = true; @@ -6304,6 +6313,9 @@ class Verifier { Obligation["source"] = sourceJSON(Result.Source); Record["obligation"] = std::move(Obligation); } + if (Result.Status == VerifyStatus::Failed) + if (std::string Values = counterexampleText(Result); !Values.empty()) + Record["counterexample"] = jsonText(Values); if (!Result.Model.empty()) { llvm::json::Array Model; for (const VerifyModelValue &Value : Result.Model) { @@ -6316,6 +6328,8 @@ class Verifier { : llvm::json::Value(nullptr); if (Value.Source.isValid()) Entry["source"] = sourceJSON(Value.Source); + if (Value.OnPath == false) + Entry["on_path"] = false; Model.push_back(std::move(Entry)); } Record["model"] = std::move(Model); @@ -6326,6 +6340,7 @@ class Verifier { llvm::json::Object Entry; Entry["kind"] = traceKindCode(Event.Kind); Entry["message"] = jsonText(Event.Message); + Entry["text"] = jsonText(traceStepText(Event)); Entry["active"] = Event.Active ? llvm::json::Value(*Event.Active) : llvm::json::Value(nullptr); if (Event.Source.isValid()) @@ -6563,8 +6578,16 @@ class Verifier { continue; if (Opts.Summary) count(D, *Opts.Summary); - if (!quieted(D)) + if (quieted(D)) + continue; + if (Opts.Parts) { + std::vector Parts = partsOf(D); + if (Parts.front().Loc.isInvalid()) + Parts.front().Loc = D.FunctionLoc; + Opts.Parts->insert(Opts.Parts->end(), Parts.begin(), Parts.end()); + } else { printDiagnostic(D, OS); + } } } @@ -6639,94 +6662,212 @@ class Verifier { return Report; } - void printDiagnostic(const VerifyDiagnostic &D, llvm::raw_ostream &OS) const { - if (Opts.Diagnostics == DiagnosticFormat::Json) { - printJSONDiagnostic(D, OS); - return; - } - const std::pair *Instantiation = - instantiationOf(D); - const std::string InstantiationNote = - Instantiation ? "in instantiation of " + Instantiation->second + " '" + - D.FunctionName + "' requested here" - : std::string(); - if (D.K == VerifyDiagnostic::Error && D.Span.isValid()) { - DiagnosticOptions DiagOpts; - DiagOpts.ShowPresumedLoc = true; - TextDiagnostic Text(OS, Ctx.getLangOpts(), DiagOpts); - Text.emitDiagnostic( - FullSourceLoc(D.Span.getBegin(), Ctx.getSourceManager()), - DiagnosticsEngine::Error, D.Message, - CharSourceRange::getTokenRange(D.Span), {}); - if (Instantiation) - Text.emitDiagnostic( - FullSourceLoc(Instantiation->first, Ctx.getSourceManager()), - DiagnosticsEngine::Note, InstantiationNote, {}, {}); - return; + /// An error as Clang diagnostics: the error at the failing source, with + /// its range; for a counterexample, its values in source names and each + /// step of its path at its location; then the error's notes. + std::vector + errorParts(const VerifyDiagnostic &D) const { + std::vector Parts; + VerifyDiagnosticPart Error; + Error.Level = DiagnosticsEngine::Error; + Error.Message = D.Message; + if (D.Span.isValid()) { + Error.Loc = D.Span.getBegin(); + Error.Range = D.Span; + } else { + Error.Loc = D.Loc; + // An obligation's range underlines its expression, the caret at its + // operator, when the expression lies on the caret's line; one that runs + // to another line (a spec's post through to its return) is not shown. + const SourceManager &SM = Ctx.getSourceManager(); + auto onCaretLine = [&](SourceLocation Loc) { + return Loc.isValid() && SM.isWrittenInSameFile(D.Loc, Loc) && + SM.getSpellingLineNumber(D.Loc) == SM.getSpellingLineNumber(Loc); + }; + if (D.Result && D.Result->Location == D.Loc) { + SourceLocation Begin = D.Result->BeginLocation; + SourceLocation End = D.Result->EndLocation; + if (!onCaretLine(Begin) || SM.isBeforeInTranslationUnit(D.Loc, Begin)) + Begin = D.Loc; + if (onCaretLine(End) && !SM.isBeforeInTranslationUnit(End, D.Loc) && + (Begin != D.Loc || End != D.Loc)) + Error.Range = SourceRange(Begin, End); + } } - if (D.Loc.isValid()) { - PresumedLoc PLoc = Ctx.getSourceManager().getPresumedLoc(D.Loc); - if (PLoc.isValid()) - OS << PLoc.getFilename() << ":" << PLoc.getLine() << ":" - << PLoc.getColumn() << ": "; + Parts.push_back(Error); + // A step inside a macro is shown where the macro is used: the error + // already shows the expansion, and each note would repeat it. + auto note = [&](SourceLocation Loc, std::string Message) { + if (Loc.isMacroID()) + Loc = Ctx.getSourceManager().getExpansionLoc(Loc); + Parts.push_back({DiagnosticsEngine::Note, Loc, {}, std::move(Message)}); + }; + if (D.Result && D.Result->Status == VerifyStatus::Failed) { + const VerifyResult &R = *D.Result; + if (std::string Values = counterexampleText(R); !Values.empty()) + note(Error.Loc, "counterexample: " + Values); + const size_t Steps = R.Trace.size(); + for (size_t I = 0; I != Steps; ++I) { + if (Steps > TraceStepLimit && I == TraceStepLimit / 2) + note({}, "(skipping " + std::to_string(Steps - TraceStepLimit) + + " steps of the path; JSON lists every one)"); + if (Steps > TraceStepLimit && I >= TraceStepLimit / 2 && + I < Steps - TraceStepLimit / 2) + continue; + note(R.Trace[I].Loc, traceStepText(R.Trace[I])); + } } + if (const auto *Instantiation = instantiationOf(D)) + note(Instantiation->first, "in instantiation of " + + Instantiation->second + " '" + + D.FunctionName + "' requested here"); + for (const std::string &Note : D.Notes) + note({}, Note); + return Parts; + } + + /// The line a result prints, apart from its location: "Verified: f". + std::string statusLine(const VerifyDiagnostic &D) const { switch (D.K) { case VerifyDiagnostic::Lowered: - OS << "Lowered: " << D.Message << "\n"; - break; + return "Lowered: " + D.Message; case VerifyDiagnostic::Verified: - OS << "Verified: " << D.Message << "\n"; - break; + return "Verified: " + D.Message; case VerifyDiagnostic::Error: - OS << "error: " << D.Message << "\n"; - break; + case VerifyDiagnostic::Warning: + return D.Message; case VerifyDiagnostic::Unresolved: - OS << "Unresolved: " << D.Message << "\n"; - if (D.Result) - for (const QuantifierProfileEntry &Entry : D.Result->QuantifierProfile) - OS << "note: the quantifier at " << Entry.Line << ":" << Entry.Column - << " was instantiated " << Entry.Instances - << " times, up to generation " << Entry.MaxGeneration << "\n"; - break; + return "Unresolved: " + D.Message; case VerifyDiagnostic::Interrupted: - OS << "Interrupted: " << D.Message << "\n"; - break; + return "Interrupted: " + D.Message; case VerifyDiagnostic::BoundedSafe: - OS << "BoundedSafe: " << D.Message << "\n"; - break; - case VerifyDiagnostic::Exported: - OS << "Exported: " << D.Message; + return "BoundedSafe: " + D.Message; + case VerifyDiagnostic::Exported: { + std::string Line = "Exported: " + D.Message; if (D.LeanAutomatic && D.Result) - OS << " (cppverify_auto proved " << D.LeanAutomatic << " of " - << std::set(D.Result->LeanGoals.begin(), - D.Result->LeanGoals.end()) - .size() - << " goals)"; - OS << "\n"; - break; + Line += + " (cppverify_auto proved " + std::to_string(D.LeanAutomatic) + + " of " + + std::to_string(std::set(D.Result->LeanGoals.begin(), + D.Result->LeanGoals.end()) + .size()) + + " goals)"; + return Line; + } case VerifyDiagnostic::Certified: - OS << "Certified: " << D.Message << "\n"; - break; + return "Certified: " + D.Message; case VerifyDiagnostic::MixedProof: - OS << "Proved (" << D.Result->BackendName << "): " << D.Message << "\n"; - break; + return "Proved (" + D.Result->BackendName + "): " + D.Message; case VerifyDiagnostic::Trusted: - OS << "Trusted: " << D.Message << "\n"; - break; - case VerifyDiagnostic::Warning: - OS << "warning: " << D.Message << "\n"; - break; - } - if (Instantiation) { - PresumedLoc PLoc = - Ctx.getSourceManager().getPresumedLoc(Instantiation->first); - if (PLoc.isValid()) - OS << PLoc.getFilename() << ":" << PLoc.getLine() << ":" - << PLoc.getColumn() << ": "; - OS << "note: " << InstantiationNote << "\n"; + return "Trusted: " + D.Message; } + return D.Message; + } + + /// A result as Clang diagnostics: an error with its counterexample, a + /// warning, or a status line (an error when unresolved, else a remark), + /// each followed by its notes. + std::vector partsOf(const VerifyDiagnostic &D) const { + if (D.K == VerifyDiagnostic::Error) + return errorParts(D); + std::vector Parts; + DiagnosticsEngine::Level Level = DiagnosticsEngine::Remark; + if (D.K == VerifyDiagnostic::Warning) + Level = DiagnosticsEngine::Warning; + else if (D.K == VerifyDiagnostic::Unresolved || + D.K == VerifyDiagnostic::Interrupted) + Level = DiagnosticsEngine::Error; + Parts.push_back({Level, D.Loc, {}, statusLine(D)}); + auto note = [&](SourceLocation Loc, std::string Message) { + Parts.push_back({DiagnosticsEngine::Note, Loc, {}, std::move(Message)}); + }; + if (D.K == VerifyDiagnostic::Unresolved && D.Result) + for (const QuantifierProfileEntry &Entry : D.Result->QuantifierProfile) + note({}, "the quantifier at " + std::to_string(Entry.Line) + ":" + + std::to_string(Entry.Column) + " was instantiated " + + std::to_string(Entry.Instances) + + " times, up to generation " + + std::to_string(Entry.MaxGeneration)); + if (const auto *Instantiation = instantiationOf(D)) + note(Instantiation->first, "in instantiation of " + + Instantiation->second + " '" + + D.FunctionName + "' requested here"); for (const std::string &Note : D.Notes) - OS << "note: " << Note << "\n"; + note({}, Note); + return Parts; + } + + /// A GitHub workflow command that annotates the source of an error or a + /// warning, or nothing for another result. + std::string githubAnnotation(const VerifyDiagnostic &D) const { + const bool Error = D.K == VerifyDiagnostic::Error || + D.K == VerifyDiagnostic::Unresolved || + D.K == VerifyDiagnostic::Interrupted; + if (!Error && D.K != VerifyDiagnostic::Warning && + D.K != VerifyDiagnostic::BoundedSafe) + return ""; + ObligationSource Source; + if (D.Result) + Source = D.Result->Source; + if (!Source.isValid()) + Source = sourceOf(D.Loc.isValid() ? D.Loc : D.FunctionLoc, + D.Span.getBegin(), D.Span.getEnd()); + std::string Message = statusLine(D); + if (D.Result && D.Result->Status == VerifyStatus::Failed) + if (std::string Values = counterexampleText(*D.Result); !Values.empty()) + Message += "\ncounterexample: " + Values; + for (const std::string &Note : D.Notes) + Message += "\n" + Note; + std::string Title = "cpp-verify"; + if (D.K == VerifyDiagnostic::Unresolved && D.Result) + Title += + ": unresolved (" + verifyReasonCode(D.Result->Reason).str() + ")"; + else if (D.Result && D.Result->ObligationType) + Title += + std::string(": ") + obligationKindName(*D.Result->ObligationType); + return githubCommand(Error ? "error" : "warning", Source, Title, Message); + } + + /// Errors and warnings print as Clang prints its diagnostics, with the + /// source they are about; other results as their status line. + void printDiagnostic(const VerifyDiagnostic &D, llvm::raw_ostream &OS) const { + if (Opts.Diagnostics == DiagnosticFormat::Json) { + printJSONDiagnostic(D, OS); + return; + } + printTextDiagnostic(D, OS); + if (Opts.Diagnostics == DiagnosticFormat::GitHub) + OS << githubAnnotation(D); + } + + void printTextDiagnostic(const VerifyDiagnostic &D, + llvm::raw_ostream &OS) const { + const std::vector Parts = partsOf(D); + if (D.K == VerifyDiagnostic::Error || D.K == VerifyDiagnostic::Warning) { + DiagnosticOptions DiagOpts; + DiagOpts.ShowPresumedLoc = true; + TextDiagnostic Text(OS, Ctx.getLangOpts(), DiagOpts); + for (const VerifyDiagnosticPart &Part : Parts) { + llvm::SmallVector Ranges; + if (Part.Range.isValid()) + Ranges.push_back(CharSourceRange::getTokenRange(Part.Range)); + Text.emitDiagnostic( + Part.Loc.isValid() ? FullSourceLoc(Part.Loc, Ctx.getSourceManager()) + : FullSourceLoc(), + Part.Level, Part.Message, Ranges, {}); + } + return; + } + for (const VerifyDiagnosticPart &Part : Parts) { + if (Part.Loc.isValid()) { + PresumedLoc PLoc = Ctx.getSourceManager().getPresumedLoc(Part.Loc); + if (PLoc.isValid()) + OS << PLoc.getFilename() << ":" << PLoc.getLine() << ":" + << PLoc.getColumn() << ": "; + } + OS << (Part.Level == DiagnosticsEngine::Note ? "note: " : "") + << Part.Message << "\n"; + } } }; diff --git a/clang/lib/Verify/Driver/Verifier.h b/clang/lib/Verify/Driver/Verifier.h index 01a0477cf8..d24b01ac41 100644 --- a/clang/lib/Verify/Driver/Verifier.h +++ b/clang/lib/Verify/Driver/Verifier.h @@ -3,6 +3,7 @@ #define LLVM_CLANG_VERIFY_DRIVER_VERIFIER_H #include "../Backend/VerifyBackend.h" +#include "DiagnosticPart.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -31,7 +32,9 @@ inline constexpr uint64_t DefaultMaxQueryNodes = 4000000; /// The megabytes Z3's queries may allocate together, and each cvc5 process. inline constexpr uint64_t DefaultMemoryLimitMB = 8192; -enum class DiagnosticFormat { Text, Json }; +/// How results are printed: text, JSON Lines (cppverify.diagnostic/2), or +/// text with a GitHub workflow annotation after each error and warning. +enum class DiagnosticFormat { Text, Json, GitHub }; /// Which obligations of an unresolved module --lean-fallback exports. enum class LeanFallbackScope { @@ -139,6 +142,9 @@ struct VerifyOptions { unsigned ConfirmBound = 10; /// Where the run counts the verdicts it prints, if anywhere. VerifySummary *Summary = nullptr; + /// Where to put each result as Clang diagnostics instead of printing it, + /// for the compile path to report through the compiler's own engine. + std::vector *Parts = nullptr; /// A run over several files, which holds each file's results until every /// file is verified (see CrossFileRun), or null. class CrossFileRun *CrossFile = nullptr; diff --git a/clang/lib/Verify/Frontend/ASTConverter.cpp b/clang/lib/Verify/Frontend/ASTConverter.cpp index 7da19f9b3e..f0e0100b97 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.cpp +++ b/clang/lib/Verify/Frontend/ASTConverter.cpp @@ -145,8 +145,10 @@ static std::unique_ptr mathOperand(std::unique_ptr V) { } VType From = V->Ty; SourceLocation Loc = V->Loc; + SourceLocation BeginLoc = V->BeginLoc; SourceLocation EndLoc = V->EndLoc; auto Cast = std::make_unique(std::move(V), From, To, Loc); + Cast->BeginLoc = BeginLoc; Cast->EndLoc = EndLoc; return Cast; } @@ -1299,6 +1301,9 @@ void ASTConverter::recordSourceVariable(const ValueDecl *D) { } Record(InternalBase, DisplayBase, VType::fromQualType(D->getType(), IntMode, Ctx)); + if (auto It = CurrentFn->SourceVariables.find(InternalBase); + It != CurrentFn->SourceVariables.end()) + It->second.Reference = D->getType()->isReferenceType(); } bool ASTConverter::isDynamicAddress(const Expr *E) const { @@ -2427,6 +2432,7 @@ std::vector> ASTConverter::convertTranslationUnit() { } llvm::SaveAndRestore Owner(ErrorFunction, functionIdentity(FD)); llvm::SaveAndRestore OwnerName(ErrorFunctionName, functionName(FD)); + llvm::SaveAndRestore OwnerLoc(ErrorFunctionLoc, FD->getLocation()); llvm::SaveAndRestore InContract(ErrorInContract, true); error(FD->getLocation(), FD->getQualifiedNameAsString() + ": " + *UnsupportedMember); @@ -2559,6 +2565,7 @@ std::vector> ASTConverter::convertTranslationUnit() { // The post describes the spec where its body defines it. for (auto &Post : Fn->Postconditions) { const SourceLocation PostLoc = Post->Loc; + const SourceLocation PostBegin = Post->BeginLoc; const SourceLocation PostEnd = Post->EndLoc; Post = std::make_unique( VBinOp::Or, @@ -2566,6 +2573,7 @@ std::vector> ASTConverter::convertTranslationUnit() { cloneVExpr(Fn->Domain.get()), VType::makeBool(), PostLoc), std::move(Post), VType::makeBool(), PostLoc); + Post->BeginLoc = PostBegin; Post->EndLoc = PostEnd; } Unspecified.push_back(std::move(Outside)); @@ -3233,11 +3241,76 @@ ASTConverter::exitClassInvariants(const CXXMethodDecl *Method, : std::vector(); } +/// Appends to a loop's invariants the class invariants of the objects the +/// loop can reach: the local objects alive at it and the objects its +/// function's reference and pointer parameters address. Only the class's +/// members change what an invariant states, and a public one keeps it, so it +/// holds at every iteration of a loop outside the class; like the user's +/// invariants, each is proved at entry and preserved. A member function may +/// break the invariant of another object of its class between calls, so its +/// loops get none of its class's. +void ASTConverter::appendObjectInvariants( + std::vector> &Invariants) { + const auto *Member = dyn_cast_or_null(ConvertingDecl); + auto invariantsOf = [&](QualType Object) -> const TypeContractInfo * { + const RecordDecl *Record = getRecordFromType(Object); + const TypeContractInfo *TCI = + Record ? Ctx.getTypeContract(Record) : nullptr; + if (!TCI || TCI->Invariants.empty() || + (Member && + Member->getParent()->getCanonicalDecl() == Record->getCanonicalDecl())) + return nullptr; + return TCI; + }; + auto append = [&](const TypeContractInfo &TCI, const VExpr &Address, + bool MayBeNull, SourceLocation Loc) { + llvm::SaveAndRestore Substitute(ThisSubstitute, &Address); + llvm::SaveAndRestore DropMarkers(DropExtentMarkers, true); + llvm::SaveAndRestore Contract(InContractExpression, true); + FieldSubstPrefix.clear(); + for (const Expr *Invariant : TCI.Invariants) { + auto E = convertTypeInvariantExpr(Invariant); + if (!E) + continue; + if (MayBeNull) + E = std::make_unique( + VBinOp::Or, + std::make_unique( + VBinOp::Eq, cloneVExpr(&Address), + std::make_unique(0, VType::makePtr(), Loc), + VType::makeBool(), Loc), + std::move(E), VType::makeBool(), Loc); + Invariants.push_back(std::move(E)); + } + }; + for (const auto &Scope : AutomaticScopeStack) + for (const VarDecl *VD : Scope) + if (const TypeContractInfo *TCI = invariantsOf(VD->getType())) + if (auto Address = convertAutomaticLocalAddress(VD, VD->getLocation())) + append(*TCI, *Address, /*MayBeNull=*/false, VD->getLocation()); + const FunctionContractInfo *FCI = + ConvertingDecl ? functionContract(ConvertingDecl) : nullptr; + if (!ConvertingDecl || !FCI) + return; + for (const ParmVarDecl *P : ConvertingDecl->parameters()) { + QualType Ty = P->getType(); + if ((!Ty->isLValueReferenceType() && !Ty->isPointerType()) || + hasExtentMarker(*FCI, P)) + continue; + if (const TypeContractInfo *TCI = invariantsOf(Ty->getPointeeType())) { + VVarExpr Pointer(valueName(P), VType::makePtr(), P->getLocation()); + append(*TCI, Pointer, Ty->isPointerType(), P->getLocation()); + } + } +} + std::unique_ptr ASTConverter::convertFunction(const FunctionDecl *FD) { + llvm::SaveAndRestore Converting(ConvertingDecl, FD); llvm::SaveAndRestore At(CurrentRange, SourceRange(FD->getLocation())); llvm::SaveAndRestore Owner(ErrorFunction, functionIdentity(FD)); llvm::SaveAndRestore OwnerName(ErrorFunctionName, functionName(FD)); + llvm::SaveAndRestore OwnerLoc(ErrorFunctionLoc, FD->getLocation()); llvm::SaveAndRestore InContract(ErrorInContract, true); const size_t ErrorsAtStart = Errors.size(); const FunctionContractInfo *FCI = functionContract(FD); @@ -4259,6 +4332,7 @@ std::unique_ptr ASTConverter::convertConstexprSpec(const FunctionDecl *FD) { llvm::SaveAndRestore Owner(ErrorFunction, functionIdentity(FD)); llvm::SaveAndRestore OwnerName(ErrorFunctionName, functionName(FD)); + llvm::SaveAndRestore OwnerLoc(ErrorFunctionLoc, FD->getLocation()); llvm::SaveAndRestore InContract(ErrorInContract, true); const size_t ErrorsAtStart = Errors.size(); nameParameters(FD, FD); @@ -5395,8 +5469,10 @@ std::unique_ptr ASTConverter::convertExpr(const Expr *E) { It->second, VType::fromQualType(E->getType(), IntMode, Ctx), E->getExprLoc()); auto Result = convertExprImpl(E); - if (Result && E) + if (Result && E) { + Result->BeginLoc = E->getBeginLoc(); Result->EndLoc = E->getEndLoc(); + } if (Result && E && isTriggerMarked(E)) markTrigger(Result, E); return Result; @@ -6296,6 +6372,14 @@ std::unique_ptr ASTConverter::convertExprImpl(const Expr *E) { if (U->getOpcode() == UO_AddrOf) { if (subobjectPointerRoot(U->getSubExpr())) return subobjectPointer(E, U->getSubExpr(), /*AddressOf=*/true); + // In a contract the address of a local object in memory is a value: + // a spec it is passed to cannot keep it, and nothing else can. + if (InContractExpression) + if (const auto *Ref = + dyn_cast(U->getSubExpr()->IgnoreParens())) + if (const auto *VD = dyn_cast(Ref->getDecl()); + VD && AddressableLocals.count(VD)) + return convertAutomaticLocalAddress(VD, E->getExprLoc()); error(CurrentFn->Name + ": raw address-of is unsupported until lexical lifetime and escape " "effects are modeled"); @@ -8286,6 +8370,7 @@ ASTConverter::convertRangeFor(const CXXForRangeStmt *RF) { if (auto E = convertExpr(D)) Decreases.push_back(std::move(E)); } + appendObjectInvariants(Invariants); // 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(), @@ -9059,6 +9144,7 @@ ASTConverter::convertStmtBody(const Stmt *S) { Decreases.push_back(std::move(E)); InContractExpression = SavedContract; } + appendObjectInvariants(Invariants); if (!checkConditionVariableUnnamed(WS->getConditionVariable(), Invariants, Decreases) || !checkLoopTermination(WS, !Decreases.empty())) { @@ -9124,6 +9210,7 @@ ASTConverter::convertStmtBody(const Stmt *S) { Decreases.push_back(std::move(E)); InContractExpression = SavedContract; } + appendObjectInvariants(Invariants); if (!checkLoopTermination(DS, !Decreases.empty())) { InitializedValues = Before; InitializationPathReachable = BeforeReachable; @@ -9210,6 +9297,7 @@ ASTConverter::convertStmtBody(const Stmt *S) { Decreases.push_back(std::move(E)); InContractExpression = SavedContract; } + appendObjectInvariants(Invariants); if (!checkConditionVariableUnnamed(FS->getConditionVariable(), Invariants, Decreases) || !checkLoopTermination(FS, !Decreases.empty())) { diff --git a/clang/lib/Verify/Frontend/ASTConverter.h b/clang/lib/Verify/Frontend/ASTConverter.h index dfc350c871..a419726b4d 100644 --- a/clang/lib/Verify/Frontend/ASTConverter.h +++ b/clang/lib/Verify/Frontend/ASTConverter.h @@ -20,6 +20,7 @@ struct ConversionError { /// decides; empty for an error of the translation unit. std::string Function; std::string FunctionName; + SourceLocation FunctionLoc; /// In the function's signature or contract, rather than its body: then /// its callers cannot use the contract either. bool InContract = false; @@ -55,16 +56,17 @@ class ASTConverter { /// converter is in its signature and contract or in its body. std::string ErrorFunction; std::string ErrorFunctionName; + SourceLocation ErrorFunctionLoc; bool ErrorInContract = false; void error(std::string Message) { error(CurrentRange, std::move(Message)); } void error(SourceRange Range, std::string Message) { Errors.push_back({Range, std::move(Message), ErrorFunction, - ErrorFunctionName, ErrorInContract}); + ErrorFunctionName, ErrorFunctionLoc, ErrorInContract}); } /// An error about \p Fn found once every function is converted. void errorOf(const VFunction &Fn, SourceLocation Loc, std::string Message) { - Errors.push_back( - {SourceRange(Loc), std::move(Message), Fn.Identity, Fn.Name, false}); + Errors.push_back({SourceRange(Loc), std::move(Message), Fn.Identity, + Fn.Name, Fn.DeclLoc, false}); } std::vector> Warnings; /// Maps unqualified field name -> "param." for type_invariant lowering. @@ -115,6 +117,8 @@ class ASTConverter { /// The object `this` stands for while a class invariant is instantiated /// for a parameter: the pointer or reference that addresses it. const VExpr *ThisSubstitute = nullptr; + /// The function whose body is being converted. + const FunctionDecl *ConvertingDecl = nullptr; std::set GhostLocals; unsigned DynamicProvenanceId = 0; unsigned DynamicPointerAssignmentId = 0; @@ -483,6 +487,7 @@ class ASTConverter { std::string specIdentityFromExpr(const Expr *E); void injectTypeInvariants(const FunctionDecl *FD, VFunction &Fn); std::unique_ptr convertTypeInvariantExpr(const Expr *E); + void appendObjectInvariants(std::vector> &Invariants); void emitReturnInvariantAssert(const Expr *RetE, std::vector> &Out, SourceLocation Loc); diff --git a/clang/lib/Verify/IR/VExpr.cpp b/clang/lib/Verify/IR/VExpr.cpp index a362e0ceb6..ce98cfa5c6 100644 --- a/clang/lib/Verify/IR/VExpr.cpp +++ b/clang/lib/Verify/IR/VExpr.cpp @@ -254,8 +254,10 @@ VIntMode verify::evaluatedIntMode(const VExpr *E) { std::unique_ptr verify::cloneVExpr(const VExpr *E) { auto Copy = cloneVExprImpl(E); - if (Copy && E) + if (Copy && E) { + Copy->BeginLoc = E->BeginLoc; Copy->EndLoc = E->EndLoc; + } return Copy; } diff --git a/clang/lib/Verify/IR/VExpr.h b/clang/lib/Verify/IR/VExpr.h index f05495971b..e91f9ec2cc 100644 --- a/clang/lib/Verify/IR/VExpr.h +++ b/clang/lib/Verify/IR/VExpr.h @@ -89,13 +89,16 @@ class VExpr { Kind K; VType Ty; SourceLocation Loc; + /// The source the expression spans, Loc being where in it a diagnostic + /// points (an operator's position, as Clang's getExprLoc). + SourceLocation BeginLoc; SourceLocation EndLoc; virtual ~VExpr() = default; protected: VExpr(Kind K, VType Ty, SourceLocation Loc) - : K(K), Ty(Ty), Loc(Loc), EndLoc(Loc) { + : K(K), Ty(Ty), Loc(Loc), BeginLoc(Loc), EndLoc(Loc) { NodeBudget::count(); } }; diff --git a/clang/lib/Verify/IR/VStmt.h b/clang/lib/Verify/IR/VStmt.h index 833f1e5c3b..1e5b0c75f4 100644 --- a/clang/lib/Verify/IR/VStmt.h +++ b/clang/lib/Verify/IR/VStmt.h @@ -319,6 +319,8 @@ struct VSourceVariable { VType Type; SourceLocation Loc; SourceLocation EndLoc; + /// A reference, whose value is the address of what it refers to. + bool Reference = false; }; /// reads(Base, Count) on a spec: the cells Base[0..Count), ElementSize bytes diff --git a/clang/lib/Verify/Transform/Passivize.cpp b/clang/lib/Verify/Transform/Passivize.cpp index de70753c15..acecb5a936 100644 --- a/clang/lib/Verify/Transform/Passivize.cpp +++ b/clang/lib/Verify/Transform/Passivize.cpp @@ -174,8 +174,10 @@ static std::string stateVariableName(const CloneCtx &Ctx, static std::unique_ptr cloneExpr(const VExpr *E, const CloneCtx &Ctx) { auto Copy = cloneExprImpl(E, Ctx); - if (Copy && E) + if (Copy && E) { + Copy->BeginLoc = E->BeginLoc; Copy->EndLoc = E->EndLoc; + } return Copy; } @@ -2376,6 +2378,7 @@ collectSafety(const VExpr *E, const FunctionMap *FnMap, Collect(Expanded.get(), Inside); for (SafetyCheck &Check : Inside) { Check.Cond->Loc = C->Loc; + Check.Cond->BeginLoc = C->BeginLoc; Check.Cond->EndLoc = C->EndLoc; Out.push_back(std::move(Check)); } @@ -3620,6 +3623,9 @@ class PassivizerImpl { std::set HeapBases; std::set HeapVariables; std::map ModelVariables; + /// The path condition of the statement being passivized, for the versions + /// it defines. + const std::unique_ptr *VersionGuard = nullptr; std::map> SourceVersions; std::set SuppressedSourceVariables; std::vector TraceEvents; @@ -4329,9 +4335,13 @@ class PassivizerImpl { auto Source = Fn.SourceVariables.find(Base); if (Source == Fn.SourceVariables.end()) return; - ModelVariables[Versioned] = {Source->second.DisplayName, - Source->second.Type, Source->second.Loc, - Source->second.EndLoc}; + std::shared_ptr Guard; + if (VersionGuard && *VersionGuard) + Guard = cloneVExpr(VersionGuard->get()); + ModelVariables[Versioned] = { + Source->second.DisplayName, Source->second.Type, + Source->second.Loc, Source->second.EndLoc, + Source->second.Reference, std::move(Guard)}; SourceVersions[Base].insert(Versioned); } @@ -5248,9 +5258,11 @@ class PassivizerImpl { auto afterEarlier = [&](std::unique_ptr Cond) { if (!EarlierPosts) return Cond; - const SourceLocation Loc = Cond->Loc, End = Cond->EndLoc; + const SourceLocation Loc = Cond->Loc, Begin = Cond->BeginLoc, + End = Cond->EndLoc; auto Guarded = makeOr(makeNot(cloneVExpr(EarlierPosts.get()), Loc), std::move(Cond), Loc); + Guarded->BeginLoc = Begin; Guarded->EndLoc = End; return Guarded; }; @@ -5281,6 +5293,7 @@ class PassivizerImpl { // another file. auto Cond = cloneExpr(Check.Cond.get(), PCtx); Cond->Loc = Post->Loc; + Cond->BeginLoc = Post->BeginLoc; Cond->EndLoc = Post->EndLoc; P.ExitAsserts.push_back({Check.Kind, afterEarlier(std::move(Cond))}); } @@ -6369,6 +6382,8 @@ class PassivizerImpl { std::optional> Named; if (S.K != VStmt::Seq && S.K != VStmt::GhostBlock) Named.emplace(Origin, statementOrigin(S)); + llvm::SaveAndRestore *> Defining(VersionGuard, + &Active); switch (S.K) { case VStmt::Assign: { const auto &A = static_cast(S); @@ -6415,8 +6430,6 @@ class PassivizerImpl { std::vector TraceValues; TraceValues.push_back({"address", cloneVExpr(Ptr.get())}); TraceValues.push_back({"value", cloneVExpr(Val.get())}); - emitTrace(PassiveTraceKind::HeapWrite, "store", Active.get(), St.Loc, - std::move(TraceValues)); emitExprSafety(P, Ptr.get(), Active.get(), St.Loc, Renames, false, St.Ptr.get()); emitExprSafety(P, Val.get(), Active.get(), St.Loc, Renames, true, @@ -6511,6 +6524,8 @@ class PassivizerImpl { St.Loc); emitPassive(P, PassiveStmt::Assert, std::move(Allowed), Active.get(), St.Loc, ProofObligationKind::Frame); + emitTrace(PassiveTraceKind::HeapWrite, "store", Active.get(), St.Loc, + std::move(TraceValues)); std::string OldHeap = Renames[VHeapName]; std::string NewHeap = bump(VHeapName); Renames[VHeapName] = NewHeap; @@ -6805,8 +6820,6 @@ class PassivizerImpl { auto Pointer = cloneExpr(F.Ptr.get(), Ctx); std::vector TraceValues; TraceValues.push_back({"address", cloneVExpr(Pointer.get())}); - emitTrace(PassiveTraceKind::Deallocation, "delete", Active.get(), F.Loc, - std::move(TraceValues)); emitExprSafety(P, Pointer.get(), Active.get(), F.Loc, Renames, false, F.Ptr.get()); auto Provenance = pointerProvenance(Pointer.get()); @@ -6817,6 +6830,8 @@ class PassivizerImpl { std::make_unique(0, VType::makePtr(), F.Loc), F.Loc), Active.get(), F.Loc, ProofObligationKind::Deallocation); + emitTrace(PassiveTraceKind::Deallocation, "delete", Active.get(), F.Loc, + std::move(TraceValues)); break; } auto IsNull = makeEq( @@ -6852,6 +6867,8 @@ class PassivizerImpl { F.Loc); emitPassive(P, PassiveStmt::Assert, std::move(Owned), Active.get(), F.Loc, ProofObligationKind::Deallocation); + emitTrace(PassiveTraceKind::Deallocation, "delete", Active.get(), F.Loc, + std::move(TraceValues)); auto LivenessAfterDelete = std::make_unique( cloneVExpr(IsNull.get()), cloneVExpr(Live.get()), makeBoolLiteral(false, F.Loc), VType::makeBool(), F.Loc); @@ -6953,12 +6970,14 @@ class PassivizerImpl { break; } auto BoundReturn = cloneExpr(R.Value.get(), Ctx); + // A step of the path is one that happens: a check of the value comes + // before the return, which a failure of the check never reaches. + emitExprSafety(P, BoundReturn.get(), Active.get(), R.Loc, Renames, true, + R.Value.get()); std::vector TraceValues; TraceValues.push_back({"value", cloneVExpr(BoundReturn.get())}); emitTrace(PassiveTraceKind::Return, "return", Active.get(), R.Loc, std::move(TraceValues)); - emitExprSafety(P, BoundReturn.get(), Active.get(), R.Loc, Renames, true, - R.Value.get()); const VExpr *RetVal = R.Value.get(); while (RetVal && RetVal->K == VExpr::Cast) RetVal = static_cast(RetVal)->Inner.get(); diff --git a/clang/lib/Verify/Transform/Passivize.h b/clang/lib/Verify/Transform/Passivize.h index 939e90d99c..b1a2d361cf 100644 --- a/clang/lib/Verify/Transform/Passivize.h +++ b/clang/lib/Verify/Transform/Passivize.h @@ -74,6 +74,11 @@ struct PassiveModelVariable { VType Type; SourceLocation Loc; SourceLocation EndLoc; + bool Reference = false; + /// The path condition where this version is defined, null on every path. + /// Elsewhere it equals the version before it, so only where the condition + /// holds is it a value the variable takes. + std::shared_ptr Guard; }; /// What a construction for a program built past its function's limit diff --git a/clang/lib/Verify/Transform/SpecInline.cpp b/clang/lib/Verify/Transform/SpecInline.cpp index 10f95150a7..0145df3cd0 100644 --- a/clang/lib/Verify/Transform/SpecInline.cpp +++ b/clang/lib/Verify/Transform/SpecInline.cpp @@ -137,6 +137,7 @@ class SpecInlinerImpl { const VSpecCallExpr &Call) { if (Expr) { Expr->Loc = Call.Loc; + Expr->BeginLoc = Call.BeginLoc; Expr->EndLoc = Call.EndLoc; } return Expr; @@ -563,8 +564,10 @@ class SpecInlinerImpl { evalExpr(const VExpr *E, const std::map> &Env) { auto Result = evalExprImpl(E, Env); - if (Result && E) + if (Result && E) { + Result->BeginLoc = E->BeginLoc; Result->EndLoc = E->EndLoc; + } return Result; } diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt index e73e465d92..ba320d0c0b 100644 --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -49,6 +49,8 @@ if(LLVM_WITH_Z3 AND Z3_FOUND AND CMAKE_SYSTEM_NAME MATCHES "Linux") endif() endif() +get_property(CPPVERIFY_Z3_SHELL GLOBAL PROPERTY CPPVERIFY_Z3_SHELL) + configure_lit_site_cfg( ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py diff --git a/clang/test/Verify/compile_diagnostics.cpp b/clang/test/Verify/compile_diagnostics.cpp new file mode 100644 index 0000000000..4d710f2c87 --- /dev/null +++ b/clang/test/Verify/compile_diagnostics.cpp @@ -0,0 +1,81 @@ +// RUN: not %clang -std=c++17 -fverify-contracts -c -o %t.o %s 2>&1 | FileCheck %s --implicit-check-not=remark: +// RUN: not %clang -std=c++17 -fverify-contracts -Rcppverify -c -o %t.o %s 2>&1 | FileCheck %s --check-prefixes=CHECK,REMARK +// RUN: not %clang -std=c++17 -fverify-contracts -Wno-cppverify -c -o %t.o %s 2>&1 | FileCheck %s --check-prefix=QUIET +// RUN: not %clang -std=c++17 -fverify-contracts -Werror=cppverify -c -o %t.o %s 2>&1 | FileCheck %s --check-prefix=WERROR +// RUN: not %clang -std=c++17 -fverify-contracts -fdiagnostics-format=sarif -Wno-sarif-format-unstable -c -o %t.o %s 2>&1 | FileCheck %s --check-prefix=SARIF +// RUN: %clang -std=c++17 -fverify-contracts -fno-verify -c -o %t.o %s 2>&1 | count 0 +// +// A compilation with -fverify-contracts reports each result as a diagnostic +// of the compiler: a failure is an error at the failing check, its +// expression underlined, with its counterexample and path as notes; an +// unresolved result is an error; a warning belongs to -Wcppverify; a proof +// is a remark of -Rcppverify at its function, shown only when asked for. + +namespace cv = cppverify; + +int inc(int x) + cv::pre(x < 100) + cv::post(cv::result == x + 1) +{ + return x + 1; +} +// REMARK: compile_diagnostics.cpp:[[@LINE-6]]:5: remark: Verified: inc [backend=z3] [-Rcppverify] + +int clamp(int x) + cv::post(cv::result >= 0) +{ + if (x < 0) + return x + 1; + return x; +} +// CHECK: compile_diagnostics.cpp:[[@LINE-6]]:23: error: verification failed: clamp [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: [[@LINE-7]] | cv::post(cv::result >= 0) +// CHECK-NEXT: | ~~~~~~~~~~~^~~~ +// CHECK-NEXT: compile_diagnostics.cpp:[[@LINE-9]]:23: note: counterexample: x = {{-[0-9]+}}, result = {{-?[0-9]+}} +// CHECK-NEXT: compile_diagnostics.cpp:[[@LINE-8]]:3: note: takes the true branch +// CHECK: compile_diagnostics.cpp:[[@LINE-8]]:5: note: returns {{-?[0-9]+}} + +int never(int x) + cv::pre(x > 0 && x < 0) + cv::post(cv::result == 1) +{ + return 0; +} +// REMARK: compile_diagnostics.cpp:[[@LINE-6]]:5: remark: Verified: never [backend=z3] [vacuous] [-Rcppverify] +// CHECK: compile_diagnostics.cpp:[[@LINE-7]]:5: warning: never: the precondition is unsatisfiable, so every claim about it holds vacuously [-Wcppverify] +// WERROR: compile_diagnostics.cpp:[[@LINE-8]]:5: error: never: the precondition is unsatisfiable, so every claim about it holds vacuously [-Werror,-Wcppverify] + +int unmeasured(int n) + cv::pre(n >= 0) + cv::post(cv::result == n) +{ + int i = 0; + while (i < n) + cv::invariant(i <= n) + { + i = i + 1; + } + return i; +} +// CHECK: compile_diagnostics.cpp:[[@LINE-7]]:3: error: Unresolved: unmeasured [backend=z3] [reason=decreases.missing] +// CHECK: 1 warning and 2 errors generated. + +// QUIET-NOT: warning: +// QUIET: error: verification failed: clamp +// QUIET-NOT: warning: +// QUIET: error: Unresolved: unmeasured +// QUIET-NOT: warning: +// QUIET: 2 errors generated. + +// WERROR: 3 errors generated. + +// SARIF: "level": "error", +// SARIF: "ruleId": "err_cppverify_result" +// SARIF: "level": "note", +// SARIF: "text": "counterexample: x = {{-[0-9]+}}, result = {{-?[0-9]+}}" +// SARIF: "ruleId": "note_cppverify_result" +// SARIF: "level": "warning", +// SARIF: "text": "never: the precondition is unsatisfiable, so every claim about it holds vacuously" +// SARIF: "ruleId": "warn_cppverify_result" +// SARIF: "text": "Unresolved: unmeasured [backend=z3] [reason=decreases.missing] +// SARIF: "ruleId": "err_cppverify_result" diff --git a/clang/test/Verify/located_errors.cpp b/clang/test/Verify/located_errors.cpp index afc9b9cdb6..eac6da6a68 100644 --- a/clang/test/Verify/located_errors.cpp +++ b/clang/test/Verify/located_errors.cpp @@ -16,7 +16,7 @@ int pick(int x) // 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: 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"} +// JSON: {"function":"pick","function_source":{"column":5,"end_column":9,"end_line":[[#@LINE-8]],"file":"{{.*}}located_errors.cpp","line":[[#@LINE-8]]},"message":"pick: throw is unsupported: exceptions are not modeled","reason":"construct.unsupported","schema":"cppverify.diagnostic/2","severity":"error","source":{"column":14,"end_column":21,"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; } @@ -50,4 +50,4 @@ int down(int n) // CHECK: {{.*}}located_errors.cpp:[[@LINE-8]]:5: error: down: recursive executable functions require decreases, or cppverify::decreases(*) to allow divergence // CHECK-NEXT: {{^ +}}[[@LINE-9]] | int down(int n) // CHECK-NEXT: {{^ +}}| ^~~~ -// JSON: {"function":"down","message":"down: recursive executable functions require decreases, or cppverify::decreases(*) to allow divergence","reason":"construct.unsupported",{{.*}}"status":"unresolved"} +// JSON: {"function":"down",{{.*}}"message":"down: recursive executable functions require decreases, or cppverify::decreases(*) to allow divergence","reason":"construct.unsupported",{{.*}}"status":"unresolved"} diff --git a/clang/test/Verify/suite/Inputs/failure_notes_quantified.cpp b/clang/test/Verify/suite/Inputs/failure_notes_quantified.cpp new file mode 100644 index 0000000000..fc9987aaf9 --- /dev/null +++ b/clang/test/Verify/suite/Inputs/failure_notes_quantified.cpp @@ -0,0 +1,15 @@ +// A failure whose query cvc5 answers unknown, with a model the certifier +// checks, for failure_notes_cvc5.test. + +namespace cv = cppverify; + +int first(const int *a, int n) + cv::pre(cv::valid(a, n) && n > 2) + cv::pre(cv::forall(k, 0, n, a[k] >= 0)) + cv::post(cv::result > 0) +{ + int r = a[1]; + if (r > 5) + r = r - 1; + return r; +} diff --git a/clang/test/Verify/suite/Inputs/mutate_obligation_archive.py b/clang/test/Verify/suite/Inputs/mutate_obligation_archive.py index 64dfac1893..90820f341e 100644 --- a/clang/test/Verify/suite/Inputs/mutate_obligation_archive.py +++ b/clang/test/Verify/suite/Inputs/mutate_obligation_archive.py @@ -23,6 +23,10 @@ def __init__(self, data): # earlier schema has not. self.measure_spans = [] self.measures = [] + # What schema 4 adds: where each diagnostic variable is defined. + self.guard_spans = [] + # What schema 5 adds: the begin of each source range. + self.begin_spans = [] self.modules = [] def u8(self): @@ -75,6 +79,11 @@ def source(self): end_line = self.u32() end_column_offset = self.offset end_column = self.u32() + if self.version >= 5: + start = self.offset + self.u32() + self.u32() + self.begin_spans.append((start, self.offset)) result = { "line": line, "line_offset": line_offset, @@ -161,6 +170,11 @@ def module(self): self.string() self.diagnostic_sorts.append(self.sort(track=False)) self.source() + if self.version >= 4: + start = self.offset + if self.u8(): + self.expression(track=False) + self.guard_spans.append((start, self.offset)) for _ in range(self.u64()): self.trace_kind_offsets.append(self.offset) self.offset += 1 @@ -368,6 +382,10 @@ def invalidate_quantifier_binder_sort(self, node): "schema-v1", "schema-v1-kinds", "schema-v2", + "schema-v3", + "schema-v4", + "invalid-source-begin", + "invalid-guard-flag", "measure-free-variable", "truncated", ], @@ -382,6 +400,22 @@ def invalidate_quantifier_binder_sort(self, node): if args.mode == "invalid-kind": archive.data[archive.expression_kind_offsets[0]] = 255 +elif args.mode in ("schema-v3", "schema-v4"): + version = 3 if args.mode == "schema-v3" else 4 + for offset in archive.version_offsets: + struct.pack_into(" 3: archive.data[offset] = 1 - # Records before schema 3 have no measures or premises; a premise - # inside a measure goes with it. - spans = sorted(archive.measure_spans) + # Records before schema 3 have no measures or premises, and none before + # schema 4 a variable's guard; a premise inside a measure goes with it. + spans = sorted(archive.measure_spans + archive.guard_spans + + archive.begin_spans) outer = [ span for span in spans @@ -400,6 +435,19 @@ def invalidate_quantifier_binder_sort(self, node): ] for start, end in reversed(outer): del archive.data[start:end] +elif args.mode == "invalid-source-begin": + # A range whose begin lies after its anchor. + begin = next( + (start for start, _ in archive.begin_spans), None + ) + if begin is None: + raise ValueError("archive has no source range begin") + struct.pack_into(" -100 && x < 100) + cv::post(cv::result >= 0) +{ + if (x < 0) + return x + 1; + return x; +} + +int second(const int *a, int n) + cv::pre(cv::valid(a, n) && n > 2) + cv::pre(cv::forall(k, 0, n, a[k] >= 0)) + cv::post(cv::result >= 0) +{ + return a[1]; +} + +cppverify::spec int tri(int n) + cv::decreases(n) +{ + return n <= 0 ? 0 : tri(n - 1) + n; +} + +int closed(int n) + cv::pre(0 <= n && n <= 3) + cv::post(cv::result == tri(n)) +{ + return n; +} + +// Overloads, whose queries are kept apart. +int half(int x) + cv::pre(x >= 0) + cv::post(cv::result <= x) +{ + return x / 2; +} + +long half(long x) + cv::pre(x >= 0) + cv::post(cv::result <= x) +{ + return x / 2; +} diff --git a/clang/test/Verify/suite/Inputs/smt2_dumps_narrowed.cpp b/clang/test/Verify/suite/Inputs/smt2_dumps_narrowed.cpp new file mode 100644 index 0000000000..45c50d6ae1 --- /dev/null +++ b/clang/test/Verify/suite/Inputs/smt2_dumps_narrowed.cpp @@ -0,0 +1,12 @@ +// For smt2_dumps.test: a counterexample whose quantifier range is too wide +// to check is sought again, on the same solver, with a narrower range. + +namespace cv = cppverify; + +int first_positive_bitwise(const int *p, int n) + cv::pre(n >= 2000000 && n <= 3000000 && cv::valid(p, n)) + cv::pre(cv::forall(i, 0, n, (i ^ 1431655765) < 0 || p[i] >= 0)) + cv::post(cv::result > 0) +{ + return p[0]; +} diff --git a/clang/test/Verify/suite/Inputs/structured_output.cpp b/clang/test/Verify/suite/Inputs/structured_output.cpp new file mode 100644 index 0000000000..3759965f2e --- /dev/null +++ b/clang/test/Verify/suite/Inputs/structured_output.cpp @@ -0,0 +1,29 @@ +namespace cv = cppverify; + +int inc(int x) + cv::pre(x < 100) + cv::post(cv::result == x + 1) +{ + return x + 1; +} + +int clamp(int x) + cv::post(cv::result >= 0) +{ + if (x < 0) + return x + 1; + return x; +} + +int never(int x) + cv::pre(x > 0 && x < 0) + cv::post(cv::result == 1) +{ + return 0; +} + +int assigns(int x) { + if (x = 1) + return 0; + return 1; +} diff --git a/clang/test/Verify/suite/Inputs/structured_syntax_error.cpp b/clang/test/Verify/suite/Inputs/structured_syntax_error.cpp new file mode 100644 index 0000000000..51b76935ee --- /dev/null +++ b/clang/test/Verify/suite/Inputs/structured_syntax_error.cpp @@ -0,0 +1,5 @@ +int broken(int x) + cppverify::post(cppverify::result > 0) +{ + return x +} diff --git a/clang/test/Verify/suite/assertions.cpp b/clang/test/Verify/suite/assertions.cpp index 73278119ae..31c15d206c 100644 --- a/clang/test/Verify/suite/assertions.cpp +++ b/clang/test/Verify/suite/assertions.cpp @@ -39,7 +39,8 @@ int checked_wrong(int 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] +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: checked_wrong [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{-?[0-9]+}}{{( ->|,|$)}} // NDEBUG-DAG: Verified: checked_wrong [backend=z3] // MSVC's assert expands to a || whose right operand calls _wassert, which @@ -52,7 +53,7 @@ int msvc_assert(int x) (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] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: msvc_assert [unreachable]{{.*}}[reason=counterexample] // After assert(c), c holds. int positive_after(int x) @@ -62,8 +63,8 @@ int positive_after(int x) 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] +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: positive_after [unreachable]{{.*}}[reason=counterexample] +// NDEBUG-DAG: error: verification failed: positive_after [postcondition]{{.*}}[reason=counterexample] int positive_after_check(int x) cv::pre(x > 0) @@ -81,7 +82,8 @@ int aborts(int x) abort(); return x; } -// CHECK-DAG: error: verification failed: aborts [{{.*}}::unreachable@[[@LINE-3]]:5] (counterexample: x [ssa=x_0] [type=i32] = {{-[0-9]+}}{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:5: error: verification failed: aborts [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{-[0-9]+}}{{( ->|,|$)}} int aborts_guarded(int x) cv::pre(x >= 0) @@ -103,7 +105,8 @@ int exits(int code) 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] +// CHECK-DAG: :[[@LINE-3]]:5: error: verification failed: exits [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}code = {{[0-9]+}}{{( ->|,|$)}} int classify(int x) cv::pre(0 <= x && x < 3) @@ -131,7 +134,8 @@ int classify_wrong(int x) 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] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: classify_wrong [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 3{{( ->|,|$)}} // An assumption is proved where it stands, then holds. int assumed(int x) @@ -150,7 +154,8 @@ int assumed_wrong(int x) [[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] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: assumed_wrong [assertion] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{-?[0-9]+}}{{( ->|,|$)}} // A discarded value is computed, with its checks; the operands of a // discarded ||, &&, or ?: run on their paths. @@ -161,7 +166,7 @@ int discarded(int x) x + 1; return 0; } -// CHECK-DAG: error: verification failed: discarded [{{.*}}::overflow@[[@LINE-3]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: discarded [overflow]{{.*}}[reason=counterexample] int or_else(int x) cv::post(cv::result > 0) @@ -169,7 +174,7 @@ int or_else(int x) x > 0 || (abort(), 0); return x; } -// CHECK-DAG: error: verification failed: or_else [{{.*}}::unreachable@[[@LINE-3]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: or_else [unreachable]{{.*}}[reason=counterexample] int or_else_guarded(int x) cv::pre(x > 0) @@ -205,7 +210,7 @@ int use_half_wrong(int x) { return half(x); } -// CHECK-DAG: error: verification failed: use_half_wrong [{{.*}}::unreachable@[[@LINE-2]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: use_half_wrong [unreachable]{{.*}}[reason=counterexample] int use_quarter_wrong(int x) cv::pre(-1000 < x && x < 1000 && x % 4 == 2) @@ -213,7 +218,7 @@ int use_quarter_wrong(int x) { return quarter(x); } -// CHECK-DAG: error: verification failed: use_quarter_wrong [{{.*}}::unreachable@[[@LINE-2]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: use_quarter_wrong [unreachable]{{.*}}[reason=counterexample] // NDEBUG-DAG: Verified: use_quarter_wrong [backend=z3] // The requirement holds on the helper's path to the call that does not @@ -244,7 +249,8 @@ int use_div_wrong(int a, int b) { 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] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: use_div_wrong [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}b = 0{{( ->|,|$)}} int use_positive(int x) cv::pre(x > 0) @@ -277,7 +283,7 @@ uint64_t alignTo_zero(uint64_t Value) { return alignTo(Value, 0); } -// CHECK-DAG: error: verification failed: alignTo_zero [{{.*}}::precondition@[[@LINE-2]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: alignTo_zero [precondition]{{.*}}[reason=counterexample] inline constexpr uint64_t maxUIntN(uint64_t N) cv::pre(N <= 64) @@ -319,12 +325,13 @@ int64_t SignExtend64_wide(uint64_t X, unsigned B) 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] +// CHECK-DAG: :[[@LINE-5]]:3: error: verification failed: SignExtend64_wide [unreachable] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}B = {{[0-9]+}}{{( ->|,|$)}} +// 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@ +// BMC: error: verification failed: checked_wrong [unreachable] +// BMC: error: verification failed: aborts [unreachable] // VCR-LABEL: fn aborts // VCR: {{^ +}}if{{$}} diff --git a/clang/test/Verify/suite/backend_model_names.cpp b/clang/test/Verify/suite/backend_model_names.cpp index fc90bb19bd..4481c1cce5 100644 --- a/clang/test/Verify/suite/backend_model_names.cpp +++ b/clang/test/Verify/suite/backend_model_names.cpp @@ -9,10 +9,10 @@ unsigned renamed_parameter(unsigned implementation_name) { } // CHECK: error: verification failed: renamed_parameter -// CHECK-SAME: implementation_name [ssa=interface_name_0] [type=u32] // CHECK-SAME: [reason=counterexample] +// CHECK: note: counterexample: implementation_name = 0, result = 1 // JSON: "name":"implementation_name" // JSON-SAME: "sort":"u32" // JSON-SAME: "ssa_name":"interface_name_0" -// JSON-SAME: "schema":"cppverify.diagnostic/1" +// JSON-SAME: "schema":"cppverify.diagnostic/2" diff --git a/clang/test/Verify/suite/backend_model_truth.cpp b/clang/test/Verify/suite/backend_model_truth.cpp index cc96d9393d..a4a6480704 100644 --- a/clang/test/Verify/suite/backend_model_truth.cpp +++ b/clang/test/Verify/suite/backend_model_truth.cpp @@ -11,21 +11,21 @@ int underdetermined_model(int value, bool take_value) } // CHECK: error: verification failed: underdetermined_model -// CHECK-SAME: value [ssa=value_0] [type=i32] = // CHECK-SAME: [backend=z3] [reason=counterexample] +// CHECK: note: counterexample: value = , take_value = false, result = 0 // JSON: "model":[ // JSON-SAME: {"name":"value","sort":"i32" // JSON-SAME: "ssa_name":"value_0","value":null} // JSON-SAME: "obligation":{"id":"{{.*}}::postcondition@{{[0-9]+}}:{{[0-9]+}}","kind":"postcondition" -// JSON-SAME: "source":{"column":19,"end_column":61,"end_line":8 +// JSON-SAME: "source":{"column":19,"end_column":66,"end_line":8 // JSON-SAME: "reason":"counterexample" -// JSON-SAME: "schema":"cppverify.diagnostic/1" +// JSON-SAME: "schema":"cppverify.diagnostic/2" // JSON-SAME: "status":"failed" // ARCHIVE: "model":[ // ARCHIVE-SAME: {"name":"value","sort":"i32" // ARCHIVE-SAME: "ssa_name":"value_0","value":null} // ARCHIVE-SAME: "semantic_hash":"sha256:{{[0-9a-f]+}}" -// ARCHIVE-SAME: "source":{"column":19,"end_column":61,"end_line":8 +// ARCHIVE-SAME: "source":{"column":19,"end_column":66,"end_line":8 // ARCHIVE-SAME: "status":"failed" diff --git a/clang/test/Verify/suite/backend_model_values.cpp b/clang/test/Verify/suite/backend_model_values.cpp index 2406c80e26..28bd5a9ea7 100644 --- a/clang/test/Verify/suite/backend_model_values.cpp +++ b/clang/test/Verify/suite/backend_model_values.cpp @@ -13,6 +13,6 @@ unsigned maximum_result() } // CHECK: error: verification failed: negative_result -// CHECK-SAME: result [ssa=__result_1] [type=i32] = -1 +// CHECK: note: counterexample: result = -1 // CHECK: error: verification failed: maximum_result -// CHECK-SAME: result [ssa=__result_1] [type=u32] = 4294967295 +// CHECK: note: counterexample: result = 4294967295 diff --git a/clang/test/Verify/suite/backend_result_contract.cpp b/clang/test/Verify/suite/backend_result_contract.cpp index 80cf21c00a..e2b3408ad6 100644 --- a/clang/test/Verify/suite/backend_result_contract.cpp +++ b/clang/test/Verify/suite/backend_result_contract.cpp @@ -20,11 +20,11 @@ int invalid_result() } // Z3-DAG: Verified: valid_result [backend=z3] -// Z3: backend_result_contract.cpp:{{[0-9]+}}:{{[0-9]+}}: error: verification failed: invalid_result [{{.*}}::postcondition@{{[0-9]+}}:{{[0-9]+}}] +// Z3: backend_result_contract.cpp:{{[0-9]+}}:{{[0-9]+}}: error: verification failed: invalid_result [postcondition] // Z3-SAME: [backend=z3] [reason=counterexample] // BMC-DAG: Verified: valid_result [backend=bmc, bound=0] -// BMC: backend_result_contract.cpp:{{[0-9]+}}:{{[0-9]+}}: error: verification failed: invalid_result [{{.*}}::postcondition@{{[0-9]+}}:{{[0-9]+}}] +// BMC: backend_result_contract.cpp:{{[0-9]+}}:{{[0-9]+}}: error: verification failed: invalid_result [postcondition] // BMC-SAME: [backend=bmc, bound=0] [reason=counterexample] // LOWER-DAG: Lowered: valid_result diff --git a/clang/test/Verify/suite/backend_stable_id.test b/clang/test/Verify/suite/backend_stable_id.test index 9765d12491..b592ee3a1e 100644 --- a/clang/test/Verify/suite/backend_stable_id.test +++ b/clang/test/Verify/suite/backend_stable_id.test @@ -6,12 +6,12 @@ # RUN: not %cpp-verify --diagnostics-format=json %S/Inputs/backend_inlined_source.cpp -- 2>&1 | FileCheck %s --check-prefix=INLINED # RUN: not %cpp-verify %S/Inputs/backend_stable_id_suffix.cpp -- 2>&1 | FileCheck %s --check-prefix=SUFFIX -# CHECK: verification failed: stable_identity [{{.*}}::postcondition@2:37] +# CHECK: verification failed: stable_identity [postcondition] # INLINED: "function":"inlined_source" # INLINED-SAME: "id":"{{.*}}::postcondition@4:19" -# INLINED-SAME: "end_column":37 +# INLINED-SAME: "end_column":38 # INLINED-SAME: "line":4 # The suffix separates obligations of one kind at one anchor: here both # preconditions of the callee are checked at the call. -# SUFFIX: verification failed: two_preconditions [{{.*}}::precondition@10:3#2] +# SUFFIX: verification failed: two_preconditions [precondition] diff --git a/clang/test/Verify/suite/behaviors.cpp b/clang/test/Verify/suite/behaviors.cpp index 7f97694738..aec988869d 100644 --- a/clang/test/Verify/suite/behaviors.cpp +++ b/clang/test/Verify/suite/behaviors.cpp @@ -32,7 +32,7 @@ int calls_with_minimum(int y) { return abs_value(y); } -// CHECK-DAG: error: verification failed: calls_with_minimum [{{.*}}::precondition@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: calls_with_minimum [precondition] int sign(int x) cppverify::behavior(positive, x > 0) @@ -43,7 +43,7 @@ int sign(int x) { return x > 0 ? 1 : (x < 0 ? -1 : 0); } -// CHECK-DAG: error: verification failed: sign [{{.*}}::assertion@[[@LINE-4]]:3] +// CHECK-DAG: :[[@LINE-4]]:3: error: verification failed: sign [assertion] int clamp(int x) cppverify::behavior(low, x <= 0) @@ -54,7 +54,7 @@ int clamp(int x) { return x; } -// CHECK-DAG: error: verification failed: clamp [{{.*}}::assertion@[[@LINE-4]]:3] +// CHECK-DAG: :[[@LINE-4]]:3: error: verification failed: clamp [assertion] int small_zero(int x) cppverify::behavior(small, x < 10) @@ -62,7 +62,7 @@ int small_zero(int x) { return x < 5 ? 0 : 1; } -// CHECK-DAG: error: verification failed: small_zero [{{.*}}::postcondition@[[@LINE-4]]:21] +// CHECK-DAG: :[[@LINE-4]]:21: error: verification failed: small_zero [postcondition] // A behavior's assumption reads memory at entry. void bump(int *p) diff --git a/clang/test/Verify/suite/bmc_incremental.cpp b/clang/test/Verify/suite/bmc_incremental.cpp index e10f9e1092..d1c68b5026 100644 --- a/clang/test/Verify/suite/bmc_incremental.cpp +++ b/clang/test/Verify/suite/bmc_incremental.cpp @@ -57,10 +57,11 @@ int open_frontier(int limit) // TEXT: Verified: completes_at_two [backend=bmc, bound=2] // TEXT-SAME: [bounds=0,1,2] [reused-queries={{[1-9][0-9]*}}] // TEXT: error: verification failed: bug_on_second_iteration -// TEXT-SAME: trace: loop.iteration 1 -// TEXT-SAME: -> loop.iteration 2 // TEXT-SAME: [backend=bmc, bound=2] // TEXT-SAME: [bounds=0,1,2] [reused-queries={{[1-9][0-9]*}}] +// TEXT: note: counterexample: index = 0 -> 1 +// TEXT: note: runs iteration 1 of the loop +// TEXT: note: runs iteration 2 of the loop // TEXT: BoundedSafe: open_frontier [backend=bmc, bound=4] // TEXT-SAME: [reason=bmc.incomplete-bound] // TEXT-SAME: [bounds=0,1,2,3,4] [reused-queries={{[1-9][0-9]*}}] @@ -68,7 +69,7 @@ int open_frontier(int limit) // JSON: "bound":0,"explored_bounds":[0],"function":"loop_free" // JSON: "bound":2,"explored_bounds":[0,1,2],"function":"completes_at_two" // JSON-SAME: "reused_queries":{{[1-9][0-9]*}} -// JSON: "bound":2,"explored_bounds":[0,1,2],"function":"bug_on_second_iteration" +// JSON: "bound":2,{{.*}}"explored_bounds":[0,1,2],"function":"bug_on_second_iteration" // JSON-SAME: "message":"iteration 1" // JSON-SAME: "message":"iteration 2" // JSON: "bound":4,"explored_bounds":[0,1,2,3,4],"function":"open_frontier" diff --git a/clang/test/Verify/suite/bounds_single_object.cpp b/clang/test/Verify/suite/bounds_single_object.cpp index 4c2b1e0769..ff86d9880a 100644 --- a/clang/test/Verify/suite/bounds_single_object.cpp +++ b/clang/test/Verify/suite/bounds_single_object.cpp @@ -4,7 +4,7 @@ // Without a valid(p, n) declaration a pointer addresses a single object, so // p[i] is in bounds only at i == 0. --no-check-ub turns memory checking off. int get(int* p, int i) cppverify::pre(p != nullptr && i >= 0) cppverify::post(cppverify::result == p[i]) { return p[i]; } -// CHECKED: error: verification failed: get [{{.*}}::bounds@ +// CHECKED: error: verification failed: get [bounds] // UNCHECKED: Verified: get int first(int* p) cppverify::pre(p != nullptr) cppverify::post(cppverify::result == p[0]) { return p[0]; } diff --git a/clang/test/Verify/suite/calls_in_expressions.cpp b/clang/test/Verify/suite/calls_in_expressions.cpp index 223ac0b3ef..d3f40e6453 100644 --- a/clang/test/Verify/suite/calls_in_expressions.cpp +++ b/clang/test/Verify/suite/calls_in_expressions.cpp @@ -153,7 +153,7 @@ int unguarded(int x) return 1; return 0; } -// CHECK: error: verification failed: unguarded {{.*}}precondition@[[@LINE-4]]:7 +// CHECK: :[[@LINE-4]]:7: error: verification failed: unguarded [precondition] int either(int x) cv::pre(-2000 <= x && x <= 2000) @@ -180,7 +180,7 @@ int choose_wrong(bool c, int x) int y = c ? inc(x) : sq(x); return y; } -// CHECK: error: verification failed: choose_wrong {{.*}}precondition@[[@LINE-3]]:15 +// CHECK: :[[@LINE-3]]:15: error: verification failed: choose_wrong [precondition] int count_up(int n) cv::pre(0 <= n && n < 100) @@ -250,7 +250,7 @@ int count_past(int n) } return i; } -// CHECK: error: verification failed: count_past {{.*}}precondition@[[@LINE-8]]:10 +// CHECK: :[[@LINE-8]]:10: error: verification failed: count_past [precondition] // A call's result is its postcondition's, not any value. int wrong(int x) @@ -395,7 +395,8 @@ int classify_wrong(int x) return 1; return 0; } -// CHECK: error: verification failed: classify_wrong {{.*}}postcondition{{.*}}x [ssa=x_0] [type=i32] = 5 +// CHECK: error: verification failed: classify_wrong {{.*}}postcondition +// CHECK: note: counterexample: {{.*}}x = 5{{( ->|,|$)}} int *first_of(int *a) cv::pre(cv::valid(a, 2)) diff --git a/clang/test/Verify/suite/certified_counterexamples.cpp b/clang/test/Verify/suite/certified_counterexamples.cpp index ad216a1f98..3ea7c56b99 100644 --- a/clang/test/Verify/suite/certified_counterexamples.cpp +++ b/clang/test/Verify/suite/certified_counterexamples.cpp @@ -95,10 +95,13 @@ int masked_wrong(int x) // CHECK-DAG: Unresolved: hidden_recursive [backend={{z3|bmc, bound=0}}] [reason=spec.hidden] // Z3-DAG: Verified: bounded_closed_form [backend=z3] // BMC-DAG: Verified: bounded_closed_form [backend=bmc -// CHECK-DAG: verification failed: bounded_closed_form_wrong {{.*}}n [ssa=n_0] [type=i32] = 33; +// CHECK-DAG: verification failed: bounded_closed_form_wrong +// CHECK-DAG: note: counterexample: {{.*}}n = 33{{( ->|,|$)}} // CHECK-DAG: Unresolved: scaled_true [backend={{z3|bmc, bound=0}}] [reason=spec.hidden] -// CHECK-DAG: verification failed: scaled_wrong {{.*}}x [ssa=x_0] [type=u32] = 3000000000; -// CHECK-DAG: verification failed: masked_wrong {{.*}}x [ssa=x_0] [type=i32] = 700; +// CHECK-DAG: verification failed: scaled_wrong +// CHECK-DAG: note: counterexample: {{.*}}x = 3000000000{{( ->|,|$)}} +// CHECK-DAG: verification failed: masked_wrong +// CHECK-DAG: note: counterexample: {{.*}}x = 700{{( ->|,|$)}} // JSON-DAG: "function":"hidden_true"{{.*}}"reason":"spec.hidden" // JSON-DAG: "function":"hidden_false"{{.*}}"reason":"counterexample" diff --git a/clang/test/Verify/suite/certified_counterexamples_cvc5.test b/clang/test/Verify/suite/certified_counterexamples_cvc5.test index d0c4ed88c8..5cb587d3f9 100644 --- a/clang/test/Verify/suite/certified_counterexamples_cvc5.test +++ b/clang/test/Verify/suite/certified_counterexamples_cvc5.test @@ -16,7 +16,10 @@ # CVC5-DAG: verification failed: masked_wrong {{.*}}[backend=cvc5] # PORTFOLIO-DAG: Unresolved: hidden_true [backend=portfolio] [reason=spec.hidden] -# PORTFOLIO-DAG: verification failed: hidden_false {{.*}}x [ssa=x_0] +# PORTFOLIO-DAG: verification failed: hidden_false +# PORTFOLIO-DAG: note: counterexample: {{.*}}x = # PORTFOLIO-DAG: Verified: bounded_closed_form [backend=portfolio] -# PORTFOLIO-DAG: verification failed: scaled_wrong {{.*}}x [ssa=x_0] [type=u32] = 3000000000; -# PORTFOLIO-DAG: verification failed: masked_wrong {{.*}}x [ssa=x_0] [type=i32] = 700; +# PORTFOLIO-DAG: verification failed: scaled_wrong +# PORTFOLIO-DAG: note: counterexample: {{.*}}x = 3000000000{{( ->|,|$)}} +# PORTFOLIO-DAG: verification failed: masked_wrong +# PORTFOLIO-DAG: note: counterexample: {{.*}}x = 700{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/certified_failure_attribution.cpp b/clang/test/Verify/suite/certified_failure_attribution.cpp index 25a0377b2b..463842f6f0 100644 --- a/clang/test/Verify/suite/certified_failure_attribution.cpp +++ b/clang/test/Verify/suite/certified_failure_attribution.cpp @@ -35,5 +35,5 @@ void break_heap_wrong(int *p) i = i + 1; } } -// CHECK: error: verification failed: break_heap_wrong [{{.*}}::postcondition@23:24] {{.*}}[reason=counterexample] +// CHECK: :23:24: error: verification failed: break_heap_wrong [postcondition] {{.*}}[reason=counterexample] // CHECK-NOT: within_seconds diff --git a/clang/test/Verify/suite/certified_quantifier_ranges.cpp b/clang/test/Verify/suite/certified_quantifier_ranges.cpp index 737522d962..6092e4a818 100644 --- a/clang/test/Verify/suite/certified_quantifier_ranges.cpp +++ b/clang/test/Verify/suite/certified_quantifier_ranges.cpp @@ -75,12 +75,13 @@ int first_positive_bitwise(const int *p, int n) return p[0]; } -// CHECK-DAG: verification failed: first_positive_squared_wide [{{.*}}::postcondition@ -// CHECK-DAG: verification failed: first_positive [{{.*}}::postcondition@ -// CHECK-DAG: verification failed: first_positive_long [{{.*}}::postcondition@{{.*}}n [ssa=n_0] [type=i32] = 2000000 -// CHECK-DAG: verification failed: writes_first [{{.*}}::postcondition@ -// CHECK-DAG: verification failed: first_positive_indexed [{{.*}}::postcondition@ -// CHECK-DAG: verification failed: first_positive_squared [{{.*}}::postcondition@ +// CHECK-DAG: verification failed: first_positive_squared_wide [postcondition] +// CHECK-DAG: verification failed: first_positive [postcondition] +// CHECK-DAG: verification failed: first_positive_long [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}n = 2000000{{( ->|,|$)}} +// CHECK-DAG: verification failed: writes_first [postcondition] +// CHECK-DAG: verification failed: first_positive_indexed [postcondition] +// CHECK-DAG: verification failed: first_positive_squared [postcondition] // CHECK-DAG: Unresolved: first_positive_bitwise [backend=z3] [reason=counterexample.unchecked] // CHECK-DAG: a quantifier range of {{[0-9]+}} values is too wide to expand diff --git a/clang/test/Verify/suite/choose.cpp b/clang/test/Verify/suite/choose.cpp index a20dcceed6..423331fdf2 100644 --- a/clang/test/Verify/suite/choose.cpp +++ b/clang/test/Verify/suite/choose.cpp @@ -44,13 +44,13 @@ void claims_one() cppverify::post(pick() == 1) { } -// CHECK-DAG: error: verification failed: claims_one [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: claims_one [postcondition]{{.*}}[reason=counterexample] void no_witness() { cppverify::check(cppverify::choose(x, x * x == 2) == 0); } -// CHECK-DAG: error: verification failed: no_witness [{{.*}}::assertion@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: no_witness [assertion]{{.*}}[reason=counterexample] // In ghost code a choice is stored like any mathematical value: the store // must fit, which the range guarantees when a witness exists. diff --git a/clang/test/Verify/suite/choose_axioms.cpp b/clang/test/Verify/suite/choose_axioms.cpp index 01b001304e..1bc600f893 100644 --- a/clang/test/Verify/suite/choose_axioms.cpp +++ b/clang/test/Verify/suite/choose_axioms.cpp @@ -40,7 +40,7 @@ cppverify::proof void odd_chosen_at_4() cppverify::post(odd_chosen(4)) { } -// CHECK-DAG: error: verification failed: odd_chosen_at_4 [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: odd_chosen_at_4 [postcondition]{{.*}}[reason=counterexample] // In the postcondition of a hidden spec. cppverify::spec int parity(int n) @@ -63,7 +63,7 @@ cppverify::proof void parity_of_7_wrong(int x) { cppverify::hide(parity); } -// CHECK-DAG: error: verification failed: parity_of_7_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: parity_of_7_wrong [postcondition]{{.*}}[reason=counterexample] // A choose that reads memory is read where its application is. cppverify::spec int low_bit(const int *a) @@ -90,7 +90,7 @@ void set_one_wrong(int *a) cppverify::hide(low_bit); a[0] = 1; } -// CHECK-DAG: error: verification failed: set_one_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: set_one_wrong [postcondition]{{.*}}[reason=counterexample] cppverify::spec int low_bit_rec(const int *a, int n) cppverify::decreases(n) @@ -130,7 +130,7 @@ int max_index_wrong(const int *a, int n, int m) { return m; } -// CHECK-DAG: error: verification failed: max_index_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: max_index_wrong [postcondition]{{.*}}[reason=counterexample] // In the induction hypothesis of a recursive spec. cppverify::spec int bit(int n) @@ -191,4 +191,4 @@ cppverify::proof void parity_sum_5_wrong(int x) cppverify::post(parity_sum(x) == 2) { } -// CHECK-DAG: error: verification failed: parity_sum_5_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: parity_sum_5_wrong [postcondition]{{.*}}[reason=counterexample] diff --git a/clang/test/Verify/suite/choose_identity.cpp b/clang/test/Verify/suite/choose_identity.cpp index 46efc598de..ed0d329c7d 100644 --- a/clang/test/Verify/suite/choose_identity.cpp +++ b/clang/test/Verify/suite/choose_identity.cpp @@ -45,13 +45,15 @@ cv::proof void other_condition(int n) cv::post(cv::choose(k, k > n) == cv::choose(k, k >= n)) { } -// CHECK-DAG: error: verification failed: other_condition [{{.*}}] (counterexample: +// CHECK-DAG: error: verification failed: other_condition [postcondition] +// CHECK-DAG: note: counterexample: cv::proof void other_value(int n, int m) cv::post(cv::choose(k, k > n) == cv::choose(k, k > m)) { } -// CHECK-DAG: error: verification failed: other_value [{{.*}}] (counterexample: +// CHECK-DAG: error: verification failed: other_value [postcondition] +// CHECK-DAG: note: counterexample: cv::proof void other_range() cv::post(cv::choose(k, 0, 10, k >= 0) == cv::choose(k, 0, 11, k >= 0)) @@ -98,7 +100,8 @@ void write_inside(int *a, int n, int x) { a[0] = x == 0 ? 1 : 0; } -// CHECK-DAG: error: verification failed: write_inside [{{.*}}] (counterexample: +// CHECK-DAG: choose_identity.cpp:[[@LINE-5]]:{{[0-9]+}}: error: verification failed: write_inside [postcondition]{{.*}}[reason=counterexample] +// CHECK-DAG: choose_identity.cpp:[[@LINE-6]]:{{[0-9]+}}: note: counterexample: a = // Which cells c[a[k]] reads depends on memory too: each read compares what it // reads in each memory, its address included, so the write to b keeps it. diff --git a/clang/test/Verify/suite/choose_reads.cpp b/clang/test/Verify/suite/choose_reads.cpp index 86f72c1d4d..b80508c344 100644 --- a/clang/test/Verify/suite/choose_reads.cpp +++ b/clang/test/Verify/suite/choose_reads.cpp @@ -61,4 +61,4 @@ void set_inside(int *a, int n, int x) { a[0] = x == 0 ? 1 : 0; } -// CHECK-DAG: error: verification failed: set_inside [{{.*}}::postcondition@[[@LINE-4]]:30] +// CHECK-DAG: :[[@LINE-4]]:30: error: verification failed: set_inside [postcondition] diff --git a/clang/test/Verify/suite/choose_recursion.cpp b/clang/test/Verify/suite/choose_recursion.cpp index bf5eb368ca..31eef0e5b2 100644 --- a/clang/test/Verify/suite/choose_recursion.cpp +++ b/clang/test/Verify/suite/choose_recursion.cpp @@ -25,7 +25,8 @@ cppverify::spec int stuck(int n) { return n <= 0 ? 0 : stuck(n - 1) + cppverify::choose(k, n == 1 ? k == stuck(n) + 1 : k == 0); } -// CHECK-DAG: error: spec decreases failed: stuck [backend=z3] [reason=counterexample] (n [type=math-i32] = 1) +// CHECK-DAG: error: spec decreases failed: stuck [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = 1{{( ->|,|$)}} // The same with a postcondition, which is proved with termination. cppverify::spec int stuck_post(int n) @@ -34,7 +35,8 @@ cppverify::spec int stuck_post(int n) { return n <= 0 ? 0 : cppverify::choose(k, n == 1 ? k == stuck_post(n) + 1 : k == 0); } -// CHECK-DAG: error: spec decreases and post failed: stuck_post [backend=z3] [reason=counterexample] (n [type=math-i32] = 1) +// CHECK-DAG: error: spec decreases and post failed: stuck_post [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = 1{{( ->|,|$)}} // The axiom of a choose holds where its applications lower the measure, // which places the choice in [0, n). @@ -51,7 +53,8 @@ cppverify::spec int unbounded(int n) { return n <= 0 ? 0 : cppverify::choose(k, unbounded(k) == 0); } -// CHECK-DAG: error: spec decreases failed: unbounded [backend=z3] [reason=counterexample] (n [type=math-i32] = {{[1-9][0-9]*}}) +// CHECK-DAG: error: spec decreases failed: unbounded [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[1-9][0-9]*}}{{( ->|,|$)}} int forty_two(int x) cppverify::post(cppverify::result == 42) diff --git a/clang/test/Verify/suite/class_invariants.cpp b/clang/test/Verify/suite/class_invariants.cpp index 293be0b1a2..5d1be26497 100644 --- a/clang/test/Verify/suite/class_invariants.cpp +++ b/clang/test/Verify/suite/class_invariants.cpp @@ -103,8 +103,8 @@ class Ring { // CHECK-DAG: Verified: Ring::size [backend=z3] // CHECK-DAG: Verified: Ring::rotate [backend=z3] // CHECK-DAG: Verified: Ring::set_head [backend=z3] -// CHECK-DAG: error: verification failed: Ring::skip [{{.*}}::type-invariant@ -// CHECK-DAG: error: verification failed: Ring::peek_broken [{{.*}}::type-invariant@ +// CHECK-DAG: error: verification failed: Ring::skip [type-invariant] +// CHECK-DAG: error: verification failed: Ring::peek_broken [type-invariant] // A constructor that does not establish the invariant. class Window { @@ -117,7 +117,7 @@ class Window { cppverify::type_invariant(0 <= first && first < 100); }; -// CHECK-DAG: error: verification failed: Window::Window [{{.*}}::type-invariant@ +// CHECK-DAG: error: verification failed: Window::Window [type-invariant] class Bounded { public: @@ -170,7 +170,7 @@ int ring_value() int v = r.pop(); return v; } -// CHECK-DAG: error: verification failed: ring_value [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: ring_value [postcondition] int broken_by_caller() cppverify::post(true) @@ -179,7 +179,7 @@ int broken_by_caller() b.first = 500; return b.get(); } -// CHECK-DAG: error: verification failed: broken_by_caller [{{.*}}::type-invariant@[[@LINE-2]] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: broken_by_caller [type-invariant] // An extent in a class invariant: on entry `this->data` points to `len` // elements, as a pointer field's extent in a precondition does. At exit the @@ -234,4 +234,4 @@ int grown(const int *p, int n) int x = v.first(); return x; } -// CHECK-DAG: error: verification failed: grown [{{.*}}::bounds@[[@LINE-3]] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: grown [bounds] diff --git a/clang/test/Verify/suite/class_invariants_in_loops.cpp b/clang/test/Verify/suite/class_invariants_in_loops.cpp new file mode 100644 index 0000000000..fa7026e9de --- /dev/null +++ b/clang/test/Verify/suite/class_invariants_in_loops.cpp @@ -0,0 +1,181 @@ +// RUN: not %cpp-verify --jobs=1 %s -- 2>&1 | FileCheck %s +// RUN: not %cpp-verify --jobs=1 %s -- -DGHOST_ADDRESS 2>&1 | FileCheck %s --check-prefix=GHOST +// +// A loop outside a class keeps the class invariants of the objects it +// reaches: the local objects alive at it and those its function's reference +// and pointer parameters address. Only the class's members change what an +// invariant states, and a public one keeps it; each invariant is still +// checked at the loop's entry and kept by its iterations, like the user's. +// A member function's own loops get none of its class's: it may break the +// invariant between calls. +// +// No proof here holds only because no execution reaches its end. +// CHECK-NOT: vacuous + +namespace cv = cppverify; + +class Meter { +public: + Meter() cv::post(reading(this) == 0) : total(0), last(0) {} + + void add(int d) + cv::pre(0 <= d && d <= 10 && reading(this) <= 1000) + cv::post(reading(this) == cv::old(reading(this)) + d) + { + last = d; + total = total + d; + } + + // Counts the reading up again from 0: while its loop runs, last may + // exceed total, and only the end restores the invariant. + void recount(int n) + cv::pre(0 <= n && n <= 10) + cv::post(reading(this) == n) + { + total = 0; + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n && total == i && last == cv::old(last)) + cv::decreases(n - i) + { + total = total + 1; + i = i + 1; + } + last = 0; + } + + // Empties another meter: while its loop runs, that meter's last may + // exceed its total, since its loop is the class's own code too. + void empty(Meter &other) + cv::pre(reading(&other) <= 100) + cv::post(reading(&other) == 0) + cv::modifies(other) + { + while (other.total > 0) + cv::invariant(0 <= other.total && other.total <= 100) + cv::decreases(other.total) + { + other.total = other.total - 1; + } + other.last = 0; + } + + friend cv::spec int reading(const Meter *m); + +private: + int total; + int last; + +public: + cv::type_invariant(0 <= last && last <= total); +}; + +cv::spec int reading(const Meter *m) { return m->total; } + +// The loop keeps the meter's invariant without saying so. +int count_to(int n) + cv::pre(0 <= n && n <= 100) + cv::post(cv::result == n) +{ + Meter m; + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n && reading(&m) == i) + cv::decreases(n - i) + { + m.add(1); + i = i + 1; + } + cv::check(reading(&m) == n); + return i; +} + +// So does a loop over an object a reference addresses. +void fill(Meter &m, int n) + cv::pre(0 <= n && n <= 100 && reading(&m) <= 100) + cv::post(reading(&m) == cv::old(reading(&m)) + n) + cv::modifies(m) +{ + for (int i = 0; i < n; i = i + 1) + cv::invariant(0 <= i && i <= n && reading(&m) == cv::old(reading(&m)) + i) + cv::decreases(n - i) + { + m.add(1); + } +} + +// And a do loop over one a pointer addresses, which may be null. +void drain(Meter *m, int n) + cv::pre(m != nullptr && 1 <= n && n <= 100 && reading(m) <= 100) + cv::post(reading(m) == cv::old(reading(m)) + n) + cv::modifies(*m) +{ + int i = 0; + do { + m->add(1); + i = i + 1; + } while (i < n) + cv::invariant(1 <= i && i <= n && reading(m) == cv::old(reading(m)) + i) + cv::decreases(n - i); +} +// A pointer that may be null addresses no object whose invariant holds. +int idle(const Meter *m, int n) + cv::pre(0 <= n && n <= 10) + cv::post(cv::result == n) +{ + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n) + cv::decreases(n - i) + { + i = i + 1; + } + return i; +} + +#ifdef GHOST_ADDRESS +// Ghost code could keep the address of a local: it is still an error. +int kept() + cv::post(cv::result == 0) +{ + Meter m; + cv::ghost { const Meter *q = &m; } + return 0; +} +// GHOST: class_invariants_in_loops.cpp:[[@LINE-3]]:32: error: kept: raw address-of is unsupported until lexical lifetime and escape effects are modeled +#endif + +// CHECK-DAG: Verified: idle [backend=z3] +// CHECK-DAG: Verified: Meter::recount [backend=z3] +// CHECK-DAG: Verified: Meter::empty [backend=z3] +// CHECK-DAG: Verified: count_to [backend=z3] +// CHECK-DAG: Verified: fill [backend=z3] +// CHECK-DAG: Verified: drain [backend=z3] + +// A friend may break an invariant, and a loop that does fails it: the +// invariant is checked, not assumed. +class Gauge { + int lo; + int hi; + friend int spread(int n); + +public: + Gauge() cv::post(lo == 0 && hi == 5) : lo(0), hi(5) {} + + cv::type_invariant(lo <= hi); +}; + +int spread(int n) + cv::pre(0 <= n && n <= 10) + cv::post(cv::result >= 0) +{ + Gauge g; + for (int i = 0; i < n; i = i + 1) + cv::invariant(0 <= i && i <= n && g.lo == i && g.hi == 5) + cv::decreases(n - i) + { + g.lo = g.lo + 1; + } + return g.hi - g.lo; +} +// CHECK-DAG: class_invariants_in_loops.cpp:[[@LINE-16]]:25: error: verification failed: spread [invariant-preserved] [backend=z3] [reason=counterexample] diff --git a/clang/test/Verify/suite/class_templates.cpp b/clang/test/Verify/suite/class_templates.cpp index c59e3aaa2b..8035283f81 100644 --- a/clang/test/Verify/suite/class_templates.cpp +++ b/clang/test/Verify/suite/class_templates.cpp @@ -82,9 +82,9 @@ signed char twice_char() // CHECK-DAG: Verified: Box::twice [backend=z3] // CHECK-DAG: Verified: twice_int [backend=z3] // CHECK-DAG: Verified: Box::Box [backend=z3] -// CHECK-DAG: error: verification failed: Box::twice [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: Box::twice [postcondition] // NOTE: error: verification failed: Box::twice -// NOTE-NEXT: class_templates.cpp:[[@LINE-8]]:21: note: in instantiation of member function 'Box::twice' requested here +// NOTE: class_templates.cpp:[[@LINE-8]]:21: note: in instantiation of member function 'Box::twice' requested here // An out-of-line definition with a contract, explicitly instantiated. template diff --git a/clang/test/Verify/suite/collections.cpp b/clang/test/Verify/suite/collections.cpp index fd2d88e85a..1af454789b 100644 --- a/clang/test/Verify/suite/collections.cpp +++ b/clang/test/Verify/suite/collections.cpp @@ -32,7 +32,8 @@ cppverify::proof void set_wrong(set a, int x) { cppverify::check(a.contains(x)); } -// CHECK-DAG: error: verification failed: set_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: a [ssa=a_0] [type=set] = {{{.*}}}{{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: set_wrong [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: a = {{{.*}}}{{.*}} cppverify::proof void multisets(multiset m, int x) { @@ -48,7 +49,7 @@ cppverify::proof void multiset_wrong(multiset m, int x) { cppverify::check(m.remove(x).insert(x) == m); } -// CHECK-DAG: error: verification failed: multiset_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: multiset_wrong [assertion] [backend=z3] [reason=counterexample] cppverify::proof void maps(map m, int k, int v, int j) cppverify::pre(k != j) @@ -67,21 +68,24 @@ cppverify::proof void map_wrong(map m, int k) { cppverify::check(m.insert(k, 1).remove(k) == m); } -// CHECK-DAG: error: verification failed: map_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: {{.*}}m [ssa=m_0] [type=map] = {{{.*}} -> {{.*}}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: map_wrong [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}m = {{{.*}} -> {{.*}}} // A set may hold every integer; the certifier checks the infinite one. cppverify::proof void some_outside(set a) { cppverify::check(cppverify::exists(k, !a.contains(k))); } -// CHECK-DAG: error: verification failed: some_outside [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: a [ssa=a_0] [type=set] = {..}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: some_outside [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: a = {..} cppverify::proof void map_values(map m) cppverify::pre(m.contains(5)) { cppverify::check(cppverify::forall(k, m[k] == 0)); } -// CHECK-DAG: error: verification failed: map_values [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: m [ssa=m_0] [type=map] = {{{.*}}5{{.*}}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: map_values [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: m = {{{.*}}5{{.*}}} cppverify::spec bool valid(const int *p, int n) { return true; } diff --git a/clang/test/Verify/suite/command_line.cpp b/clang/test/Verify/suite/command_line.cpp index 9c7b6a5579..10b71413e6 100644 --- a/clang/test/Verify/suite/command_line.cpp +++ b/clang/test/Verify/suite/command_line.cpp @@ -64,7 +64,7 @@ int first(const int *p) // 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: 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: {{.}} @@ -72,7 +72,7 @@ int first(const int *p) // 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 +// QUIET: Summary: 4 verified, 1 failed, 1 unresolved (construct.unsupported 1), 1 trusted in // VERBOSE: Verified: spec axiom: twice // VERBOSE-NEXT: Verified: spec decreases: down diff --git a/clang/test/Verify/suite/confirmation_invariants.cpp b/clang/test/Verify/suite/confirmation_invariants.cpp index 6900c3ccc6..071136de11 100644 --- a/clang/test/Verify/suite/confirmation_invariants.cpp +++ b/clang/test/Verify/suite/confirmation_invariants.cpp @@ -1,10 +1,13 @@ -// RUN: not %cpp-verify --jobs=4 --timeout=5000 --time-report %s -- 2>&1 | FileCheck %s +// RUN: not %cpp-verify --jobs=4 --timeout=20000 --time-report %s -- 2>&1 | FileCheck %s // // Unrolling drops the loops' invariants and measures, so a failure of one // has no copy at any bound: once the loops are unrolled once and no copy // has appeared, the search for an execution that fails it stops, instead of // solving every check at every bound until its time runs out (5.8 s at -// --timeout=5000 for this function, 30 s at the default 30000). +// --timeout=5000 for this function, 30 s at the default 30000). The time +// report shows the confirmation under a second; the budget leaves the +// failure itself time on a slow core (at --timeout=5000 a macOS runner timed +// out before finding it). namespace cv = cppverify; diff --git a/clang/test/Verify/suite/constant_expressions.cpp b/clang/test/Verify/suite/constant_expressions.cpp index 3d4507fffa..18d789dd67 100644 --- a/clang/test/Verify/suite/constant_expressions.cpp +++ b/clang/test/Verify/suite/constant_expressions.cpp @@ -32,4 +32,5 @@ int facts_wrong(int x) __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] +// CHECK-DAG: error: verification failed: facts_wrong [postcondition] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}result = 2{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/constructors.cpp b/clang/test/Verify/suite/constructors.cpp index 3a46c95158..17cdb271e7 100644 --- a/clang/test/Verify/suite/constructors.cpp +++ b/clang/test/Verify/suite/constructors.cpp @@ -85,7 +85,7 @@ struct Partial { int n; int limit; }; -// CHECK-DAG: error: verification failed: Partial::Partial [{{.*}}::initialization@[[@LINE-3]]:7] +// CHECK-DAG: :[[@LINE-3]]:7: error: verification failed: Partial::Partial [initialization] // Members are initialized in declaration order, whatever the order of the // initializers: `n` reads `limit` before it is initialized. @@ -97,7 +97,7 @@ struct Early { int n; int limit; }; -// CHECK-DAG: error: verification failed: Early::Early [{{.*}}::initialization@ +// CHECK-DAG: error: verification failed: Early::Early [initialization] // The in-class initializers of the members a constructor does not name run // in their place. diff --git a/clang/test/Verify/suite/contract_math.cpp b/clang/test/Verify/suite/contract_math.cpp index 354b4fa2bf..d95c7c6c48 100644 --- a/clang/test/Verify/suite/contract_math.cpp +++ b/clang/test/Verify/suite/contract_math.cpp @@ -13,7 +13,8 @@ unsigned add_wraps(unsigned a, unsigned b) { return a + b; } -// CHECK-DAG: error: verification failed: add_wraps [{{.*}}::postcondition@{{.*}}b [ssa=b_0] [type=u32] = +// CHECK-DAG: error: verification failed: add_wraps [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}b = unsigned add_mod(unsigned a, unsigned b) cppverify::post(cppverify::result == (a + b) % 4294967296) @@ -51,7 +52,7 @@ bool is_max(unsigned u) return u == 4294967295u; } // CHECK-DAG: warning: contract arithmetic is mathematical: the negative value -1 keeps its value here instead of converting to unsigned int -// CHECK-DAG: error: verification failed: is_max [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: is_max [postcondition] int quotient(int x, int y) cppverify::pre(y != 0 && !(x == -2147483647 - 1 && y == -1)) @@ -65,7 +66,7 @@ void divides_by_zero(int x, int y) cppverify::post(x / y == x / y) { } -// CHECK-DAG: error: verification failed: divides_by_zero [{{.*}}::division-by-zero@ +// CHECK-DAG: error: verification failed: divides_by_zero [division-by-zero] int even(int x) cppverify::pre(x >= 0 && x < 1000) @@ -89,7 +90,7 @@ int cast_overflows(int x) { return 0; } -// CHECK-DAG: error: verification failed: cast_overflows [{{.*}}::overflow@ +// CHECK-DAG: error: verification failed: cast_overflows [overflow] constexpr int twice(int v) { return v + v; } diff --git a/clang/test/Verify/suite/contract_wellformedness.cpp b/clang/test/Verify/suite/contract_wellformedness.cpp index c631d0b0ea..59bd58da53 100644 --- a/clang/test/Verify/suite/contract_wellformedness.cpp +++ b/clang/test/Verify/suite/contract_wellformedness.cpp @@ -25,10 +25,11 @@ int ratio(int a, int b) { return 0; } -// CHECK: [[@LINE-4]]:20: error: verification failed: contract of ratio [{{.*}}::contract::division-by-zero@[[@LINE-4]]:20] (counterexample: b [ssa=b_0] [type=i32] = 0) -// CHECK-NEXT: Verified: ratio [backend=z3] -// BMC: error: verification failed: contract of ratio [{{.*}}::contract::division-by-zero@ -// NOUB: error: verification failed: contract of ratio [{{.*}}::contract::division-by-zero@ +// CHECK: [[@LINE-4]]:20: error: verification failed: contract of ratio [division-by-zero] +// CHECK: note: counterexample: b = 0 +// CHECK: Verified: ratio [backend=z3] +// BMC: error: verification failed: contract of ratio [division-by-zero] +// NOUB: error: verification failed: contract of ratio [division-by-zero] int ratio_ok(int a, int b) cppverify::pre(b != 0 && a / b == 0) @@ -43,8 +44,9 @@ int deref(const int *p) { return 0; } -// CHECK: [[@LINE-4]]:18: error: verification failed: contract of deref [{{.*}}::contract::dereference@[[@LINE-4]]:18] (counterexample: p [ssa=p_0] [type=pointer] = 0) -// NOUB: error: verification failed: contract of deref [{{.*}}::contract::dereference@ +// CHECK: [[@LINE-4]]:18: error: verification failed: contract of deref [dereference] +// CHECK: note: counterexample: p = 0 +// NOUB: error: verification failed: contract of deref [dereference] int deref_ok(const int *p) cppverify::pre(p != nullptr && *p > 0) @@ -59,8 +61,8 @@ int at(const int *p, int i) { return 0; } -// CHECK: [[@LINE-4]]:33: error: verification failed: contract of at [{{.*}}::contract::bounds@[[@LINE-4]]:33] -// NOUB: error: verification failed: contract of at [{{.*}}::contract::dereference@ +// CHECK: [[@LINE-4]]:33: error: verification failed: contract of at [bounds] +// NOUB: error: verification failed: contract of at [dereference] int at_ok(const int *p, int i) cppverify::pre(valid(p, 8) && 0 <= i && i < 8 && p[i] == 3) @@ -78,7 +80,7 @@ int second(const int *p) { return 0; } -// CHECK: [[@LINE-4]]:34: error: verification failed: contract of second [{{.*}}::contract::bounds@[[@LINE-4]]:34] +// CHECK: [[@LINE-4]]:34: error: verification failed: contract of second [bounds] // NOUB-NOT: contract of second int slot(const Ring *r, int i) @@ -86,8 +88,8 @@ int slot(const Ring *r, int i) { return 0; } -// CHECK: [[@LINE-4]]:31: error: verification failed: contract of slot [{{.*}}::contract::bounds@[[@LINE-4]]:31] -// NOUB: error: verification failed: contract of slot [{{.*}}::contract::bounds@ +// CHECK: [[@LINE-4]]:31: error: verification failed: contract of slot [bounds] +// NOUB: error: verification failed: contract of slot [bounds] constexpr bool positive(int v) { return v > 0; } @@ -96,7 +98,8 @@ int next_positive(int x) { return 0; } -// CHECK: [[@LINE-4]]:29: error: verification failed: contract of next_positive [{{.*}}::contract::overflow@[[@LINE-4]]:29] (counterexample: x [ssa=x_0] [type=i32] = 2147483647) +// CHECK: [[@LINE-4]]:29: error: verification failed: contract of next_positive [overflow] +// CHECK: note: counterexample: x = 2147483647 constexpr int share(int v) { return 100 / v; } @@ -106,7 +109,8 @@ int shared(int x) { return 0; } -// CHECK: [[@LINE-4]]:18: error: verification failed: contract of shared [{{.*}}::contract::division-by-zero@[[@LINE-4]]:18] (counterexample: x [ssa=x_0] [type=i32] = 0) +// CHECK: [[@LINE-4]]:18: error: verification failed: contract of shared [division-by-zero] +// CHECK: note: counterexample: x = 0 // Order: a clause, an operand of && or ||, a branch of ?:, and a quantifier's // body are evaluated where what comes before them holds. @@ -125,7 +129,8 @@ int reversed(int a, int b) { return 0; } -// CHECK: [[@LINE-5]]:20: error: verification failed: contract of reversed [{{.*}}::contract::division-by-zero@[[@LINE-5]]:20] (counterexample: b [ssa=b_0] [type=i32] = 0) +// CHECK: [[@LINE-5]]:20: error: verification failed: contract of reversed [division-by-zero] +// CHECK: note: counterexample: b = 0 int either(int a, int b) cppverify::pre(b == 0 || a / b > 0) @@ -154,7 +159,7 @@ int one_more(const int *a, int n) { return 0; } -// CHECK: [[@LINE-4]]:18: error: verification failed: contract of one_more [{{.*}}::contract::bounds@[[@LINE-4]]:18] +// CHECK: [[@LINE-4]]:18: error: verification failed: contract of one_more [bounds] int sign(int x) cppverify::behavior(positive, x > 0) @@ -175,8 +180,8 @@ int read_ahead(const int *p, int i) { return p[i]; } -// CHECK: [[@LINE-4]]:37: error: verification failed: read_ahead [{{.*}}::bounds@[[@LINE-4]]:37] -// BMC: error: verification failed: read_ahead [{{.*}}::bounds@ +// CHECK: [[@LINE-4]]:37: error: verification failed: read_ahead [bounds] +// BMC: error: verification failed: read_ahead [bounds] int one(int x) cppverify::post(cppverify::result != 0) @@ -192,7 +197,7 @@ int zero(int x) { return 0; } -// CHECK: [[@LINE-5]]:37: error: verification failed: zero [{{.*}}::postcondition@[[@LINE-5]]:37] +// CHECK: [[@LINE-5]]:37: error: verification failed: zero [postcondition] // CHECK-NOT: division-by-zero // A loop invariant and an assertion read memory as the body does. @@ -209,7 +214,7 @@ int walk(const int *p, int n) } return 0; } -// CHECK: [[@LINE-7]]:43: error: verification failed: walk [{{.*}}::bounds@[[@LINE-7]]:43 +// CHECK: [[@LINE-7]]:43: error: verification failed: walk [bounds] int last(const int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n <= 100) @@ -218,8 +223,8 @@ int last(const int *p, int n) cppverify::check(p[n] >= 0); return 0; } -// CHECK: [[@LINE-3]]:3: error: verification failed: last [{{.*}}::bounds@[[@LINE-3]]:3] -// BMC: error: verification failed: last [{{.*}}::bounds@ +// CHECK: [[@LINE-3]]:3: error: verification failed: last [bounds] +// BMC: error: verification failed: last [bounds] int ghost_last(const int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n <= 100) @@ -227,7 +232,7 @@ int ghost_last(const int *p, int n) cppverify::ghost { cppverify::check(p[n] >= 0 || p[n] < 0); } return 0; } -// CHECK: [[@LINE-3]]:22: error: verification failed: ghost_last [{{.*}}::bounds@[[@LINE-3]]:22] +// CHECK: [[@LINE-3]]:22: error: verification failed: ghost_last [bounds] // The postcondition before it holds, and the body's own state places the // result: *result lies in p. @@ -274,7 +279,7 @@ int next_object(const int *p, const int *q) p = p + 1; return q[0]; } -// CHECK: [[@LINE-5]]:37: error: verification failed: next_object [{{.*}}::bounds@[[@LINE-5]]:37] +// CHECK: [[@LINE-5]]:37: error: verification failed: next_object [bounds] int prev_object(const int *p, const int *q) cppverify::pre(valid(p, 2) && valid(q, 2) && q == p + 2 && p[2] == 0) @@ -282,13 +287,14 @@ int prev_object(const int *p, const int *q) p = p + 1; return 0; } -// CHECK: [[@LINE-5]]:55: error: verification failed: contract of prev_object [{{.*}}::contract::bounds@[[@LINE-5]]:55] +// CHECK: [[@LINE-5]]:55: error: verification failed: contract of prev_object [bounds] // A trusted contract holds in every state its frame allows, with any result. [[cppverify::trusted]] int inverse(int x) cppverify::post(10 / cppverify::result > 0); // CHECK: Trusted: inverse (contract assumed, not verified) -// CHECK-NEXT: [[@LINE-2]]:22: error: verification failed: contract of inverse [{{.*}}::contract::division-by-zero@[[@LINE-2]]:22] (counterexample: result [ssa=__result_1] [type=i32] = 0) +// CHECK-NEXT: [[@LINE-2]]:22: error: verification failed: contract of inverse [division-by-zero] +// CHECK: note: counterexample: result = 0 [[cppverify::trusted]] int nonzero(int x) cppverify::post(cppverify::result != 0) @@ -305,7 +311,7 @@ int prev_object(const int *p, const int *q) [[cppverify::trusted]] int *find_any(int *p, int x) cppverify::pre(valid(p, 4)) cppverify::post(*cppverify::result == x); -// CHECK: [[@LINE-1]]:19: error: verification failed: contract of find_any [{{.*}}::contract::dereference@[[@LINE-1]]:19] +// CHECK: [[@LINE-1]]:19: error: verification failed: contract of find_any [dereference] // What the frame does not name keeps its value: *q stays nonzero. [[cppverify::trusted]] void scale(int *p, const int *q) @@ -319,14 +325,14 @@ int prev_object(const int *p, const int *q) cppverify::pre(p != nullptr && q != nullptr && *q != 0) cppverify::modifies(*p, *q) cppverify::post(*p == cppverify::old(*p) + 10 / *q); -// CHECK: [[@LINE-1]]:49: error: verification failed: contract of scale_both [{{.*}}::contract::division-by-zero@[[@LINE-1]]:49] +// CHECK: [[@LINE-1]]:49: error: verification failed: contract of scale_both [division-by-zero] // A contract without a definition is checked too. int past(const int *p, int n) cppverify::pre(valid(p, n) && n >= 1) cppverify::post(cppverify::result == p[n]); // CHECK: warning: past has a contract but no definition -// CHECK-NEXT: [[@LINE-2]]:40: error: verification failed: contract of past [{{.*}}::contract::bounds@[[@LINE-2]]:40] +// CHECK: [[@LINE-2]]:40: error: verification failed: contract of past [bounds] // A trusted contract whose extent marker cannot be read establishes nothing. [[cppverify::trusted]] int either_end(const int *p, int n) @@ -373,8 +379,8 @@ cppverify::proof void lemma(const int *a, int n) cppverify::pre(valid(a, n) && n >= 1 && a[n] == 0) { } -// CHECK: [[@LINE-3]]:43: error: verification failed: contract of lemma [{{.*}}::contract::bounds@[[@LINE-3]]:43] -// CHECK-NEXT: Verified: lemma [backend=z3] +// CHECK: [[@LINE-3]]:43: error: verification failed: contract of lemma [bounds] +// CHECK: Verified: lemma [backend=z3] // A spec is total: division by zero is zero there, and it has no // precondition to check. diff --git a/clang/test/Verify/suite/contracts_across_files.test b/clang/test/Verify/suite/contracts_across_files.test index 05e410f9e0..5951869829 100644 --- a/clang/test/Verify/suite/contracts_across_files.test +++ b/clang/test/Verify/suite/contracts_across_files.test @@ -30,7 +30,8 @@ RIGHT-NEXT: Summary: 3 verified WRONG: Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish) WRONG-NEXT: Unresolved: twice_scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish) -WRONG-NEXT: mathx.h:2:42: error: verification failed: clamp100 [{{.*}}::postcondition@2:42] (counterexample: {{.*}}result [ssa=__result_1] [type=i32] = 101 +WRONG: mathx.h:2:42: error: verification failed: clamp100 [postcondition] +WRONG: note: counterexample: {{.*}}result = 101{{( ->|,|$)}} WRONG: Summary: 1 failed, 2 unresolved (callee.contract 2) The files are printed in the order they are given, settled together. @@ -38,12 +39,12 @@ ORDER: mathx.h:2:42: error: verification failed: clamp100 ORDER: Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish) ORDER: Summary: 1 failed, 2 unresolved (callee.contract 2) -JSON: {"backend":"z3","function":"scaled","message":"scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish)","reason":"callee.contract",{{.*}}"status":"unresolved"} -JSON: "function":"clamp100","message":"verification failed: clamp100 +JSON: {"backend":"z3","function":"scaled",{{.*}}"message":"scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish)","reason":"callee.contract",{{.*}}"status":"unresolved"} +JSON: "function":"clamp100",{{.*}}"message":"verification failed: clamp100 A file alone is as before: the contract has no definition. ALONE: mathx.h:1:5: warning: clamp100 has a contract but no definition, so its callers are not verified; mark the declaration {{\[\[cppverify::trusted\]\]}} to assume the contract -ALONE-NEXT: Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100 (no definition; mark it {{\[\[cppverify::trusted\]\]}} to assume it), which is not established) +ALONE: Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100 (no definition; mark it {{\[\[cppverify::trusted\]\]}} to assume it), which is not established) ALONE-NEXT: Unresolved: twice_scaled [backend=z3] [reason=callee.contract] (relies on the contract of scaled, which is not established) A declaration that states another contract is not the definition's. diff --git a/clang/test/Verify/suite/contracts_link_check.test b/clang/test/Verify/suite/contracts_link_check.test index db269ac4ee..05ae98e817 100644 --- a/clang/test/Verify/suite/contracts_link_check.test +++ b/clang/test/Verify/suite/contracts_link_check.test @@ -18,10 +18,10 @@ # RUN: | FileCheck %s --check-prefix=MISSING # # Through the compiler, each object's record beside it. -# RUN: %clang -std=c++17 -fverify-contracts -fverify-contracts-out=%t/a.o.cppverify \ +# RUN: %clang -std=c++17 -fverify-contracts -Rcppverify -fverify-contracts-out=%t/a.o.cppverify \ # RUN: -c %S/Inputs/across/src/a.cpp -o %t/a.o -I%S/Inputs/across/include 2>&1 \ -# RUN: | FileCheck %s --check-prefix=APART -# RUN: %clang -std=c++17 -fverify-contracts -fverify-contracts-out=%t/m.o.cppverify \ +# RUN: | FileCheck %s --check-prefix=REMARK +# RUN: %clang -std=c++17 -fverify-contracts -Rcppverify -fverify-contracts-out=%t/m.o.cppverify \ # RUN: -c %S/Inputs/across/src/mathx.cpp -o %t/m.o -I%S/Inputs/across/include -DBUG=100 # RUN: %cpp-verify --check-contracts %t/a.o.cppverify %t/m.o.cppverify 2>&1 \ # RUN: | FileCheck %s --check-prefix=RIGHT @@ -45,6 +45,10 @@ A proof that rests on a contract of another file says so. APART: Verified: scaled [backend=z3] [assumes=clamp100] APART-NEXT: Verified: twice_scaled [backend=z3] [assumes=clamp100] +Through the compiler each proof is a remark at its function. +REMARK: a.cpp:3:5: remark: Verified: scaled [backend=z3] [assumes=clamp100] [-Rcppverify] +REMARK: a.cpp:9:5: remark: Verified: twice_scaled [backend=z3] [assumes=clamp100] [-Rcppverify] + WRONG: a.cpp:3:5: error: the proof of scaled relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish WRONG-NEXT: a.cpp:9:5: error: the proof of twice_scaled relies on the contract of clamp100, which its verification in {{.*}}mathx.cpp does not establish WRONG-NEXT: Contracts: 2 proofs rest on contracts of other files, 2 on one not established diff --git a/clang/test/Verify/suite/counterexample_confirmation.cpp b/clang/test/Verify/suite/counterexample_confirmation.cpp index 7895f51356..5650f198cd 100644 --- a/clang/test/Verify/suite/counterexample_confirmation.cpp +++ b/clang/test/Verify/suite/counterexample_confirmation.cpp @@ -31,7 +31,8 @@ cppverify::proof void small_power(int n) cppverify::post(pow2(n) < 1000) { } -// CHECK-DAG: error: verification failed: small_power {{.*}}(counterexample: n [ssa=n_0] [type=i32] = {{[0-9]+}}; confirmed by a proof at this input, since its check could not compute pow2({{[0-9]+}})) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: small_power [postcondition] (confirmed by a proof at this input, since its check could not compute pow2({{[0-9]+}})) [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: n = {{[0-9]+}} cppverify::spec int fibo(int n) cppverify::decreases(n) @@ -57,7 +58,8 @@ cppverify::proof void fibo_small(int n) cppverify::post(fibo(n) < 5) { } -// CHECK-DAG: error: verification failed: fibo_small {{.*}}(counterexample: n [ssa=n_0] [type=i32] = 1000000000; confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: fibo_small [postcondition] (confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: n = 1000000000 // JSON-DAG: "confirmed_with":["fibo_at_least_5"] // The same with a lemma that does not hold: a confirmation uses only the @@ -124,7 +126,9 @@ cppverify::spec int capped(int n) { return fibo(n); } -// CHECK-DAG: error: spec post failed: capped [backend=z3] [reason=counterexample] (n [type=math-i32] = 1000000000; confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) +// CHECK-DAG: error: spec post failed: capped [backend=z3] [reason=counterexample] (confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) +// CHECK-DAG: note: counterexample: n = 1000000000 +// CHECK-DAG: note: counterexample: {{.*}}n = 1000000000{{( ->|,|$)}} // A lemma is used through what its verification proved: its postconditions // wherever every assumption its proof started from holds. Besides its own @@ -157,7 +161,8 @@ cppverify::proof void fib4_small(int n) { } // CHECK-DAG: Verified: fib4_at_least_5 -// CHECK-DAG: error: verification failed: fib4_small {{.*}}(counterexample: n [ssa=n_0] [type=i32] = 1000000000; confirmed by a proof at this input that uses the contract of fib4_at_least_5, since its check could not compute fib4(1000000000)) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: fib4_small [postcondition] (confirmed by a proof at this input that uses the contract of fib4_at_least_5, since its check could not compute fib4(1000000000)) [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: n = 1000000000 // The same through mutable pointers, which must be distinct objects (or // null), and through a record whose type invariant the proof assumes. @@ -185,7 +190,7 @@ cppverify::proof void fib5_small(int n) { } // CHECK-DAG: error: verification failed: fib5_small {{.*}}confirmed by a proof at this input that uses the contract of fib5_at_least_5 -// JSON-DAG: "confirmed_with":["fib5_at_least_5"],"function":"fib5_small" +// JSON-DAG: "confirmed_with":["fib5_at_least_5"],{{.*}}"function":"fib5_small" struct Positive { int x; diff --git a/clang/test/Verify/suite/demoted_vacuity.cpp b/clang/test/Verify/suite/demoted_vacuity.cpp index 223c45fa56..c760bfb462 100644 --- a/clang/test/Verify/suite/demoted_vacuity.cpp +++ b/clang/test/Verify/suite/demoted_vacuity.cpp @@ -21,7 +21,7 @@ int uses_loop(int x) cppverify::check(loop(x) > 0 || loop(x) <= 0); return 1; } -// CHECK-NEXT: Unresolved: uses_loop [backend=z3] [reason=spec.termination] (relies on the definition of loop, whose termination is not established) +// CHECK: Unresolved: uses_loop [backend=z3] [reason=spec.termination] (relies on the definition of loop, whose termination is not established) // The precondition holds for every x. int loop_in_precondition(int x) @@ -45,7 +45,7 @@ int uses_contradictory(int x) int r = contradictory(x); return r; } -// CHECK-NEXT: Unresolved: uses_contradictory [backend=z3] [reason=callee.contract] (relies on the contract of contradictory, which is not established) +// CHECK: Unresolved: uses_contradictory [backend=z3] [reason=callee.contract] (relies on the contract of contradictory, which is not established) // A contract that fails but does not contradict itself. int wrong_seven(int x) @@ -57,7 +57,7 @@ int wrong_seven(int x) [[cppverify::trusted]] void claims_nonnegative(int x) cppverify::post(x >= 0); -// CHECK-NEXT: Trusted: claims_nonnegative +// CHECK: Trusted: claims_nonnegative int contradicted(int x) cppverify::post(cppverify::result == 7) @@ -84,7 +84,7 @@ int never_applies(int x) { return wrong_seven(x); } -// CHECK-NEXT: Unresolved: never_applies [backend=z3] [reason=callee.contract] (relies on the contract of wrong_seven, which is not established) +// CHECK: Unresolved: never_applies [backend=z3] [reason=callee.contract] (relies on the contract of wrong_seven, which is not established) // CHECK-NEXT: warning: never_applies: behavior impossible never applies // A proof that stands keeps its finding. @@ -94,9 +94,9 @@ int unsatisfiable(int x) { return 0; } -// CHECK-NEXT: Verified: unsatisfiable [backend=z3] [vacuous] +// CHECK: Verified: unsatisfiable [backend=z3] [vacuous] // CHECK-NEXT: warning: unsatisfiable: the precondition is unsatisfiable, so every claim about it holds vacuously -// CHECK-NEXT: Summary: {{.*}} +// CHECK: Summary: {{.*}} // CHECK-NOT: {{.}} // JSON: "function":"uses_loop" diff --git a/clang/test/Verify/suite/dynamic_storage_fail.cpp b/clang/test/Verify/suite/dynamic_storage_fail.cpp index fc6c1bf5e4..f15d51b2ac 100644 --- a/clang/test/Verify/suite/dynamic_storage_fail.cpp +++ b/clang/test/Verify/suite/dynamic_storage_fail.cpp @@ -439,7 +439,7 @@ int modular_discarded_pointer_forwarding() // VERIFY-DAG: Unresolved: modular_copied_pointer_return {{.*}}[reason=construct.unsupported] // VERIFY-DAG: Unresolved: modular_unsafe_forwarding {{.*}}[reason=construct.unsupported] // VERIFY-DAG: Unresolved: modular_discarded_pointer_forwarding {{.*}}[reason=construct.unsupported] -// VERIFY-DAG: error: verification failed: unsafe_forwarded_read [{{.*}}::bounds@107:37] +// VERIFY-DAG: :107:37: error: verification failed: unsafe_forwarded_read [bounds] // VERIFY-DAG: Verified: modular_forwarded_scalar // VERIFY-DAG: Verified: modular_forwarded_temporary // VERIFY-DAG: Verified: modular_forwarded_control diff --git a/clang/test/Verify/suite/enum_ranges.cpp b/clang/test/Verify/suite/enum_ranges.cpp index 7adc59e068..96acc5be64 100644 --- a/clang/test/Verify/suite/enum_ranges.cpp +++ b/clang/test/Verify/suite/enum_ranges.cpp @@ -71,21 +71,22 @@ bool fixed_type(Wide w) { return static_cast(w) < 2; } -// CHECK-DAG: error: verification failed: fixed_type [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: fixed_type [postcondition]{{.*}}[reason=counterexample] Color convert(int i) cppverify::pre(0 <= i && i <= 4) { return static_cast(i); } -// CHECK-DAG: error: verification failed: convert [{{.*}}::overflow@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: convert [overflow]{{.*}}[reason=counterexample] cppverify::spec bool small(Color c) cppverify::post(cppverify::result) { return c < 4; } -// CHECK-DAG: error: spec post failed: small [backend=z3] [reason=counterexample] (c [type=math-i32] = 4) +// CHECK-DAG: error: spec post failed: small [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}c = 4{{( ->|,|$)}} // The same check run as a proof function by its block. cppverify::spec bool small_by(Color c) diff --git a/clang/test/Verify/suite/extent_separation.cpp b/clang/test/Verify/suite/extent_separation.cpp index 19b811bed6..aab46d5965 100644 --- a/clang/test/Verify/suite/extent_separation.cpp +++ b/clang/test/Verify/suite/extent_separation.cpp @@ -1,5 +1,5 @@ // RUN: %clang -std=c++17 -fverify-contracts -fsyntax-only %s -// RUN: not %cpp-verify --check-ub %s -- 2>&1 | FileCheck %s --check-prefixes=CHECK,DECIDED +// RUN: not %cpp-verify --check-ub --timeout=120000 %s -- 2>&1 | FileCheck %s --check-prefixes=CHECK,DECIDED // RUN: not %cpp-verify --check-ub --int-encoding=bitvector --timeout=5000 %s -- 2>&1 | FileCheck %s --check-prefixes=CHECK,BV // RUN: %cpp-verify --check-ub --lower-only --dump-ir=1 %s -- 2>&1 | FileCheck %s --check-prefix=VCR // RUN: %cpp-verify --check-ub --lower-only --dump-ir=2 %s -- 2>&1 | FileCheck %s --check-prefix=PASSIVE @@ -51,8 +51,9 @@ void copy(const int *a, int *out, int n) // VCR-NEXT: post // A copy that writes zeros: its invariant fails where a holds anything else, -// which the solver finds at once (a reversed copy took 30 to 60 s, and on a -// slow runner timed out). +// which the solver finds in 1.7 s (3.6 s with one job; a reversed copy took +// 30 to 60 s). The first run's budget covers a slow runner, where the +// default 30 s once ran out. void wrong_copy(const int *a, int *out, int n) cppverify::pre(n >= 0 && valid(a, n) && valid(out, n)) cppverify::modifies(*out) @@ -68,7 +69,7 @@ void wrong_copy(const int *a, int *out, int n) i = i + 1; } } -// DECIDED-DAG: verification failed: wrong_copy [{{.*}}::invariant-preserved@ +// DECIDED-DAG: verification failed: wrong_copy [invariant-preserved] // BV-DAG: {{verification failed|Unresolved}}: wrong_copy // The separation must not make the preconditions contradictory. @@ -116,7 +117,7 @@ int overlapping_caller(int *buffer) { return inspect(buffer + 2, buffer, 4); } -// CHECK-DAG: verification failed: overlapping_caller [{{.*}}::aliasing@ +// CHECK-DAG: verification failed: overlapping_caller [aliasing] int empty_caller(int *buffer) cppverify::pre(valid(buffer, 8)) diff --git a/clang/test/Verify/suite/failure_notes.cpp b/clang/test/Verify/suite/failure_notes.cpp new file mode 100644 index 0000000000..a59957df9c --- /dev/null +++ b/clang/test/Verify/suite/failure_notes.cpp @@ -0,0 +1,342 @@ +// RUN: not %cpp-verify --jobs=1 %s -- 2>&1 | FileCheck %s --strict-whitespace +// RUN: not %cpp-verify --jobs=1 --backend=bmc --unroll=3 %s -- 2>&1 | FileCheck %s --check-prefix=BMC +// RUN: not %cpp-verify --jobs=1 --diagnostics-format=json %s -- 2>/dev/null | FileCheck %s --check-prefix=JSON +// RUN: not %cpp-verify --jobs=1 --obligation-out=%t.cpv %s -- > /dev/null 2>&1 +// RUN: not %cpp-verify --obligation-in=%t.cpv 2>&1 | FileCheck %s --check-prefix=REPLAY +// +// A failure is a Clang error at the failing check, its expression +// underlined with the caret at its operator when it lies on one line (a +// caret alone otherwise); its counterexample, each variable once with the +// values it takes along the path, and not those of branches the path does +// not take; and each step of the path at its line, in the words of Clang's +// static analyzer. A path of more than ten steps shows its first and last +// five. A step inside a macro is shown where the macro is used, and an +// operation whose own check fails is no step of the path. + +namespace cv = cppverify; + +int steps(int x) + cv::pre(0 <= x && x <= 10) + cv::post(cv::result < 3) +{ + int y = 0; + if (x > 4) + y = y + 1; + else + y = y + 2; + if (x > 8) + y = y + 3; + return y; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-11]]:23: error: verification failed: steps [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-12]] | cv::post(cv::result < 3){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-14]]:23: note: counterexample: x = {{9|10}}, y = 0 -> 1 -> 4, result = 4 +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | if (x > 4){{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-11]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-12]] | if (x > 8){{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: returns 4 +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | return y;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int add(int a) + cv::pre(a < 100) + cv::post(cv::result == a + 1) +{ + return a + 1; +} + +int calls(int x) + cv::pre(0 <= x && x < 50) + cv::post(cv::result < 10) +{ + return add(x); +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-4]]:23: error: verification failed: calls [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-5]] | cv::post(cv::result < 10){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-7]]:23: note: counterexample: x = [[X:[0-9]+]], result = {{[0-9]+}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-6]]:3: note: calls add([[X]]) +// CHECK-NEXT: {{^ *}}[[@LINE-7]] | return add(x);{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:3: note: returns {{[0-9]+}} + + +void store(int *p, int v) + cv::pre(p != nullptr) + cv::modifies(*p) + cv::post(*p == 7) +{ + *p = v; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-4]]:15: error: verification failed: store [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-5]] | cv::post(*p == 7){{$}} +// CHECK-NEXT: {{^}} | ~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-7]]:15: note: counterexample: p = [[P:[0-9]+]], v = [[V:-?[0-9]+]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-6]]:6: note: stores [[V]] at address [[P]] +// CHECK-NEXT: {{^ *}}[[@LINE-7]] | *p = v;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int heap(int v) + cv::post(cv::result == 1) +{ + int *p = new int(v); + int r = *p; + delete p; + return r; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-7]]:23: error: verification failed: heap [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-8]] | cv::post(cv::result == 1){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:23: note: counterexample: v = [[V:-?[0-9]+]], p = [[P:[0-9]+]], r = [[V]], result = [[V]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:3: note: allocates an object at address [[P]] +// CHECK-NEXT: {{^ *}}[[@LINE-10]] | int *p = new int(v);{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:3: note: deletes the object at address [[P]] +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | delete p;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: returns [[V]] +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | return r;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int local(int v) + cv::pre(0 <= v && v < 100) + cv::post(cv::result == 1) +{ + int a = v; + int &r = a; + r = r + 1; + return a; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-7]]:23: error: verification failed: local [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-8]] | cv::post(cv::result == 1){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:23: note: counterexample: v = [[V:[0-9]+]], &r = [[A:[0-9]+]], result = [[R:[0-9]+]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:3: note: creates a local object at address [[A]] +// CHECK-NEXT: {{^ *}}[[@LINE-10]] | int a = v;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:5: note: stores [[R]] at address [[A]] +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | r = r + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: returns [[R]] +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | return a;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int scoped(int v) + cv::pre(2 <= v && v < 100) + cv::post(cv::result == 1) +{ + int s = 0; + { + int a = v; + int &r = a; + s = r; + } + return s; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-10]]:23: error: verification failed: scoped [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | cv::post(cv::result == 1){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-13]]:23: note: counterexample: v = [[V:[0-9]+]], s = 0 -> [[V]], &r = [[A:[0-9]+]], result = [[V]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:5: note: creates a local object at address [[A]] +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | int a = v;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:3: note: ends the lifetime of a local object +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | }{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: returns [[V]] +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | return s;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int loop(int n) + cv::pre(0 <= n && n <= 5) + cv::post(cv::result == 0) +{ + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n) + cv::decreases(n - i) + { + i = i + 1; + } + return i; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-11]]:23: error: verification failed: loop [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-12]] | cv::post(cv::result == 0){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-14]]:23: note: counterexample: n = [[N:[1-5]]], i = 0 -> [[N]], result = [[N]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-12]]:3: note: enters the loop +// CHECK-NEXT: {{^ *}}[[@LINE-13]] | while (i < n){{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-15]]:3: note: leaves the loop +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-10]]:3: note: returns [[N]] +// CHECK-NEXT: {{^ *}}[[@LINE-11]] | return i;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: note: the counterexample's state at the loop at [[@LINE-19]]:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check within 1 iteration of each loop (counterexample: n = 1, i = 0 -> 1, result = 1) + +int long_path(int x) + cv::pre(0 <= x && x <= 100) + cv::post(cv::result < 12) +{ + int c = 0; + if (x > 0) c = c + 1; + if (x > 1) c = c + 1; + if (x > 2) c = c + 1; + if (x > 3) c = c + 1; + if (x > 4) c = c + 1; + if (x > 5) c = c + 1; + if (x > 6) c = c + 1; + if (x > 7) c = c + 1; + if (x > 8) c = c + 1; + if (x > 9) c = c + 1; + if (x > 10) c = c + 1; + if (x > 11) c = c + 1; + return c; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-17]]:23: error: verification failed: long_path [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-18]] | cv::post(cv::result < 12){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-20]]:23: note: counterexample: x = {{[0-9]+}}, c = 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12, result = 12 +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-18]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-19]] | if (x > 0) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-20]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-21]] | if (x > 1) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-22]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-23]] | if (x > 2) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-24]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-25]] | if (x > 3) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-26]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-27]] | if (x > 4) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: note: (skipping 3 steps of the path; JSON lists every one) +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-26]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-27]] | if (x > 8) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-28]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-29]] | if (x > 9) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-30]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-31]] | if (x > 10) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-32]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-33]] | if (x > 11) c = c + 1;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-34]]:3: note: returns 12 +// CHECK-NEXT: {{^ *}}[[@LINE-35]] | return c;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + +int two_lines(int x) + cv::pre(0 <= x && x <= 10) + cv::post(cv::result == + x + 1) +{ + return x; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-5]]:23: error: verification failed: two_lines [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-6]] | cv::post(cv::result =={{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-8]]:23: note: counterexample: x = [[X:[0-9]+]], result = [[X]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-6]]:3: note: returns [[X]] +// CHECK-NEXT: {{^ *}}[[@LINE-7]] | return x;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} + + +// JSON keeps every version, and marks those of branches the path does not +// take. +// A step the counterexample does not decide is marked. +int undecided(bool choose) + cv::post(cv::result == 0) +{ + if (choose) { + } else { + } + return 1; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-7]]:23: error: verification failed: undecided [postcondition] [backend=z3] [reason=counterexample] +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-6]]:3: note: perhaps takes the true branch (the counterexample does not decide whether this step is taken) +// CHECK-NEXT: {{^ *}}[[@LINE-7]] | if (choose) {{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:3: note: perhaps takes the false branch (the counterexample does not decide whether this step is taken) + +// A check inside a loop fails in an arbitrary iteration of it. +int iterate(int n) + cv::pre(0 <= n && n <= 5) + cv::post(cv::result == n) +{ + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n) + cv::decreases(n - i) + { + cv::check(i < 4); + i = i + 1; + } + return i; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-5]]:5: error: verification failed: iterate [assertion] [backend=z3] [reason=counterexample] +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-6]]:5: note: counterexample: n = [[N:[45]]], i = 0 -> 4 +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-11]]:3: note: enters the loop +// CHECK-NEXT: {{^ *}}[[@LINE-12]] | while (i < n){{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-14]]:3: note: starts an arbitrary iteration of the loop, where its condition is true + +// An archive keeps where each version is defined, so its replay lists the +// same values. +// REPLAY: error: verification failed: steps [postcondition] +// REPLAY-NEXT: {{.*}}failure_notes.cpp:[[@LINE-274]]:23: note: counterexample: x = {{9|10}}, y = 0 -> 1 -> 4, result = 4 + +// JSON: "function":"steps"{{.*}}{"name":"y","on_path":false,"sort":"i32",{{.*}}"ssa_name":"y_3","value":"0"} + +#define BUMP_IF_LARGE(v) if ((v) > 5) (v) = (v) + 1 + +int in_macro(int x) + cv::pre(0 <= x && x <= 10) + cv::post(cv::result <= 6) +{ + int y = x; + BUMP_IF_LARGE(y); + return y; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-6]]:23: error: verification failed: in_macro [postcondition] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-7]] | cv::post(cv::result <= 6){{$}} +// CHECK-NEXT: {{^}} | ~~~~~~~~~~~^~~~{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:23: note: counterexample: x = [[X:[6-9]|10]], y = [[X]] -> [[Y:[0-9]+]], result = [[Y]] +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-7]]:3: note: takes the true branch +// CHECK-NEXT: {{^ *}}[[@LINE-8]] | BUMP_IF_LARGE(y);{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-9]]:3: note: returns [[Y]] + +int negated(int x) + cv::post(cv::result >= 0 || cv::result < 0) +{ + return -x; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-2]]:3: error: verification failed: negated [overflow] [backend=z3] [reason=counterexample] +// CHECK-NEXT: {{^ *}}[[@LINE-3]] | return -x;{{$}} +// CHECK-NEXT: {{^}} | ^{{$}} +// CHECK-NEXT: {{.*}}failure_notes.cpp:[[@LINE-5]]:3: note: counterexample: x = -2147483648 +// CHECK-NOT: note: +// CHECK: error: verification failed: bump + +void bump(int *p) + cv::pre(p != nullptr) + cv::modifies(*p) +{ + *p = *p + 1; +} +// CHECK: {{.*}}failure_notes.cpp:[[@LINE-2]]:6: note: counterexample: p = {{[0-9]+}} +// CHECK-NOT: note: +// CHECK: Summary: + +// BMC unrolls the loop, so its path runs the iterations one by one. +// BMC: error: verification failed: loop [postcondition] [backend=bmc, bound=1] [reason=counterexample] +// BMC: note: counterexample: n = 1, i = 0 -> 1, result = 1 +// BMC-NEXT: note: runs iteration 1 of the loop diff --git a/clang/test/Verify/suite/failure_notes_cvc5.test b/clang/test/Verify/suite/failure_notes_cvc5.test new file mode 100644 index 0000000000..906ee21334 --- /dev/null +++ b/clang/test/Verify/suite/failure_notes_cvc5.test @@ -0,0 +1,65 @@ +# REQUIRES: cvc5 +# A counterexample found by cvc5 is shown as Z3's is (failure_notes.cpp): in +# source terms, each variable with the values it takes along the path, and +# the steps of the path, in text, JSON, and the replay of an archive. +# +# RUN: not %cpp-verify --jobs=1 --backend=cvc5 %S/failure_notes.cpp -- 2>&1 \ +# RUN: | FileCheck %s +# RUN: not %cpp-verify --jobs=1 --backend=cvc5 --diagnostics-format=json \ +# RUN: %S/failure_notes.cpp -- 2>/dev/null | FileCheck %s --check-prefix=JSON +# RUN: not %cpp-verify --jobs=1 --obligation-out=%t.cpv %S/failure_notes.cpp -- > /dev/null 2>&1 +# RUN: not %cpp-verify --obligation-in=%t.cpv --backend=cvc5 2>&1 \ +# RUN: | FileCheck %s --check-prefix=REPLAY +# RUN: not %cpp-verify --jobs=1 --backend=cvc5 %S/Inputs/failure_notes_quantified.cpp -- 2>&1 \ +# RUN: | FileCheck %s --check-prefix=UNKNOWN + +CHECK: failure_notes.cpp:20:23: error: verification failed: steps [postcondition] [backend=cvc5] [reason=counterexample] +CHECK: failure_notes.cpp:20:23: note: counterexample: x = {{9|10}}, y = 0 -> 1 -> 4, result = 4 +CHECK-NOT: false branch +CHECK: failure_notes.cpp:23:3: note: takes the true branch +CHECK-NOT: false branch +CHECK: failure_notes.cpp:27:3: note: takes the true branch +CHECK-NOT: false branch +CHECK: failure_notes.cpp:29:3: note: returns 4 + +CHECK: failure_notes.cpp:54:23: error: verification failed: calls [postcondition] [backend=cvc5] [reason=counterexample] +CHECK: failure_notes.cpp:54:23: note: counterexample: x = [[X:[0-9]+]], result = {{[0-9]+}} +CHECK: failure_notes.cpp:56:3: note: calls add([[X]]) + +CHECK: failure_notes.cpp:107:23: error: verification failed: local [postcondition] [backend=cvc5] [reason=counterexample] +CHECK: failure_notes.cpp:107:23: note: counterexample: v = {{[0-9]+}}, &r = [[R:[0-9]+]], result = [[V:[0-9]+]] +CHECK: failure_notes.cpp:109:3: note: creates a local object at address [[R]] +CHECK: failure_notes.cpp:111:5: note: stores [[V]] at address [[R]] +CHECK: failure_notes.cpp:112:3: note: returns [[V]] + +CHECK: failure_notes.cpp:159:3: note: enters the loop +CHECK: failure_notes.cpp:159:3: note: leaves the loop + +CHECK: note: (skipping 3 steps of the path; JSON lists every one) + +CHECK: failure_notes.cpp:258:3: note: perhaps takes the true branch (the counterexample does not decide whether this step is taken) + +A check inside a loop fails in an iteration of it: its path ends there. +CHECK: failure_notes.cpp:279:5: error: verification failed: iterate [assertion] [backend=cvc5] [reason=counterexample] +CHECK: failure_notes.cpp:275:3: note: enters the loop +CHECK: failure_notes.cpp:275:3: note: starts an arbitrary iteration of the loop, where its condition is true +CHECK-NOT: failure_notes.cpp:{{.*}}: note: +CHECK: note: the counterexample's state at the loop at 275:3 is any state + +The model lists the variables in the order Z3's does, and marks a version +of a branch the path does not take. +JSON: {"backend":"cvc5","counterexample":"x = {{9|10}}, y = 0 -> 1 -> 4, result = 4","function":"steps" +JSON-SAME: "model":[{"name":"result", +JSON-SAME: {"name":"y","on_path":false,"sort":"i32",{{.*}}"ssa_name":"y_3","value":"0"} +JSON-SAME: "trace":[{"active":true,"kind":"branch","message":"then",{{.*}}"text":"takes the true branch"} + +REPLAY: failure_notes.cpp:20:23: error: verification failed: steps [postcondition] [backend=cvc5] [reason=counterexample] [semantic-hash= +REPLAY-NEXT: failure_notes.cpp:20:23: note: counterexample: x = {{9|10}}, y = 0 -> 1 -> 4, result = 4 +REPLAY-NEXT: failure_notes.cpp:23:3: note: takes the true branch + +cvc5 answers unknown where a quantifier is assumed, and prints a model: once +certified, it is described the same way. +UNKNOWN: failure_notes_quantified.cpp:9:23: error: verification failed: first [postcondition] [backend=cvc5] [reason=counterexample] +UNKNOWN: failure_notes_quantified.cpp:9:23: note: counterexample: a = {{[0-9]+}}, n = {{[0-9]+}}, r = 0, result = 0 +UNKNOWN: failure_notes_quantified.cpp:12:3: note: takes the false branch +UNKNOWN: failure_notes_quantified.cpp:14:3: note: returns 0 diff --git a/clang/test/Verify/suite/forall_introduction.cpp b/clang/test/Verify/suite/forall_introduction.cpp index d1ee763593..e3321af9fd 100644 --- a/clang/test/Verify/suite/forall_introduction.cpp +++ b/clang/test/Verify/suite/forall_introduction.cpp @@ -40,7 +40,7 @@ cppverify::proof void strictly_below_last(const int *a, int n) pair_ordered(a, n, k, n - 1); } } -// CHECK-DAG: error: verification failed: strictly_below_last [{{.*}}::assertion@[[@LINE-4]]:3] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-4]]:3: error: verification failed: strictly_below_last [assertion] [backend=z3] [reason=counterexample] // Without bounds the variable ranges over every integer. cppverify::proof void squares(int m) diff --git a/clang/test/Verify/suite/frame_call_objects.cpp b/clang/test/Verify/suite/frame_call_objects.cpp index 8de22ab475..1f77462e29 100644 --- a/clang/test/Verify/suite/frame_call_objects.cpp +++ b/clang/test/Verify/suite/frame_call_objects.cpp @@ -105,7 +105,7 @@ void neighbor_cell(int *a) { set(a + 3, 5); } -// CHECK-DAG: error: verification failed: neighbor_cell [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: neighbor_cell [frame] // A copy into another parameter's object, outside the caller's region. void other_object(int *a, int *b) @@ -115,7 +115,7 @@ void other_object(int *a, int *b) int *q = b + 1; set(q, 5); } -// CHECK-DAG: error: verification failed: other_object [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: other_object [frame] // The copy starts inside a's object only if b lay below it. void other_object_above(int *a, int *b) @@ -125,7 +125,7 @@ void other_object_above(int *a, int *b) int *q = b + 1; set(q, 5); } -// CHECK-DAG: error: verification failed: other_object_above [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: other_object_above [frame] // A callee may name more cells than its extent: these run past a's object. void wide(int *p) @@ -143,7 +143,7 @@ void wide_past(int *a) int *q = a + 3; wide(q); } -// CHECK-DAG: error: verification failed: wide_past [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: wide_past [frame] // When c is false the cell is in b, which the caller may not write. void chosen_outside(int *a, int *b, bool c) @@ -153,7 +153,8 @@ void chosen_outside(int *a, int *b, bool c) int *q = c ? a + 1 : b + 3; set(q, 5); } -// CHECK-DAG: error: verification failed: chosen_outside [{{.*}}::frame@[[@LINE-2]]:3] (counterexample: {{.*}}c [ssa=c_0] [type=bool] = false, +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: chosen_outside [frame] +// CHECK-DAG: note: counterexample: {{.*}}c = false{{( ->|,|$)}} // The cell before the object, where its end would still be inside. [[cppverify::trusted]] int *before(const int *a, int n) @@ -168,7 +169,7 @@ void before_object(int *a) int *q = before(a, 4); set(q, 5); } -// CHECK-DAG: error: verification failed: before_object [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: before_object [frame] // The range a[3 : 2] runs past the cell a[3] the caller may write. void range_past_cell(int *a) @@ -178,4 +179,4 @@ void range_past_cell(int *a) int *q = a + 3; zero2(q, 2); } -// CHECK-DAG: error: verification failed: range_past_cell [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: range_past_cell [frame] diff --git a/clang/test/Verify/suite/frontend_gaps.cpp b/clang/test/Verify/suite/frontend_gaps.cpp index bb1c9036fb..d576fc598e 100644 --- a/clang/test/Verify/suite/frontend_gaps.cpp +++ b/clang/test/Verify/suite/frontend_gaps.cpp @@ -26,7 +26,8 @@ bool is_digit_wrong(char c) { return c >= '0' && c <= '9'; } -// CHECK-DAG: error: verification failed: is_digit_wrong [{{.*}}c [ssa=c_0] [type=i8] = 57{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: is_digit_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}c = 57{{( ->|,|$)}} int digit_value(char c) cv::pre('0' <= c && c <= '9') diff --git a/clang/test/Verify/suite/function_filter.cpp b/clang/test/Verify/suite/function_filter.cpp index 91da544d7e..940fc41a58 100644 --- a/clang/test/Verify/suite/function_filter.cpp +++ b/clang/test/Verify/suite/function_filter.cpp @@ -142,7 +142,7 @@ cv::spec bool climbs(int a, int b) // STEP: Verified: inductive predicate: climbs // STEP-NEXT: Unresolved: spec post: climbs [reason=spec.post] (relies on the postcondition of climbs.step, which is not established) // STEP-NEXT: function_filter.cpp:92:24: error: spec post by induction failed: climbs -// STEP-NEXT: Summary: 1 verified, 1 failed, 1 unresolved (spec.post 1) +// STEP: Summary: 1 verified, 1 failed, 1 unresolved (spec.post 1) // NONE: error: no function matches --function=nothing diff --git a/clang/test/Verify/suite/function_templates.cpp b/clang/test/Verify/suite/function_templates.cpp index 5c7b797aea..bea52d9834 100644 --- a/clang/test/Verify/suite/function_templates.cpp +++ b/clang/test/Verify/suite/function_templates.cpp @@ -60,11 +60,12 @@ signed char twice_char(signed char x) } // CHECK-DAG: Verified: twice [backend=z3] // CHECK-DAG: Verified: twice_int [backend=z3] -// CHECK-DAG: error: verification failed: twice [{{.*}}::postcondition@[[@LINE-20]]:{{.*}} x [ssa=x_0] [type=i8] = {{6[4-9]|[7-9][0-9]|100}} +// CHECK-DAG: :[[@LINE-20]]:{{[0-9]+}}: error: verification failed: twice [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}x = {{6[4-9]|[7-9][0-9]|100}}{{( ->|,|$)}} // CHECK-DAG: Unresolved: twice_char [backend=z3] [reason=callee.contract] (relies on the contract of twice, which is not established) // NOTE: error: verification failed: twice -// NOTE-NEXT: function_templates.cpp:[[@LINE-8]]:19: note: in instantiation of function template specialization 'twice' requested here -// JSON: "function":"twice","instantiated_at":{"column":19,"end_column":19,"end_line":[[@LINE-9]],"file":"{{.*}}function_templates.cpp","line":[[@LINE-9]]} +// NOTE: function_templates.cpp:[[@LINE-9]]:19: note: in instantiation of function template specialization 'twice' requested here +// JSON: "function":"twice",{{.*}}"instantiated_at":{"column":19,"end_column":24,"end_line":[[@LINE-10]],"file":"{{.*}}function_templates.cpp","line":[[@LINE-10]]} // A recursive template calls its own specialization. template @@ -143,10 +144,10 @@ short total_short(const short *p, int n) } // CHECK-DAG: Verified: total [backend=z3] // CHECK-DAG: Verified: total_int [backend=z3] -// CHECK-DAG: error: verification failed: total [{{.*}}::invariant-preserved@ +// CHECK-DAG: error: verification failed: total [invariant-preserved] // CHECK-DAG: Unresolved: total_short [backend=z3] [reason=callee.contract] // NOTE: error: verification failed: total -// NOTE-NEXT: function_templates.cpp:[[@LINE-8]]:13: note: in instantiation of function template specialization 'total' requested here +// NOTE: function_templates.cpp:[[@LINE-8]]:13: note: in instantiation of function template specialization 'total' requested here // Ghost code of a template's types, a ghost variable assigned in a ghost // block. @@ -415,4 +416,5 @@ int product_of(Ns... ns) } int products() { return product_of(2, 3); } -// CHECK-DAG: error: verification failed: product_of [{{.*}}::postcondition@[[@LINE-6]]:{{.*}} ns...[0] [ssa=ns...[0]_0] [type=i32] = {{.*}} ns...[1] [ssa=ns...[1]_0] [type=i32] = +// CHECK-DAG: :[[@LINE-6]]:{{[0-9]+}}: error: verification failed: product_of [postcondition] +// CHECK-DAG: note: counterexample: ns...[0] = {{[0-9]+}}, ns...[1] = {{[0-9]+}}, result = {{[0-9]+}} diff --git a/clang/test/Verify/suite/ghost_locals.cpp b/clang/test/Verify/suite/ghost_locals.cpp index f197d55a17..d295f139d6 100644 --- a/clang/test/Verify/suite/ghost_locals.cpp +++ b/clang/test/Verify/suite/ghost_locals.cpp @@ -44,4 +44,4 @@ int remembers_wrong(int x) cppverify::check(x == before); return x; } -// CHECK-DAG: error: verification failed: remembers_wrong [{{.*}}::assertion@ +// CHECK-DAG: error: verification failed: remembers_wrong [assertion] diff --git a/clang/test/Verify/suite/ghost_math_values.cpp b/clang/test/Verify/suite/ghost_math_values.cpp index a0094a2217..002b294388 100644 --- a/clang/test/Verify/suite/ghost_math_values.cpp +++ b/clang/test/Verify/suite/ghost_math_values.cpp @@ -26,7 +26,7 @@ cppverify::proof void stores_element(seq s) { int x = s[0]; } -// CHECK-DAG: error: verification failed: stores_element [{{.*}}::overflow@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: stores_element [overflow] cppverify::proof void stores_bounded_element(seq s) cppverify::pre(s.len() > 0 && 0 <= s[0] && s[0] < 100) @@ -48,7 +48,7 @@ cppverify::proof void passes_length(seq s) { takes_int(s.len() + 2147483647); } -// CHECK-DAG: error: verification failed: passes_length [{{.*}}::overflow@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: passes_length [overflow] cppverify::proof void passes_bounded_length(seq s) cppverify::pre(s.len() < 1000) @@ -77,4 +77,4 @@ void ghost_length_too_large(int n) int k = s.len() + 2147483647; } } -// CHECK-DAG: error: verification failed: ghost_length_too_large [{{.*}}::overflow@ +// CHECK-DAG: error: verification failed: ghost_length_too_large [overflow] diff --git a/clang/test/Verify/suite/globals.cpp b/clang/test/Verify/suite/globals.cpp index 9bc95c3bb4..102d799894 100644 --- a/clang/test/Verify/suite/globals.cpp +++ b/clang/test/Verify/suite/globals.cpp @@ -39,7 +39,7 @@ void bump_unframed() { counter = counter + 1; } -// CHECK-DAG: error: verification failed: bump_unframed [{{.*}}::frame@ +// CHECK-DAG: error: verification failed: bump_unframed [frame] int twice() cppverify::pre(counter < 100) @@ -67,7 +67,7 @@ void frames_wrong(int *p) { bump(); } -// CHECK-DAG: error: verification failed: frames_wrong [{{.*}}::frame@ +// CHECK-DAG: error: verification failed: frames_wrong [frame] void forgets_old() cppverify::pre(counter < 1000) @@ -76,6 +76,6 @@ void forgets_old() { bump(); } -// CHECK-DAG: error: verification failed: forgets_old [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: forgets_old [postcondition] // SPEC: error: reads_counter: a spec function cannot read the mutable global counter; pass its value as a parameter diff --git a/clang/test/Verify/suite/hide_template_specs.cpp b/clang/test/Verify/suite/hide_template_specs.cpp new file mode 100644 index 0000000000..d550e49de0 --- /dev/null +++ b/clang/test/Verify/suite/hide_template_specs.cpp @@ -0,0 +1,53 @@ +// RUN: not %cpp-verify --jobs=1 %s -- 2>&1 | FileCheck %s +// RUN: not %cpp-verify --jobs=1 %s -- -DUNNAMED 2>&1 | FileCheck %s --check-prefix=UNNAMED +// +// hide, reveal, and reveal_with_fuel name one specialization of a spec +// template, as at<4> does. + +namespace cv = cppverify; + +template cv::spec int scaled(int x) { return K * x; } + +template cv::spec int steps(int n) + cv::decreases(n) +{ + return n <= 0 ? 0 : K + steps(n - 1); +} + +int shown(int x) + cv::pre(0 <= x && x <= 100) + cv::post(cv::result == scaled<3>(x)) +{ + return 3 * x; +} +// CHECK-DAG: Verified: shown [backend=z3] + +// Hidden, the specialization's definition does not reach the proof. +int hidden(int x) + cv::pre(0 <= x && x <= 100) + cv::post(cv::result == scaled<3>(x)) +{ + cv::hide(scaled<3>); + return 3 * x; +} +// CHECK-DAG: Unresolved: hidden [backend=z3] [reason=spec.hidden] ({{.*}}the proof needs the definition of scaled<3>, which is hidden from the solver + +// Fuel unfolds a recursive specialization as deep as asked. +int fueled() + cv::post(cv::result == steps<2>(3)) +{ + cv::ghost { cv::reveal_with_fuel(steps<2>, 4); } + return 6; +} +// CHECK-DAG: Verified: fueled [backend=z3] + +#ifdef UNNAMED +// The template's name names no one specialization. +int unnamed() + cv::post(cv::result == 0) +{ + cv::hide(scaled); + return 0; +} +// UNNAMED: hide_template_specs.cpp:[[@LINE-3]]:12: error: reference to overloaded function could not be resolved; did you mean to call it? +#endif diff --git a/clang/test/Verify/suite/inductive_counterexamples.cpp b/clang/test/Verify/suite/inductive_counterexamples.cpp index 7ce2f26981..b052252f18 100644 --- a/clang/test/Verify/suite/inductive_counterexamples.cpp +++ b/clang/test/Verify/suite/inductive_counterexamples.cpp @@ -196,41 +196,43 @@ int count_up_proved(int n) return c; } -// CHECK: inductive_counterexamples.cpp:16:23: error: verification failed: count_up [{{.*}}::postcondition@16:23] {{.*}}trace: loop.entry@19:3 -> loop.exit@19:3 -// CHECK-NEXT: note: the counterexample's state at the loop at 19:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK: inductive_counterexamples.cpp:16:23: error: verification failed: count_up [postcondition] +// CHECK: inductive_counterexamples.cpp:19:3: note: enters the loop +// CHECK: inductive_counterexamples.cpp:19:3: note: leaves the loop +// CHECK: note: the counterexample's state at the loop at 19:3 is any state its invariants allow, not necessarily one an execution reaches // CHECK-NEXT: note: no execution fails this check: every loop ends within 5 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof -// CHECK-NEXT: inductive_counterexamples.cpp:29:23: error: verification failed: sum_wrong [{{.*}}::postcondition@29:23] -// CHECK-NEXT: note: the counterexample's state at the loop at 32:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: note: an execution fails this check within 1 iteration of each loop (counterexample: {{.*}}n [ssa=n_0] [type=i32] = 1{{.*}}) -// CHECK-NEXT: inductive_counterexamples.cpp:43:23: error: verification failed: far_wrong [{{.*}}::postcondition@43:23] -// CHECK-NEXT: note: the counterexample's state at the loop at 46:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: inductive_counterexamples.cpp:29:23: error: verification failed: sum_wrong [postcondition] +// CHECK: note: the counterexample's state at the loop at 32:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check within 1 iteration of each loop (counterexample: {{.*}}n = 1{{.*}}) +// CHECK-NEXT: inductive_counterexamples.cpp:43:23: error: verification failed: far_wrong [postcondition] +// CHECK: note: the counterexample's state at the loop at 46:3 is any state its invariants allow, not necessarily one an execution reaches // CHECK-NEXT: note: no execution fails this check within 10 iterations of each loop -// CHECK-NEXT: inductive_counterexamples.cpp:57:23: error: verification failed: no_loop [{{.*}}::postcondition@57:23] -// CHECK-NEXT: inductive_counterexamples.cpp:64:23: error: verification failed: unmeasured [{{.*}}::postcondition@64:23] -// CHECK-NEXT: note: the loop at 67:3 has no cppverify::decreases: a proof would be Unresolved (decreases.missing) until it has one +// CHECK-NEXT: inductive_counterexamples.cpp:57:23: error: verification failed: no_loop [postcondition] +// CHECK: inductive_counterexamples.cpp:64:23: error: verification failed: unmeasured [postcondition] +// CHECK: note: the loop at 67:3 has no cppverify::decreases: a proof would be Unresolved (decreases.missing) until it has one // CHECK-NEXT: note: the counterexample's state at the loop at 67:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n [ssa=n_0] [type=i32] = 0{{.*}}) -// CHECK-NEXT: inductive_counterexamples.cpp:87:7: error: verification failed: divides [{{.*}}::division-by-zero@87:7] -// CHECK-NEXT: note: the counterexample's state at the loop at 83:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: note: an execution fails this check within 4 iterations of each loop (counterexample: {{.*}}c [ssa=c_4] [type=i32] = 3{{.*}}) -// CHECK-NEXT: inductive_counterexamples.cpp:100:21: error: verification failed: grows [{{.*}}::invariant-preserved@100:21] -// CHECK-NEXT: note: the counterexample's state at the loop at 99:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: inductive_counterexamples.cpp:119:5: error: verification failed: two_checks [{{.*}}::assertion@119:5] -// CHECK-NEXT: note: the counterexample's state at the loop at 115:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: note: an execution fails this check within 5 iterations of each loop (counterexample: {{.*}}n [ssa=n_0] [type=i32] = 5{{.*}}) -// CHECK-NEXT: inductive_counterexamples.cpp:140:7: error: verification failed: two_kinds [{{.*}}::overflow@140:7] -// CHECK-NEXT: note: the counterexample's state at the loop at 136:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n = 0{{.*}}) +// CHECK-NEXT: inductive_counterexamples.cpp:87:7: error: verification failed: divides [division-by-zero] +// CHECK: note: the counterexample's state at the loop at 83:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check within 4 iterations of each loop (counterexample: {{.*}}c = 0 -> 1 -> 2 -> 3{{.*}}) +// CHECK-NEXT: inductive_counterexamples.cpp:100:21: error: verification failed: grows [invariant-preserved] +// CHECK: note: the counterexample's state at the loop at 99:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: inductive_counterexamples.cpp:119:5: error: verification failed: two_checks [assertion] +// CHECK: note: the counterexample's state at the loop at 115:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check within 5 iterations of each loop (counterexample: {{.*}}n = 5{{.*}}) +// CHECK-NEXT: inductive_counterexamples.cpp:140:7: error: verification failed: two_kinds [overflow] +// CHECK: note: the counterexample's state at the loop at 136:3 is any state its invariants allow, not necessarily one an execution reaches // CHECK-NEXT: note: no execution fails this check without failing an earlier one first (the division-by-zero check at 140:7 fails): those that pass the checks before it end every loop within 2 iterations, and unrolling proves it there -// CHECK-NEXT: inductive_counterexamples.cpp:160:7: error: verification failed: past_failure [{{.*}}::division-by-zero@160:7] -// CHECK-NEXT: note: the counterexample's state at the loop at 156:3 is any state its invariants allow, not necessarily one an execution reaches -// CHECK-NEXT: note: an execution fails this check within 4 iterations of each loop (counterexample: {{.*}}k [ssa=k_0] [type=i32] = 3, m [ssa=m_0] [type=i32] = 100{{.*}}) -// CHECK-NEXT: inductive_counterexamples.cpp:170:23: error: verification failed: skipped [{{.*}}::postcondition@170:23] -// CHECK-NEXT: Verified: count_up_proved [backend=z3] +// CHECK-NEXT: inductive_counterexamples.cpp:160:7: error: verification failed: past_failure [division-by-zero] +// CHECK: note: the counterexample's state at the loop at 156:3 is any state its invariants allow, not necessarily one an execution reaches +// CHECK-NEXT: note: an execution fails this check within 4 iterations of each loop (counterexample: {{.*}}k = 3, m = 100{{.*}}) +// CHECK-NEXT: inductive_counterexamples.cpp:170:23: error: verification failed: skipped [postcondition] +// CHECK: Verified: count_up_proved [backend=z3] // CHECK-NEXT: Summary: 1 verified, 11 failed // Without unrolling, a failure still says where its state comes from. // NONE: error: verification failed: count_up -// NONE-NEXT: note: the counterexample's state at the loop at 19:3 is any state its invariants allow, not necessarily one an execution reaches +// NONE: note: the counterexample's state at the loop at 19:3 is any state its invariants allow, not necessarily one an execution reaches // NONE-NEXT: error: verification failed: sum_wrong // JSON: "function":"count_up",{{.*}}"notes":["the counterexample's state at the loop at 19:3 is any state its invariants allow, not necessarily one an execution reaches","no execution fails this check: every loop ends within 5 iterations{{.*}}"] diff --git a/clang/test/Verify/suite/inductive_lemmas.cpp b/clang/test/Verify/suite/inductive_lemmas.cpp index 50f20aad69..0a310414b3 100644 --- a/clang/test/Verify/suite/inductive_lemmas.cpp +++ b/clang/test/Verify/suite/inductive_lemmas.cpp @@ -54,7 +54,8 @@ cppverify::proof void four_after_three() cppverify::pre(od(3)) cppverify::post(e // A postcondition cannot assume itself: bad(0) holds, so !bad(0) fails. cppverify::spec bool bad(int n); cppverify::spec bool bad(int n) cppverify::inductive cppverify::post(!cppverify::result || !bad(n)) { return n == 0; } -// CHECK-DAG: error: spec post by induction failed: bad [backend=z3] [reason=counterexample] (derivation.height [type=math-i64] = {{[0-9]+}}, n [type=math-i32] = 0) +// CHECK-DAG: error: spec post by induction failed: bad [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}derivation.height = {{[0-9]+}}{{( ->|,|$)}}{{.*}}n = 0{{( ->|,|$)}} // CHECK-DAG: Unresolved: spec post: bad [reason=spec.post] cppverify::proof void contradiction() cppverify::post(false) { cppverify::check(bad(0)); } diff --git a/clang/test/Verify/suite/inductive_predicates.cpp b/clang/test/Verify/suite/inductive_predicates.cpp index c4d838c4c1..6d8519754d 100644 --- a/clang/test/Verify/suite/inductive_predicates.cpp +++ b/clang/test/Verify/suite/inductive_predicates.cpp @@ -110,7 +110,8 @@ cppverify::spec bool odd(int n) { return n == 1 || odd(n - 2); } -// CHECK-DAG: error: spec post by induction failed: odd [backend=z3] [reason=counterexample] (derivation.height [type=math-i64] = {{[0-9]+}}, n [type=math-i32] = {{[0-9]+}}) +// CHECK-DAG: error: spec post by induction failed: odd [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}derivation.height = {{[0-9]+}}{{( ->|,|$)}}{{.*}}n = {{[0-9]+}}{{( ->|,|$)}} // CHECK-DAG: Unresolved: spec post: odd [reason=spec.post] (relies on the postcondition of odd.step, which is not established) // REJECT-DAG: error: negated: negated occurs in its body under negation; an inductive predicate occurs in its body only positively diff --git a/clang/test/Verify/suite/inductive_rules.cpp b/clang/test/Verify/suite/inductive_rules.cpp index d70a2edff8..f2cb60fabd 100644 --- a/clang/test/Verify/suite/inductive_rules.cpp +++ b/clang/test/Verify/suite/inductive_rules.cpp @@ -122,4 +122,4 @@ void not_empty(node *a) // 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"} +// UNPROVED-JSON: {"function_source":{{.*}},"message":"inductive predicate: reach [reason=spec.inductive] {{.*}}","reason":"spec.inductive","schema":"cppverify.diagnostic/2","severity":"error","status":"unresolved"} diff --git a/clang/test/Verify/suite/inductive_unfolding.cpp b/clang/test/Verify/suite/inductive_unfolding.cpp index eb747e45fe..71e4addae2 100644 --- a/clang/test/Verify/suite/inductive_unfolding.cpp +++ b/clang/test/Verify/suite/inductive_unfolding.cpp @@ -110,4 +110,5 @@ cppverify::spec bool path(int a, int b) { return a == b || cppverify::exists(c, edge(a, c) && path(c, b)); } -// CHECK-DAG: error: spec post by induction failed: path {{.*}}[reason=counterexample] ({{.*}}derivation.height [type=math-i64] = 1) +// CHECK-DAG: error: spec post by induction failed: path {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}derivation.height = 1{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/integer_encoding.cpp b/clang/test/Verify/suite/integer_encoding.cpp index 8162231413..41b8753252 100644 --- a/clang/test/Verify/suite/integer_encoding.cpp +++ b/clang/test/Verify/suite/integer_encoding.cpp @@ -84,6 +84,6 @@ int bad_negative_shift(int x) // DECIDED-DAG: Verified: shift_by_signed // UNDECIDED-DAG: {{Verified|Unresolved}}: shift_by_signed // VERIFY-DAG: error: verification failed: bad_wrap -// MODEL-DAG: x [ssa=x_0] [type=u32] = 4294967295; +// MODEL-DAG: note: counterexample: {{.*}}x = 4294967295{{( ->|,|$)}} // VERIFY-DAG: error: verification failed: bad_negative_shift -// MODEL-DAG: x [ssa=x_0] [type=i32] = 1; +// MODEL-DAG: note: counterexample: {{.*}}x = 1{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/interrupt.cpp b/clang/test/Verify/suite/interrupt.cpp index 714a333f05..b7ebc8da63 100644 --- a/clang/test/Verify/suite/interrupt.cpp +++ b/clang/test/Verify/suite/interrupt.cpp @@ -66,7 +66,7 @@ cppverify::spec int cube(int x) { return x * x * x; } // CHECK-NEXT: Summary: {{.*}} // CHECK-NEXT: exit: 130 -// JSON: "function":"cubes","message":"cubes","reason":"interrupted","schema":"cppverify.diagnostic/1","severity":"error","source":{{.*}}"status":"unresolved" +// JSON: "function":"cubes",{{.*}}"message":"cubes","reason":"interrupted","schema":"cppverify.diagnostic/2","severity":"error","source":{{.*}}"status":"unresolved" // JSON: exit: 130 // Replay stops the same way. diff --git a/clang/test/Verify/suite/lean_automation.test b/clang/test/Verify/suite/lean_automation.test index cb906ffbd1..1ae7aeb4ef 100644 --- a/clang/test/Verify/suite/lean_automation.test +++ b/clang/test/Verify/suite/lean_automation.test @@ -74,7 +74,7 @@ # SLOW: automation: cppverify_auto 3 did not prove it within 3 s # SLOW: {{^}} sorry{{$}} -# JSON: "function":"next","lean_automatic":3,{{.*}}"status":"exported" +# JSON: "function":"next",{{.*}}"lean_automatic":3,{{.*}}"status":"exported" # CERTIFY: Unresolved: lean obligation: a_slow (Lean certification failed: the proof of {{.*}}::postcondition@2:37: {{.*}}declaration uses `sorry` # CERTIFY-NEXT: Certified: next [backend=Lean] diff --git a/clang/test/Verify/suite/lean_refutation.test b/clang/test/Verify/suite/lean_refutation.test index 9d98556ddd..d668d941d4 100644 --- a/clang/test/Verify/suite/lean_refutation.test +++ b/clang/test/Verify/suite/lean_refutation.test @@ -65,15 +65,15 @@ # A contract, a bounded quantifier, a store to memory, and a spec Lean # defines; the choice is opaque in Lean. # CERTIFY: error: verification failed: add_one {{.*}}[reason=counterexample] (confirmed in Lean) -# CERTIFY-NEXT: error: verification failed: below_three {{.*}}[reason=counterexample] (confirmed in Lean) -# CERTIFY-NEXT: error: verification failed: zero_first {{.*}}[reason=counterexample] (confirmed in Lean) -# CERTIFY-NEXT: Verified: spec decreases: tri +# CERTIFY: error: verification failed: below_three {{.*}}[reason=counterexample] (confirmed in Lean) +# CERTIFY: error: verification failed: zero_first {{.*}}[reason=counterexample] (confirmed in Lean) +# CERTIFY: Verified: spec decreases: tri # CERTIFY-NEXT: error: verification failed: tri_three {{.*}}[reason=counterexample] (confirmed in Lean) -# CERTIFY-NEXT: error: verification failed: pick_one {{.*}}[reason=counterexample]{{$}} -# CERTIFY-NEXT: warning: the counterexample of pick_one is not confirmed in Lean: Lean cannot decide the goal at the counterexample +# CERTIFY: error: verification failed: pick_one {{.*}}[reason=counterexample]{{$}} +# CERTIFY: warning: the counterexample of pick_one is not confirmed in Lean: Lean cannot decide the goal at the counterexample -# JSON: {"backend":"z3","function":"add_one","lean_refuted":true, -# JSON: {"message":"the counterexample of pick_one is not confirmed in Lean: Lean cannot decide the goal at the counterexample","schema":"cppverify.diagnostic/1","severity":"warning","status":"warning"} +# JSON: {"backend":"z3",{{.*}}"function":"add_one",{{.*}}"lean_refuted":true, +# JSON: {"message":"the counterexample of pick_one is not confirmed in Lean: Lean cannot decide the goal at the counterexample","schema":"cppverify.diagnostic/2","severity":"warning","status":"warning"} # CHECKER: fail cppverify_fake_000000_goal_refuted cppverify_fake_000000_goal_refuted proves True, not Not cppverify_fake_000000_goal diff --git a/clang/test/Verify/suite/lean_replay.test b/clang/test/Verify/suite/lean_replay.test index 6b3b9484c1..fe2c4ba48f 100644 --- a/clang/test/Verify/suite/lean_replay.test +++ b/clang/test/Verify/suite/lean_replay.test @@ -90,7 +90,7 @@ # STALE: warning: the Lean proof CppVerify/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean is kept in Stale/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean: its function is gone, and no goal has its statement # STALE-NEXT: Exported: lean obligation: sum_small (cppverify_auto proved 7 of 7 goals) [backend=lean, bound=2] -# BACK: {"message":"the Lean proof of fn_5f5a31307472695f6e6f6e6e656769::postcondition@{{[0-9]+}}:{{[0-9]+}} came back from Stale/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean to CppVerify/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean, the file of the goal with its statement","schema":"cppverify.diagnostic/1","severity":"warning","status":"warning"} +# BACK: {"message":"the Lean proof of fn_5f5a31307472695f6e6f6e6e656769::postcondition@{{[0-9]+}}:{{[0-9]+}} came back from Stale/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean to CppVerify/Proofs/cppverify_tri_nonneg_42a030/cppverify_tri_nonneg_42a030_postcondition_053b1980.lean, the file of the goal with its statement","schema":"cppverify.diagnostic/2","severity":"warning","status":"warning"} # BACK-NEXT: {"backend":"lean","function":"tri.decreases",{{.*}}"status":"exported"} # NO-PROJECT: error: --lean-certify, --lean-prune-stale, and --no-lean-auto require --lean-project diff --git a/clang/test/Verify/suite/llvm_uleb128_mutation.cpp b/clang/test/Verify/suite/llvm_uleb128_mutation.cpp index 31a728126c..ec178bec8d 100644 --- a/clang/test/Verify/suite/llvm_uleb128_mutation.cpp +++ b/clang/test/Verify/suite/llvm_uleb128_mutation.cpp @@ -51,6 +51,6 @@ unsigned encode_128_missing_continuation(uint8_t *buffer) // CHECK: Verified: encode_128 [backend=bmc, bound=1] // CHECK: error: verification failed: encode_128_missing_continuation -// CHECK-SAME: buffer -// CHECK-SAME: value=0 +// CHECK: note: counterexample: buffer = +// CHECK: note: stores 0 at address diff --git a/clang/test/Verify/suite/llvm_uleb128_shift_ub.cpp b/clang/test/Verify/suite/llvm_uleb128_shift_ub.cpp index 2526733def..657a136ffb 100644 --- a/clang/test/Verify/suite/llvm_uleb128_shift_ub.cpp +++ b/clang/test/Verify/suite/llvm_uleb128_shift_ub.cpp @@ -74,6 +74,5 @@ uint64_t decode_overlong_upstream(const uint8_t *buffer) } // namespace cppverify_uleb128_shift // CHECK: Verified: decode_overlong_repaired -// CHECK: error: verification failed: decode_overlong_upstream -// CHECK-SAME: shift -// CHECK-SAME: = 70 +// CHECK: error: verification failed: decode_overlong_upstream [shift] +// CHECK: note: counterexample: {{.*}}shift = {{.*}}70 diff --git a/clang/test/Verify/suite/local_names.cpp b/clang/test/Verify/suite/local_names.cpp index 8a62e0078d..b2eb7f8f01 100644 --- a/clang/test/Verify/suite/local_names.cpp +++ b/clang/test/Verify/suite/local_names.cpp @@ -80,7 +80,8 @@ int hidden_wrong(int x) } return y; } -// CHECK-DAG: error: verification failed: hidden_wrong [{{.*}}y [ssa=y#2_1] [type=i32] = 5, y [ssa=y#2_2] [type=i32] = 6, y [ssa=y_1] [type=i32] = {{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: hidden_wrong [postcondition]{{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: x = {{[0-9]+}}, y (line [[@LINE-8]]) = {{[0-9]+}}, y (line [[@LINE-6]]) = 5 -> 6, result = {{[0-9]+}} // A block may hide a parameter; the contract reads the parameter. int parameter_hidden(int x) @@ -107,7 +108,7 @@ int parameter_hidden_wrong(int x) } return r; } -// CHECK-DAG: error: verification failed: parameter_hidden_wrong [{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: parameter_hidden_wrong [postcondition]{{.*}}[reason=counterexample] // Each declaration starts uninitialized, whatever an earlier one with its // name held. diff --git a/clang/test/Verify/suite/local_object_unsupported.cpp b/clang/test/Verify/suite/local_object_unsupported.cpp index bbddcb2a4a..d68ac939fb 100644 --- a/clang/test/Verify/suite/local_object_unsupported.cpp +++ b/clang/test/Verify/suite/local_object_unsupported.cpp @@ -193,7 +193,7 @@ int flattened_nested_record() // VERIFY-DAG: error: unsupported_copy_source: unsupported aggregate initializer for an automatic object // VERIFY-DAG: error: return_local_address: raw address-of is unsupported until lexical lifetime and escape effects are modeled // VERIFY-DAG: error: return_local_array: array-to-pointer decay of automatic storage is unsupported until lexical lifetime and escape effects are modeled -// FAILS-DAG: error: verification failed: dereference_escaped_local [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// FAILS-DAG: error: verification failed: dereference_escaped_local [postcondition]{{.*}}[reason=counterexample] // VERIFY-DAG: error: raw_pointer_into_local_array: raw address-of is unsupported until lexical lifetime and escape effects are modeled // VERIFY-DAG: error: whole_object_pointer_argument: raw address-of is unsupported until lexical lifetime and escape effects are modeled // VERIFY-DAG: error: provenance_pointer_field: storing a provenance-bearing pointer in an automatic object is unsupported diff --git a/clang/test/Verify/suite/local_record_arguments.cpp b/clang/test/Verify/suite/local_record_arguments.cpp index 440e4be597..690b559bb9 100644 --- a/clang/test/Verify/suite/local_record_arguments.cpp +++ b/clang/test/Verify/suite/local_record_arguments.cpp @@ -133,7 +133,8 @@ int wrong() bump(c); return c.n; } -// CHECK-DAG: error: verification failed: wrong [{{.*}}::postcondition@[[@LINE-8]]:37] (counterexample: result [ssa=__result_1] [type=i32] = 1; +// CHECK-DAG: :[[@LINE-8]]:37: error: verification failed: wrong [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}result = 1{{( ->|,|$)}} // A member and an element of a local, and the address of a field. int member() @@ -368,7 +369,7 @@ int same_local() pair_bump(a, a); return a.n; } -// CHECK-DAG: error: verification failed: same_local [{{.*}}::aliasing@[[@LINE-3]]:3] +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: same_local [aliasing] // A callee that writes its record before reading it needs none of it // initialized: c.limit never is. One that may read it needs every field. diff --git a/clang/test/Verify/suite/lookup_tables.cpp b/clang/test/Verify/suite/lookup_tables.cpp index 07954ebb14..a292f1bfbe 100644 --- a/clang/test/Verify/suite/lookup_tables.cpp +++ b/clang/test/Verify/suite/lookup_tables.cpp @@ -34,7 +34,8 @@ int popcount8_wrong(uint8_t x) { return NibbleBits[x & 15] + NibbleBits[x >> 4]; } -// CHECK-DAG: error: verification failed: popcount8_wrong [{{.*}}x [ssa=x_0] [type=u8] = 255{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: popcount8_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 255{{( ->|,|$)}} // An index outside the table is undefined. int nibble_bits(int x) @@ -42,7 +43,7 @@ int nibble_bits(int x) { return NibbleBits[x]; } -// CHECK-DAG: error: verification failed: nibble_bits [{{.*}}::bounds@[[@LINE-2]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: nibble_bits [bounds]{{.*}}[reason=counterexample] int nibble_bits_inside(int x) cv::pre(0 <= x && x < 16) @@ -58,7 +59,8 @@ int nibble_bits_before(int x) { return NibbleBits[x - 1]; } -// CHECK-DAG: error: verification failed: nibble_bits_before [{{.*}}::bounds@[[@LINE-2]]:{{.*}}x [ssa=x_0] [type=i32] = 0{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: nibble_bits_before [bounds] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 0{{( ->|,|$)}} // A table declared in a function, two-dimensional, with elements its // initializer leaves zero. @@ -78,7 +80,8 @@ int add_mod3_wrong(int a, int b) 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] +// CHECK-DAG: error: verification failed: add_mod3_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}a = 2{{( ->|,|$)}}{{.*}}b = {{1|2}}{{( ->|,|$)}} int row_outside(int a) cv::pre(0 <= a && a < 3) @@ -87,7 +90,7 @@ int row_outside(int a) 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] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: row_outside [bounds]{{.*}}[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}}; @@ -106,7 +109,8 @@ int row_major_wrong(int r, int c) { return Rows[r][c]; } -// CHECK-DAG: error: verification failed: row_major_wrong [{{.*}}r [ssa=r_0] [type=i32] = 1{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: row_major_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}r = 1{{( ->|,|$)}} constexpr int Primes[8] = {2, 3, 5, 7, 11}; @@ -133,7 +137,8 @@ char hex_digit_wrong(int d) { return "0123456789abcdef"[d + 1]; } -// CHECK-DAG: error: verification failed: hex_digit_wrong [{{.*}}::bounds@[[@LINE-2]]:{{.*}}d [ssa=d_0] [type=i32] = 16{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: hex_digit_wrong [bounds] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}d = 16{{( ->|,|$)}} // 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. @@ -195,7 +200,8 @@ bool upper_at_wrong(int c) { return Ascii::IsUpper[c]; } -// CHECK-DAG: error: verification failed: upper_at_wrong [{{.*}}c [ssa=c_0] [type=i32] = 2{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: upper_at_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}c = 2{{( ->|,|$)}} // Contracts and ghost code read tables too. int first_prime() diff --git a/clang/test/Verify/suite/loop_forms.cpp b/clang/test/Verify/suite/loop_forms.cpp index 7e9205b72e..bc801fab79 100644 --- a/clang/test/Verify/suite/loop_forms.cpp +++ b/clang/test/Verify/suite/loop_forms.cpp @@ -45,7 +45,8 @@ int stop_at_three_wrong(int n) cv::decreases(n - i); return i; } -// CHECK-DAG: error: verification failed: stop_at_three_wrong [{{.*}}n [ssa=n_0] [type=i32] = {{[4-9]|[1-9][0-9]+}}{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: stop_at_three_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[4-9]|[1-9][0-9]+}}{{( ->|,|$)}} // A break in the first iteration skips the loop. int first_break(int x) @@ -74,7 +75,8 @@ int first_break_wrong(int x) } while (false); return r; } -// CHECK-DAG: error: verification failed: first_break_wrong [{{.*}}x [ssa=x_0] [type=i32] = {{[1-9][0-9]*}}{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: first_break_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{[1-9][0-9]*}}{{( ->|,|$)}} int count_odd(int n) cv::pre(1 <= n && n <= 100) @@ -360,7 +362,7 @@ int pairs_wrong(int n) } return j; } -// CHECK-DAG: error: verification failed: pairs_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: pairs_wrong [postcondition]{{.*}}[reason=counterexample] int ordered(int x) cv::pre(0 <= x && x < 100) diff --git a/clang/test/Verify/suite/loop_frames.cpp b/clang/test/Verify/suite/loop_frames.cpp index 301c7eda9e..a294ff983a 100644 --- a/clang/test/Verify/suite/loop_frames.cpp +++ b/clang/test/Verify/suite/loop_frames.cpp @@ -37,7 +37,7 @@ void writes_input(int *a, int *b, int n) b[0] = i; } } -// CHECK-DAG: error: verification failed: writes_input [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: writes_input [postcondition] // The loop writes only a[0..n), so a[n] keeps its value. void keeps_tail(int *a, int n) @@ -68,7 +68,7 @@ void loses_tail(int *a, int n) a[i] = 0; } } -// CHECK-DAG: error: verification failed: loses_tail [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: loses_tail [postcondition] // Read in each iteration, a[0 : i] is the prefix written so far. void untouched_suffix(int *a, int n) @@ -100,7 +100,7 @@ void writes_ahead(int *a, int n) a[i + 1] = 0; } } -// CHECK-DAG: error: verification failed: writes_ahead [{{.*}}::frame@[[@LINE-6]]:25] +// CHECK-DAG: :[[@LINE-6]]:25: error: verification failed: writes_ahead [frame] // A continue ends the iteration, so the frame is checked there too. void writes_then_continues(int *a, int *b, int n) @@ -119,7 +119,7 @@ void writes_then_continues(int *a, int *b, int n) a[i] = 0; } } -// CHECK-DAG: error: verification failed: writes_then_continues [{{.*}}::frame@[[@LINE-10]]:25] +// CHECK-DAG: :[[@LINE-10]]:25: error: verification failed: writes_then_continues [frame] void set(int *x) cppverify::pre(x != nullptr) @@ -198,7 +198,7 @@ void counts_wrongly(int *count, int *a, int n) *a = i; } } -// CHECK-DAG: error: verification failed: counts_wrongly [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: counts_wrongly [postcondition] // A store's object is found from its address: a pointer walking through a // stays in a, so b keeps its value. @@ -249,7 +249,7 @@ void chosen_writes_b(int *a, int *b, int n, bool first) p[i] = 0; } } -// CHECK-DAG: error: verification failed: chosen_writes_b [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: chosen_writes_b [postcondition] // With aliases(a, b), a may be b. void aliased_writes(int *a, int *b, int n) @@ -265,7 +265,7 @@ void aliased_writes(int *a, int *b, int n) a[i] = 0; } } -// CHECK-DAG: error: verification failed: aliased_writes [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: aliased_writes [postcondition] // IR: fn untouched_suffix // IR: while diff --git a/clang/test/Verify/suite/loop_initialization.cpp b/clang/test/Verify/suite/loop_initialization.cpp index be551b7d30..77c1a32e67 100644 --- a/clang/test/Verify/suite/loop_initialization.cpp +++ b/clang/test/Verify/suite/loop_initialization.cpp @@ -77,7 +77,7 @@ int partially_filled() } return a[3]; } -// CHECK-DAG: error: verification failed: partially_filled [{{.*}}::initialization@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: partially_filled [initialization] // Nor is one it reads before writing. int read_first() @@ -93,7 +93,7 @@ int read_first() } return 0; } -// CHECK-DAG: error: verification failed: read_first [{{.*}}::initialization@[[@LINE-5]]:7] +// CHECK-DAG: :[[@LINE-5]]:7: error: verification failed: read_first [initialization] // Reading p[i] in an invariant bounds i at the head. int last(const int *p, int n) @@ -144,4 +144,4 @@ void overrun() i = i + 1; } } -// CHECK-DAG: error: verification failed: overrun [{{.*}}::dereference@[[@LINE-6]]:31#2] +// CHECK-DAG: :[[@LINE-6]]:31: error: verification failed: overrun [dereference] diff --git a/clang/test/Verify/suite/loop_local_frames.cpp b/clang/test/Verify/suite/loop_local_frames.cpp index 9e8c660749..30e9871817 100644 --- a/clang/test/Verify/suite/loop_local_frames.cpp +++ b/clang/test/Verify/suite/loop_local_frames.cpp @@ -184,7 +184,7 @@ int overwritten() } return a[3]; } -// CHECK-DAG: error: verification failed: overwritten [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: overwritten [postcondition] // Nor is a local written through a reference to it. int through_reference() @@ -202,4 +202,4 @@ int through_reference() } return x; } -// CHECK-DAG: error: verification failed: through_reference [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: through_reference [postcondition] diff --git a/clang/test/Verify/suite/loop_termination.cpp b/clang/test/Verify/suite/loop_termination.cpp index 7b8d1ea13b..153f9556c0 100644 --- a/clang/test/Verify/suite/loop_termination.cpp +++ b/clang/test/Verify/suite/loop_termination.cpp @@ -117,4 +117,4 @@ void refill(int &value, int n) if (n > 0) refill(value, n); } -// CHECK-DAG: error: verification failed: refill [{{.*}}::termination@118:5] +// CHECK-DAG: :118:5: error: verification failed: refill [termination] diff --git a/clang/test/Verify/suite/max_failures.cpp b/clang/test/Verify/suite/max_failures.cpp index ee169ceb8d..cabc2fcdc3 100644 --- a/clang/test/Verify/suite/max_failures.cpp +++ b/clang/test/Verify/suite/max_failures.cpp @@ -58,39 +58,44 @@ int counted(int n) return c; } -// ONE: max_failures.cpp:23:3: error: verification failed: three_bugs [{{.*}}::division-by-zero@23:3] (counterexample: {{.*}}y [ssa=y_0] [type=i32] = 0{{.*}}) [backend=z3] [reason=counterexample] -// ONE-NEXT: max_failures.cpp:30:23: error: verification failed: one_bug [{{.*}}::postcondition@30:23] -// ONE-NEXT: max_failures.cpp:40:3: error: verification failed: dependent [{{.*}}::division-by-zero@40:3] -// ONE-NEXT: max_failures.cpp:49:23: error: verification failed: counted [{{.*}}::postcondition@49:23] -// ONE-NEXT: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches +// ONE: max_failures.cpp:23:3: error: verification failed: three_bugs [division-by-zero] [backend=z3] [reason=counterexample] +// ONE: note: counterexample: {{.*}}y = 0{{( ->|,|$)}} +// ONE: max_failures.cpp:30:23: error: verification failed: one_bug [postcondition] +// ONE: max_failures.cpp:40:3: error: verification failed: dependent [division-by-zero] +// ONE: max_failures.cpp:49:23: error: verification failed: counted [postcondition] +// ONE: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches // ONE-NEXT: note: no execution fails this check: every loop ends within 3 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof // ONE-NEXT: Summary: 4 failed // The overflow's counterexample divides by y, which is not zero. -// TWO: max_failures.cpp:23:3: error: verification failed: three_bugs [{{.*}}::division-by-zero@23:3] -// TWO-NEXT: max_failures.cpp:24:3: error: verification failed: three_bugs [{{.*}}::overflow@24:3] (counterexample: {{.*}}y [ssa=y_0] [type=i32] = {{-?[1-9][0-9]*}}) [backend=z3] [reason=counterexample] -// TWO-NEXT: max_failures.cpp:30:23: error: verification failed: one_bug [{{.*}}::postcondition@30:23] -// TWO-NEXT: max_failures.cpp:40:3: error: verification failed: dependent [{{.*}}::division-by-zero@40:3] -// TWO-NEXT: max_failures.cpp:49:23: error: verification failed: counted [{{.*}}::postcondition@49:23] -// TWO-NEXT: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches +// TWO: max_failures.cpp:23:3: error: verification failed: three_bugs [division-by-zero] +// TWO: max_failures.cpp:24:3: error: verification failed: three_bugs [overflow] [backend=z3] [reason=counterexample] +// TWO: note: counterexample: {{.*}}y = {{-?[1-9][0-9]*}}{{( ->|,|$)}} +// TWO: max_failures.cpp:30:23: error: verification failed: one_bug [postcondition] +// TWO: max_failures.cpp:40:3: error: verification failed: dependent [division-by-zero] +// TWO: max_failures.cpp:49:23: error: verification failed: counted [postcondition] +// TWO: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches // TWO-NEXT: note: no execution fails this check: every loop ends within 3 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof -// TWO-NEXT: max_failures.cpp:50:23: error: verification failed: counted [{{.*}}::postcondition@50:23] -// TWO-NEXT: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches -// TWO-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n [ssa=n_0] [type=i32] = 0{{.*}}) +// TWO-NEXT: max_failures.cpp:50:23: error: verification failed: counted [postcondition] +// TWO: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches +// TWO-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n = 0{{.*}}) // TWO-NEXT: Summary: 4 failed -// ALL: max_failures.cpp:23:3: error: verification failed: three_bugs [{{.*}}::division-by-zero@23:3] -// ALL-NEXT: max_failures.cpp:24:3: error: verification failed: three_bugs [{{.*}}::overflow@24:3] (counterexample: {{.*}}y [ssa=y_0] [type=i32] = {{-?[1-9][0-9]*}}) -// ALL-NEXT: max_failures.cpp:20:23: error: verification failed: three_bugs [{{.*}}::postcondition@20:23] (counterexample: {{.*}}y [ssa=y_0] [type=i32] = {{-?[1-9][0-9]*}}; -// ALL-NEXT: max_failures.cpp:21:23: error: verification failed: three_bugs [{{.*}}::postcondition@21:23] (counterexample: {{.*}}y [ssa=y_0] [type=i32] = {{-?[1-9][0-9]*}}; -// ALL-NEXT: max_failures.cpp:30:23: error: verification failed: one_bug [{{.*}}::postcondition@30:23] -// ALL-NEXT: max_failures.cpp:40:3: error: verification failed: dependent [{{.*}}::division-by-zero@40:3] -// ALL-NEXT: max_failures.cpp:49:23: error: verification failed: counted [{{.*}}::postcondition@49:23] -// ALL-NEXT: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches +// ALL: max_failures.cpp:23:3: error: verification failed: three_bugs [division-by-zero] +// ALL: max_failures.cpp:24:3: error: verification failed: three_bugs [overflow] +// ALL: note: counterexample: {{.*}}y = {{-?[1-9][0-9]*}}{{( ->|,|$)}} +// ALL: max_failures.cpp:20:23: error: verification failed: three_bugs [postcondition] +// ALL: note: counterexample: {{.*}}y = {{-?[1-9][0-9]*}}{{( ->|,|$)}} +// ALL: max_failures.cpp:21:23: error: verification failed: three_bugs [postcondition] +// ALL: note: counterexample: {{.*}}y = {{-?[1-9][0-9]*}}{{( ->|,|$)}} +// ALL: max_failures.cpp:30:23: error: verification failed: one_bug [postcondition] +// ALL: max_failures.cpp:40:3: error: verification failed: dependent [division-by-zero] +// ALL: max_failures.cpp:49:23: error: verification failed: counted [postcondition] +// ALL: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches // ALL-NEXT: note: no execution fails this check: every loop ends within 3 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof -// ALL-NEXT: max_failures.cpp:50:23: error: verification failed: counted [{{.*}}::postcondition@50:23] -// ALL-NEXT: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches -// ALL-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n [ssa=n_0] [type=i32] = 0{{.*}}) +// ALL-NEXT: max_failures.cpp:50:23: error: verification failed: counted [postcondition] +// ALL: note: the counterexample's state at the loop at 53:3 is any state its invariants allow, not necessarily one an execution reaches +// ALL-NEXT: note: an execution fails this check with no iteration of any loop (counterexample: {{.*}}n = 0{{.*}}) // ALL-NEXT: Summary: 4 failed // JSON-COUNT-4: "function":"three_bugs" diff --git a/clang/test/Verify/suite/member_functions.cpp b/clang/test/Verify/suite/member_functions.cpp index e4587cdae9..d03bb2c18f 100644 --- a/clang/test/Verify/suite/member_functions.cpp +++ b/clang/test/Verify/suite/member_functions.cpp @@ -117,10 +117,11 @@ struct Counter { // CHECK-DAG: Verified: Counter::peek_counted [backend=z3] // CHECK-DAG: Verified: Counter::zero [backend=z3] -// CHECK-DAG: error: verification failed: Counter::bad [{{.*}}::frame@{{[0-9]+}}:11] (counterexample: this [ssa=this_0] [type=pointer] = +// CHECK-DAG: :{{[0-9]+}}:11: error: verification failed: Counter::bad [frame] +// CHECK-DAG: note: counterexample: {{.*}}this = // A const member function writes only its own storage, mutable fields // included, unless its modifies lists them. -// CHECK-DAG: error: verification failed: Counter::peek_bad [{{.*}}::frame@ +// CHECK-DAG: error: verification failed: Counter::peek_bad [frame] void Counter::set(int v) { @@ -206,7 +207,7 @@ void cleared_narrow(Counter &c) { c.reset(); } -// CHECK-DAG: error: verification failed: cleared_narrow [{{.*}}::frame@[[@LINE-2]]:5] +// CHECK-DAG: :[[@LINE-2]]:5: error: verification failed: cleared_narrow [frame] // `this` and a mutable or read parameter are distinct objects by default. void self_copy(Counter &c) @@ -214,7 +215,7 @@ void self_copy(Counter &c) { c.copy_from(c); } -// CHECK-DAG: error: verification failed: self_copy [{{.*}}::aliasing@[[@LINE-2]]:5] +// CHECK-DAG: :[[@LINE-2]]:5: error: verification failed: self_copy [aliasing] int self_plus(const Counter &c) cppverify::pre(-1000 < c.n && c.n < 1000) diff --git a/clang/test/Verify/suite/member_operators.cpp b/clang/test/Verify/suite/member_operators.cpp index f8251fcca9..a21e20ba44 100644 --- a/clang/test/Verify/suite/member_operators.cpp +++ b/clang/test/Verify/suite/member_operators.cpp @@ -119,7 +119,7 @@ int self_add() a += a; return a.cents; } -// CHECK-DAG: error: verification failed: self_add [{{.*}}::aliasing@[[@LINE-3]] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: self_add [aliasing] // The reference such an operator returns is no value yet. int chained() @@ -205,7 +205,7 @@ void spoil(Level &l) { l.value = 500; } -// CHECK-DAG: error: verification failed: spoil [{{.*}}::type-invariant@ +// CHECK-DAG: error: verification failed: spoil [type-invariant] int spoiled_argument() cppverify::post(true) @@ -215,7 +215,7 @@ int spoiled_argument() int v = read(l); return v; } -// CHECK-DAG: error: verification failed: spoiled_argument [{{.*}}::type-invariant@[[@LINE-3]] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: spoiled_argument [type-invariant] // A null pointer addresses no object, whose invariant would be due. int none() @@ -254,4 +254,4 @@ int first_through(const Span &s) int v = s.first(); return v; } -// CHECK-DAG: error: verification failed: first_through [{{.*}}::bounds@[[@LINE-3]] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: first_through [bounds] diff --git a/clang/test/Verify/suite/memory_objects.cpp b/clang/test/Verify/suite/memory_objects.cpp index 7d698da707..86485432a2 100644 --- a/clang/test/Verify/suite/memory_objects.cpp +++ b/clang/test/Verify/suite/memory_objects.cpp @@ -16,7 +16,7 @@ int beyond_single(const int *p) { return p[1]; } -// CHECK-DAG: error: verification failed: beyond_single [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: beyond_single [bounds] int through_copy(const int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n < 100) @@ -24,7 +24,7 @@ int through_copy(const int *p, int n) const int *q = p; return q[n]; } -// CHECK-DAG: error: verification failed: through_copy [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: through_copy [bounds] int through_selection(const int *p, const int *q, bool c) cppverify::pre(p != nullptr && q != nullptr) @@ -32,7 +32,7 @@ int through_selection(const int *p, const int *q, bool c) const int *r = c ? p : q; return r[1]; } -// CHECK-DAG: error: verification failed: through_selection [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: through_selection [bounds] int after_rebinding(const int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n < 100) @@ -40,7 +40,7 @@ int after_rebinding(const int *p, int n) p = p + n; return p[0]; } -// CHECK-DAG: error: verification failed: after_rebinding [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: after_rebinding [bounds] void loop_overrun(int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n < 100) @@ -53,14 +53,14 @@ void loop_overrun(int *p, int n) p[i] = 0; } } -// CHECK-DAG: error: verification failed: loop_overrun [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: loop_overrun [bounds] int empty_extent(const int *p, int n) cppverify::pre(valid(p, n) && n == 0 && p != nullptr) { return p[0]; } -// CHECK-DAG: error: verification failed: empty_extent [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: empty_extent [bounds] bool step_too_far(const int *p) cppverify::pre(valid(p, 2)) @@ -68,7 +68,7 @@ bool step_too_far(const int *p) const int *q = p + 3; return q == p; } -// CHECK-DAG: error: verification failed: step_too_far [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: step_too_far [bounds] bool one_past(const int *p) cppverify::pre(valid(p, 2)) @@ -134,7 +134,7 @@ int past_external(int value) int *cell = external_cell(value); return cell[1]; } -// CHECK-DAG: error: verification failed: past_external [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: past_external [bounds] struct Pair { int first; @@ -147,4 +147,4 @@ int past_object(const Pair *p, const Pair *q) { return (p + 1)->second; } -// CHECK-DAG: error: verification failed: past_object [{{.*}}::bounds@ +// CHECK-DAG: error: verification failed: past_object [bounds] diff --git a/clang/test/Verify/suite/mutual_recursion.cpp b/clang/test/Verify/suite/mutual_recursion.cpp index ff3fa2bcd1..6efc5bf2ee 100644 --- a/clang/test/Verify/suite/mutual_recursion.cpp +++ b/clang/test/Verify/suite/mutual_recursion.cpp @@ -78,5 +78,5 @@ int ping(int n) return 0; return pong(n - 1); } -// CHECK-DAG: error: verification failed: pong [{{.*}}::termination@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: pong [termination]{{.*}}[reason=counterexample] // CHECK-DAG: Unresolved: ping {{.*}}[reason=callee.contract] (relies on the contract of pong, which is not established) diff --git a/clang/test/Verify/suite/nested_quantifier_counterexamples.cpp b/clang/test/Verify/suite/nested_quantifier_counterexamples.cpp index cec19c5b3d..a83eaf27e1 100644 --- a/clang/test/Verify/suite/nested_quantifier_counterexamples.cpp +++ b/clang/test/Verify/suite/nested_quantifier_counterexamples.cpp @@ -14,7 +14,8 @@ cppverify::proof void every_has_larger(seq s) cppverify::check(cppverify::forall(i, !(0 <= i && i < s.len()) || cppverify::exists(j, 0 <= j && j < s.len() && s[j] > s[i]))); } -// CHECK-DAG: error: verification failed: every_has_larger [{{.*}}::assertion@[[@LINE-3]]:3] (counterexample: s [ssa=s_0] [type=seq] = [{{.*}}]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: every_has_larger [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [{{.*}}] cppverify::proof void every_has_no_smaller(seq s) cppverify::pre(s.len() >= 1 && s.len() <= 3) @@ -31,7 +32,7 @@ int max_index(const int *a, int n) { return 0; } -// CHECK-DAG: error: verification failed: max_index [{{.*}}::postcondition@{{.*}}] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: max_index [postcondition] [backend=z3] [reason=counterexample] // Distinct elements repeat nowhere. cppverify::proof void some_repeated(seq s) @@ -41,7 +42,8 @@ cppverify::proof void some_repeated(seq s) cppverify::exists(j, 0 <= j && j < s.len() && i != j && s[i] == s[j]))); } -// CHECK-DAG: error: verification failed: some_repeated [{{.*}}::assertion@[[@LINE-4]]:3] (counterexample: s [ssa=s_0] [type=seq] = [{{.*}}]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-4]]:3: error: verification failed: some_repeated [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [{{.*}}] // A product of bound variables is outside Presburger arithmetic, but a // witness decides the claim: no j makes 0 * j == 6. @@ -50,7 +52,8 @@ cppverify::proof void not_all_products_even(int n) { cppverify::check(cppverify::forall(i, cppverify::exists(j, i * j == 2 * n))); } -// CHECK-DAG: error: verification failed: not_all_products_even [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: n [ssa=n_0] [type=i32] = 3) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: not_all_products_even [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: n = 3 // Refuting this needs every value of i: no witness can, and i * j is no // linear term. diff --git a/clang/test/Verify/suite/obligation_archive.test b/clang/test/Verify/suite/obligation_archive.test index 61e78e97d0..ae6860096b 100644 --- a/clang/test/Verify/suite/obligation_archive.test +++ b/clang/test/Verify/suite/obligation_archive.test @@ -15,7 +15,7 @@ # RUN: %python %S/Inputs/mutate_obligation_archive.py truncated %t.one %t.truncated # RUN: not %cpp-verify --obligation-in=%t.truncated 2>&1 | FileCheck %s --check-prefix=TRUNCATED # RUN: cp %t.one %t.version -# RUN: printf '\004\000\000\000' | dd of=%t.version bs=1 seek=8 conv=notrunc 2>/dev/null +# RUN: printf '\006\000\000\000' | dd of=%t.version bs=1 seek=8 conv=notrunc 2>/dev/null # RUN: not %cpp-verify --obligation-in=%t.version 2>&1 | FileCheck %s --check-prefix=VERSION # RUN: %python %S/Inputs/mutate_obligation_archive.py schema-v1 %t.one %t.schema-v1 # RUN: not %cpp-verify --obligation-in=%t.schema-v1 2>&1 | FileCheck %s --check-prefix=SCHEMA-V1 @@ -23,6 +23,14 @@ # RUN: %cpp-verify --obligation-in=%t.schema-v1-kinds --backend=z3 | FileCheck %s --check-prefix=SCHEMA-V1-KINDS # RUN: %python %S/Inputs/mutate_obligation_archive.py schema-v2 %t.one %t.schema-v2 # RUN: %cpp-verify --obligation-in=%t.schema-v2 --backend=z3 | FileCheck %s --check-prefix=SCHEMA-V2 +# RUN: %python %S/Inputs/mutate_obligation_archive.py schema-v3 %t.one %t.schema-v3 +# RUN: %cpp-verify --obligation-in=%t.schema-v3 --backend=z3 | FileCheck %s --check-prefix=SCHEMA-V3 +# RUN: %python %S/Inputs/mutate_obligation_archive.py schema-v4 %t.one %t.schema-v4 +# RUN: %cpp-verify --obligation-in=%t.schema-v4 --backend=z3 | FileCheck %s --check-prefix=SCHEMA-V3 +# RUN: %python %S/Inputs/mutate_obligation_archive.py invalid-source-begin %t.one %t.source-begin +# RUN: not %cpp-verify --obligation-in=%t.source-begin 2>&1 | FileCheck %s --check-prefix=SOURCE-BEGIN +# RUN: %python %S/Inputs/mutate_obligation_archive.py invalid-guard-flag %t.one %t.guard-flag +# RUN: not %cpp-verify --obligation-in=%t.guard-flag 2>&1 | FileCheck %s --check-prefix=GUARD-FLAG # A measure is read like a definition, over the function's parameters. # RUN: %cpp-verify --lower-only --obligation-out=%t.measures %S/Inputs/lean_replay.cpp -- # RUN: %python %S/Inputs/mutate_obligation_archive.py measure-free-variable %t.measures %t.measure-variable @@ -99,7 +107,7 @@ # REPLAY: Proved obligations: canonical_obligations (dependencies not checked in replay) [backend=z3] [semantic-hash=sha256:{{[0-9a-f]+}}] # LOWER: vc canonical_obligations -# LOWER: schema cppverify.obligation/3 +# LOWER: schema cppverify.obligation/5 # LOWER: (let # LOWER: Lowered: canonical_obligations [backend=z3] [semantic-hash=sha256:{{[0-9a-f]+}}] # LEAN: Exported: lean obligation: canonical_obligations [backend=lean] @@ -111,6 +119,9 @@ # SCHEMA-V1: error: invalid obligation archive: invalid obligation kind in obligation archive # SCHEMA-V1-KINDS: Proved obligations: canonical_obligations (dependencies not checked in replay) [backend=z3] # SCHEMA-V2: Proved obligations: canonical_obligations (dependencies not checked in replay) [backend=z3] +# SCHEMA-V3: Proved obligations: canonical_obligations (dependencies not checked in replay) [backend=z3] +# GUARD-FLAG: error: invalid obligation archive: invalid diagnostic variable guard in obligation archive +# SOURCE-BEGIN: error: invalid obligation archive: source range begins after its anchor # MEASURE: error: invalid obligation archive: logical function definition has a free variable: z # KIND: error: invalid obligation archive: invalid expression kind in obligation archive # FEATURES: error: invalid obligation archive: obligation archive feature declaration does not match contents diff --git a/clang/test/Verify/suite/obligation_ir.cpp b/clang/test/Verify/suite/obligation_ir.cpp index 275fc316d6..936e594f82 100644 --- a/clang/test/Verify/suite/obligation_ir.cpp +++ b/clang/test/Verify/suite/obligation_ir.cpp @@ -28,7 +28,7 @@ int canonical_obligations(int x) } // OBLIGATION-LABEL: vc canonical_obligations -// OBLIGATION-NEXT: schema cppverify.obligation/3 +// OBLIGATION-NEXT: schema cppverify.obligation/5 // OBLIGATION-NEXT: simplification nodes {{[1-9][0-9]*}} -> {{[1-9][0-9]*}}, rewrites {{[1-9][0-9]*}}, functions-removed 0 // OBLIGATION-NEXT: semantic-hash sha256:f6d09cd4e2ac5c541fa8e0e443f4c2981e5763ce9b4d94d69f9856eaa1e95f2e // OBLIGATION-NEXT: identity [[IDENTITY:fn_[0-9a-f]+]] diff --git a/clang/test/Verify/suite/obligation_size_limit.cpp b/clang/test/Verify/suite/obligation_size_limit.cpp index c3433e67df..ad7ebf4f63 100644 --- a/clang/test/Verify/suite/obligation_size_limit.cpp +++ b/clang/test/Verify/suite/obligation_size_limit.cpp @@ -58,7 +58,7 @@ int steps(int x) return y; } // LIMIT: obligation_size_limit.cpp:[[@LINE-22]]:5: Unresolved: steps [backend=z3] [reason=query.size-limit] (the proof obligations would take at least 747 expression nodes, more than the limit of 500 (--max-query-nodes): each check repeats every assumption before it, so split the function or raise the limit) -// JSON: {"backend":"z3","function":"steps","message":"steps [backend=z3] [reason=query.size-limit] (the proof obligations would take at least 747 expression nodes, more than the limit of 500 (--max-query-nodes): each check repeats every assumption before it, so split the function or raise the limit)","reason":"query.size-limit","schema":"cppverify.diagnostic/1","severity":"error","source":{"column":5,"end_column":5,"end_line":[[@LINE-23]],"file":"{{.*}}obligation_size_limit.cpp","line":[[@LINE-23]]},"status":"unresolved"} +// JSON: {"backend":"z3","function":"steps","function_source":{"column":5,"end_column":10,"end_line":[[@LINE-23]],"file":"{{.*}}obligation_size_limit.cpp","line":[[@LINE-23]]},"message":"steps [backend=z3] [reason=query.size-limit] (the proof obligations would take at least 747 expression nodes, more than the limit of 500 (--max-query-nodes): each check repeats every assumption before it, so split the function or raise the limit)","reason":"query.size-limit","schema":"cppverify.diagnostic/2","severity":"error","source":{"column":5,"end_column":10,"end_line":[[@LINE-23]],"file":"{{.*}}obligation_size_limit.cpp","line":[[@LINE-23]]},"status":"unresolved"} // LOWER: obligation_size_limit.cpp:[[@LINE-24]]:5: error: Z3 lowering failed: steps [backend=z3] [reason=query.size-limit] (the proof obligations would take at least 747 expression nodes, more than the limit of 500 // AT: Verified: steps [backend=z3] // SHARED: Verified: steps [backend=z3] @@ -74,7 +74,7 @@ int caller(int x) return y + 1; } // LIMIT: Unresolved: caller [backend=z3] [reason=callee.contract] (relies on the contract of steps, which is not established) -// JSON: {"backend":"z3","function":"caller","message":"caller [backend=z3] [reason=callee.contract] (relies on the contract of steps, which is not established)","reason":"callee.contract", +// JSON: {"backend":"z3","function":"caller","function_source":{"column":5,{{.*}}},"message":"caller [backend=z3] [reason=callee.contract] (relies on the contract of steps, which is not established)","reason":"callee.contract", // LOWER: Lowered: caller // AT: Verified: caller [backend=z3] // BELOW: Unresolved: caller [backend=z3] [reason=callee.contract] diff --git a/clang/test/Verify/suite/old_loop_invariant_definedness_fail.cpp b/clang/test/Verify/suite/old_loop_invariant_definedness_fail.cpp index 0643639d8c..7d46cf3ff5 100644 --- a/clang/test/Verify/suite/old_loop_invariant_definedness_fail.cpp +++ b/clang/test/Verify/suite/old_loop_invariant_definedness_fail.cpp @@ -34,7 +34,7 @@ int reject_old_entry_null_dereference(int *pointer, int *replacement) return 0; } -// CHECK-DAG: error: verification failed: reject_old_entry_overflow [{{.*}}::overflow@ -// CHECK-DAG: error: verification failed: reject_quantified_old_entry_overflow [{{.*}}::overflow@ -// CHECK-DAG: error: verification failed: reject_old_entry_null_dereference [{{.*}}::dereference@ +// CHECK-DAG: error: verification failed: reject_old_entry_overflow [overflow] +// CHECK-DAG: error: verification failed: reject_quantified_old_entry_overflow [overflow] +// CHECK-DAG: error: verification failed: reject_old_entry_null_dereference [dereference] diff --git a/clang/test/Verify/suite/out_parameters.cpp b/clang/test/Verify/suite/out_parameters.cpp index a72b5e35d2..ccd0cd7358 100644 --- a/clang/test/Verify/suite/out_parameters.cpp +++ b/clang/test/Verify/suite/out_parameters.cpp @@ -130,7 +130,7 @@ int read_before_written() int v = r; return v; } -// CHECK: error: verification failed: read_before_written {{.*}}initialization@[[@LINE-3]]:3 +// CHECK: :[[@LINE-3]]:3: error: verification failed: read_before_written [initialization] int *fresh() cv::post(cv::result != nullptr) @@ -213,8 +213,8 @@ int reads_after_failed_get() try_get(a[0], false); return a[0]; } -// CHECK: error: verification failed: reads_after_failed_get {{.*}}initialization@[[@LINE-2]]:3 -// NOUB: error: verification failed: reads_after_failed_get {{.*}}initialization@[[@LINE-3]]:3 +// CHECK: :[[@LINE-2]]:3: error: verification failed: reads_after_failed_get [initialization] +// NOUB: :[[@LINE-3]]:3: error: verification failed: reads_after_failed_get [initialization] // Written, then read: still an out-parameter. void write_then_update(int &x) @@ -431,7 +431,7 @@ int reads_unmade() make_first(p); return p.b; } -// CHECK: error: verification failed: reads_unmade {{.*}}initialization@[[@LINE-2]]:3 +// CHECK: :[[@LINE-2]]:3: error: verification failed: reads_unmade [initialization] // A postcondition about storage the function never writes speaks of the // caller's values: an input, initialized at entry. @@ -452,8 +452,8 @@ void set_if(int &x, bool c) if (c) x = 1; } -// CHECK: error: verification failed: set_if {{.*}}initialization@[[@LINE-5]]:14 -// NOUB: error: verification failed: set_if {{.*}}initialization@[[@LINE-6]]:14 +// CHECK: :[[@LINE-5]]:14: error: verification failed: set_if [initialization] +// NOUB: :[[@LINE-6]]:14: error: verification failed: set_if [initialization] // A loop writes on no path known before it runs. void zero_each(int &x, int n) @@ -481,7 +481,7 @@ int maybe_zeroed() zero_each(a[0], 0); return a[0]; } -// CHECK: error: verification failed: maybe_zeroed {{.*}}initialization@[[@LINE-2]]:3 +// CHECK: :[[@LINE-2]]:3: error: verification failed: maybe_zeroed [initialization] // A call that may write a cell keeps it initialized. int kept_through_call(int n) @@ -585,7 +585,7 @@ int init_in_loop_only(int n) } return a[0]; } -// CHECK: error: verification failed: init_in_loop_only {{.*}}initialization@[[@LINE-2]]:3 +// CHECK: :[[@LINE-2]]:3: error: verification failed: init_in_loop_only [initialization] void zero_rec(int &x, int n) cv::pre(n >= 0) @@ -698,7 +698,7 @@ void fill_three(int *b) { fill(b, 3); } -// CHECK: error: verification failed: fill_three {{.*}}initialization@[[@LINE-4]]:12 +// CHECK: :[[@LINE-4]]:12: error: verification failed: fill_three [initialization] // A member function that writes its object. struct Point { diff --git a/clang/test/Verify/suite/parallel_functions.test b/clang/test/Verify/suite/parallel_functions.test index 2934017920..c9c49bd416 100644 --- a/clang/test/Verify/suite/parallel_functions.test +++ b/clang/test/Verify/suite/parallel_functions.test @@ -37,15 +37,15 @@ # CHECK: Verified: spec decreases: climb # CHECK-NEXT: error: spec decreases failed: diverge -# CHECK-NEXT: Verified: climb_zero [backend=z3] +# CHECK: Verified: climb_zero [backend=z3] # CHECK-NEXT: Unresolved: diverge_zero [backend=z3] [reason=spec.termination] # CHECK-NEXT: error: verification failed: off_by_one -# CHECK-NEXT: Unresolved: relies_on_off_by_one [backend=z3] [reason=callee.contract] +# CHECK: Unresolved: relies_on_off_by_one [backend=z3] [reason=callee.contract] # CHECK-NEXT: Verified: positive_identity [backend=z3] (its precondition is assumed, not checked, at calls from unverified unchecked_caller) # CHECK-NEXT: Verified: sum_to [backend=z3] # CHECK-NEXT: Verified: clear [backend=z3] -# CHECK-NEXT: error: verification failed: overflow [{{.*}}::overflow@ -# CHECK-NEXT: error: verification failed: two_failures [{{.*}}::assertion@72:3] -# CHECK-NEXT: Verified: both_ways [backend=z3] +# CHECK-NEXT: error: verification failed: overflow [overflow] +# CHECK: :72:3: error: verification failed: two_failures [assertion] +# CHECK: Verified: both_ways [backend=z3] # BUDGET: Unresolved: sum_to [backend=z3] [reason=solver.timeout] diff --git a/clang/test/Verify/suite/per_function_errors.test b/clang/test/Verify/suite/per_function_errors.test index 65e02a63e7..6f42beac5b 100644 --- a/clang/test/Verify/suite/per_function_errors.test +++ b/clang/test/Verify/suite/per_function_errors.test @@ -34,9 +34,9 @@ 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"} -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: {"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"} JSON: {"backend":"z3","function":"inc",{{.*}}"status":"verified"} diff --git a/clang/test/Verify/suite/pointer_difference_null.cpp b/clang/test/Verify/suite/pointer_difference_null.cpp index 7ccb9c8e7d..df76fec608 100644 --- a/clang/test/Verify/suite/pointer_difference_null.cpp +++ b/clang/test/Verify/suite/pointer_difference_null.cpp @@ -47,7 +47,7 @@ long null_minus_object(const int *a, int n) const int *z = nullptr; return a - z; } -// CHECK-DAG: error: verification failed: null_minus_object [{{.*}}::pointer-difference@{{[0-9]+:[0-9]+}}] {{.*}}[reason=counterexample] +// CHECK-DAG: :{{[0-9]+:[0-9]+}}: error: verification failed: null_minus_object [pointer-difference] {{.*}}[reason=counterexample] // Either may be null while the other is not. long one_null(const int *a, const int *b) @@ -55,4 +55,4 @@ long one_null(const int *a, const int *b) { return a - b; } -// CHECK-DAG: error: verification failed: one_null [{{.*}}::pointer-difference@{{[0-9]+:[0-9]+}}] {{.*}}[reason=counterexample] +// CHECK-DAG: :{{[0-9]+:[0-9]+}}: error: verification failed: one_null [pointer-difference] {{.*}}[reason=counterexample] diff --git a/clang/test/Verify/suite/pointer_field_extents.cpp b/clang/test/Verify/suite/pointer_field_extents.cpp index eae92770c9..8c4cdfe4fb 100644 --- a/clang/test/Verify/suite/pointer_field_extents.cpp +++ b/clang/test/Verify/suite/pointer_field_extents.cpp @@ -72,7 +72,8 @@ int past(const View *v) { return v->data[v->len]; } -// CHECK-DAG: error: verification failed: past [{{.*}}::bounds@[[@LINE-2]]:10] (counterexample: v->data [ssa=v->data_0] [type=pointer] = +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: past [bounds] +// CHECK-DAG: note: counterexample: {{.*}}v->data = // Without an extent the field's buffer is no object of the function's. void unknown(Buf &b) @@ -81,7 +82,7 @@ void unknown(Buf &b) { b.data[0] = 1; } -// CHECK-DAG: error: verification failed: unknown [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: unknown [bounds] // The buffer is disjoint from its record, so the length keeps its value. int keeps_len(Buf &b) @@ -147,8 +148,8 @@ void too_long(int *p, int n) b.len = n + 1; zero(b); } -// CHECK-DAG: error: verification failed: too_long [{{.*}}::bounds@[[@LINE-2]]:3] -// BMC-DAG: error: verification failed: too_long [{{.*}}::bounds@[[@LINE-3]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: too_long [bounds] +// BMC-DAG: :[[@LINE-3]]:3: error: verification failed: too_long [bounds] // The extent is the field's at entry: a field given another pointer // addresses that pointer's object. @@ -170,7 +171,7 @@ void reseat_past(Buf &b, int *other) b.data = other; b.data[1] = 1; } -// CHECK-DAG: error: verification failed: reseat_past [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: reseat_past [bounds] // Two mutable buffers are disjoint by default, as two mutable parameters // are; two read-only ones may be one. @@ -198,7 +199,8 @@ void self_copy(Buf &b) { copy(b, b); } -// CHECK-DAG: error: verification failed: self_copy [{{.*}}::aliasing@[[@LINE-2]]:3] (counterexample: b.data [ssa=b->data_0] [type=pointer] = +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: self_copy [aliasing] +// CHECK-DAG: note: counterexample: {{.*}}b.data = int first_of_both(const View &a, const View &b) cppverify::pre(cppverify::valid(a.data, a.len) && cppverify::valid(b.data, b.len)) @@ -243,7 +245,7 @@ void nonempty_inside(Buf *b) { touch(b, &b->len, 1); } -// CHECK-DAG: error: verification failed: nonempty_inside [{{.*}}::aliasing@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: nonempty_inside [aliasing] // Not yet: a field of an element (s->items[k].data). struct Shelf { diff --git a/clang/test/Verify/suite/pointer_origins.cpp b/clang/test/Verify/suite/pointer_origins.cpp index 32a1dd9d9e..74e19f2384 100644 --- a/clang/test/Verify/suite/pointer_origins.cpp +++ b/clang/test/Verify/suite/pointer_origins.cpp @@ -114,7 +114,7 @@ void clear_chosen_loose(int *a, int *b, int n, bool s) q = q + 1; } } -// CHECK-DAG: error: verification failed: clear_chosen_loose [{{.*}}::bounds@[[@LINE-4]]:6] +// CHECK-DAG: :[[@LINE-4]]:6: error: verification failed: clear_chosen_loose [bounds] long choice_diff(int *a, int *b, bool first) cppverify::pre(a != nullptr && b != nullptr && first) @@ -140,7 +140,7 @@ void param_past(int *a) a = a + 2; a = a + 1; } -// CHECK-DAG: error: verification failed: param_past [{{.*}}::bounds@[[@LINE-2]]:9] +// CHECK-DAG: :[[@LINE-2]]:9: error: verification failed: param_past [bounds] // One past a single object, even where another object may start. void adjacent(int *a, int *b) @@ -150,7 +150,7 @@ void adjacent(int *a, int *b) int *q = a + 1; *q = 0; } -// CHECK-DAG: error: verification failed: adjacent [{{.*}}::bounds@[[@LINE-2]]:4] +// CHECK-DAG: :[[@LINE-2]]:4: error: verification failed: adjacent [bounds] // The walker does change its own object. void walk_changes(int *a, int n) @@ -167,7 +167,7 @@ void walk_changes(int *a, int n) q = q + 1; } } -// CHECK-DAG: error: verification failed: walk_changes [{{.*}}::postcondition@{{.*}}] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: walk_changes [postcondition] [backend=z3] [reason=counterexample] // A chosen object may be either. void chosen_changes(int *a, int *b, bool first) @@ -183,7 +183,8 @@ void chosen_changes(int *a, int *b, bool first) *p = 5; } } -// CHECK-DAG: error: verification failed: chosen_changes [{{.*}}::postcondition@{{.*}}] (counterexample: {{.*}}first [ssa=first_0] [type=bool] = true{{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: chosen_changes [postcondition] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}first = true{{.*}} // The companion naming q's origin, and the generated invariants. // VCR-LABEL: fn clear_chosen diff --git a/clang/test/Verify/suite/pointer_steps.cpp b/clang/test/Verify/suite/pointer_steps.cpp index 46d27b0b4d..f5318f2618 100644 --- a/clang/test/Verify/suite/pointer_steps.cpp +++ b/clang/test/Verify/suite/pointer_steps.cpp @@ -72,7 +72,8 @@ int too_far(const int *a, int n) q -= 2; return *q; } -// CHECK-DAG: error: verification failed: too_far [{{.*}}@[[@LINE-4]]:{{.*}}n [ssa=n_0] [type=i32] = 1{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-4]]:{{[0-9]+}}: error: verification failed: too_far [{{[a-z-]+}}]{{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = 1{{( ->|,|$)}} int before_start(const int *a, int n) cv::pre(cv::valid(a, n) && 1 <= n && n <= 100) @@ -83,7 +84,7 @@ int before_start(const int *a, int n) ++q; return *q; } -// CHECK-DAG: error: verification failed: before_start [{{.*}}@[[@LINE-4]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-4]]:{{[0-9]+}}: error: verification failed: before_start [{{[a-z-]+}}]{{.*}}[reason=counterexample] // Stepping a null pointer is undefined. int *step_null(int *p) @@ -92,7 +93,8 @@ int *step_null(int *p) p += 1; return p; } -// CHECK-DAG: error: verification failed: step_null [{{.*}}::bounds@[[@LINE-3]]:{{.*}}p [ssa=p_0] [type=pointer] = 0{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: step_null [bounds] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}p = 0{{( ->|,|$)}} // A parameter may be stepped too; contracts read its entry value. void zero(int *p, int n) @@ -192,7 +194,7 @@ int ring_past(const Ring *r) ++q; return 0; } -// CHECK-DAG: error: verification failed: ring_past [{{.*}}@[[@LINE-3]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: ring_past [{{[a-z-]+}}]{{.*}}[reason=counterexample] // Unsupported: stepping a pointer to dynamic storage, and the value of a // step in an expression. diff --git a/clang/test/Verify/suite/pointer_subobjects.cpp b/clang/test/Verify/suite/pointer_subobjects.cpp index 40b778c243..492d1051bb 100644 --- a/clang/test/Verify/suite/pointer_subobjects.cpp +++ b/clang/test/Verify/suite/pointer_subobjects.cpp @@ -206,14 +206,16 @@ int past_end(const Outer *p, int i) { return p->buf[i]; } -// CHECK-DAG: error: verification failed: past_end [{{.*}}::bounds@[[@LINE-2]]:3] (counterexample: i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: past_end [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} int below_zero(const Outer *p, int i) cppverify::pre(p != nullptr && -1 <= i && i < 8) { return p->buf[i]; } -// CHECK-DAG: error: verification failed: below_zero [{{.*}}::bounds@[[@LINE-2]]:10] (counterexample: i [ssa=i_0] [type=i32] = -1, +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: below_zero [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = -1{{( ->|,|$)}} // A postcondition's reads are checked like the body's. int post_past_end(const Ring *r, int i) @@ -222,7 +224,8 @@ int post_past_end(const Ring *r, int i) { return 0; } -// CHECK-DAG: error: verification failed: post_past_end [{{.*}}::bounds@[[@LINE-4]]:50] (counterexample: {{.*}}i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-4]]:50: error: verification failed: post_past_end [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} // grid[i][4] is grid[i + 1][0] in memory, but j indexes a row of 4. int row_overflow(const Outer *p, int i, int j) @@ -230,7 +233,8 @@ int row_overflow(const Outer *p, int i, int j) { return p->grid[i][j]; } -// CHECK-DAG: error: verification failed: row_overflow [{{.*}}::bounds@[[@LINE-2]]:3] (counterexample: i [ssa=i_0] [type=i32] = {{[0-2]}}, j [ssa=j_0] [type=i32] = 4, +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: row_overflow [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = {{[0-2]}}{{( ->|,|$)}}{{.*}}j = 4{{( ->|,|$)}} void store_past_end(Outer *p, int i) cppverify::pre(p != nullptr && 0 <= i && i <= 8) @@ -238,7 +242,8 @@ void store_past_end(Outer *p, int i) { p->buf[i] = 0; } -// CHECK-DAG: error: verification failed: store_past_end [{{.*}}::bounds@[[@LINE-2]]:13] (counterexample: i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-2]]:13: error: verification failed: store_past_end [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} void slot_past_end(Ring *r, int i) cppverify::pre(r != nullptr && 0 <= i && i <= 8) @@ -247,7 +252,8 @@ void slot_past_end(Ring *r, int i) int &slot = r->buf[i]; slot = 0; } -// CHECK-DAG: error: verification failed: slot_past_end [{{.*}}::bounds@[[@LINE-3]]:3] (counterexample: i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: slot_past_end [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} void bump_past_end(Ring *r, int i) cppverify::pre(r != nullptr && 0 <= i && i <= 8 && r->head < 100) @@ -255,7 +261,8 @@ void bump_past_end(Ring *r, int i) { bump(r->buf[i]); } -// CHECK-DAG: error: verification failed: bump_past_end [{{.*}}::bounds@[[@LINE-2]]:8] (counterexample: i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-2]]:8: error: verification failed: bump_past_end [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} // A write to buf[i] may be the read of buf[j]. void write_keeps_all(Outer *p, int i, int j) @@ -265,7 +272,8 @@ void write_keeps_all(Outer *p, int i, int j) { p->buf[i] = 7; } -// CHECK-DAG: error: verification failed: write_keeps_all [{{.*}}::postcondition@[[@LINE-4]]:29] (counterexample: i [ssa=i_0] [type=i32] = [[I:[0-7]]], j [ssa=j_0] [type=i32] = [[I]], +// CHECK-DAG: :[[@LINE-4]]:29: error: verification failed: write_keeps_all [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}i = [[I:[0-7]]]{{.*}}j = [[I]]{{( ->|,|$)}} void outside_frame(Outer *p, int i) cppverify::pre(p != nullptr && 0 <= i && i < 8) @@ -273,7 +281,7 @@ void outside_frame(Outer *p, int i) { p->buf[i] = 0; } -// CHECK-DAG: error: verification failed: outside_frame [{{.*}}::frame@[[@LINE-2]]:13] +// CHECK-DAG: :[[@LINE-2]]:13: error: verification failed: outside_frame [frame] void nested_write_wrong(Outer *p) cppverify::pre(p != nullptr) @@ -282,7 +290,7 @@ void nested_write_wrong(Outer *p) { p->inner.y = 3; } -// CHECK-DAG: error: verification failed: nested_write_wrong [{{.*}}::postcondition@[[@LINE-4]]:30] +// CHECK-DAG: :[[@LINE-4]]:30: error: verification failed: nested_write_wrong [postcondition] #else // Arrays in global storage are not modeled; the message names the global. Ring global_ring; diff --git a/clang/test/Verify/suite/proof_clusters.cpp b/clang/test/Verify/suite/proof_clusters.cpp index 4ea1b5c8f5..4c0025f31f 100644 --- a/clang/test/Verify/suite/proof_clusters.cpp +++ b/clang/test/Verify/suite/proof_clusters.cpp @@ -72,7 +72,8 @@ cppverify::proof void lie_lemma(int n) { } // CHECK-DAG: error: verification failed: lie_lemma {{.*}}[reason=counterexample] -// CHECK-DAG: error: spec post failed: lie {{.*}}[reason=counterexample] {{.*}}call.lie_lemma +// CHECK-DAG: error: spec post failed: lie {{.*}}[reason=counterexample] +// CHECK-DAG: note: calls lie_lemma( // Without a shared measure, two proofs that rest on each other are a // cycle: the lemma hides the spec, so it uses the spec's postcondition, diff --git a/clang/test/Verify/suite/proof_steps.cpp b/clang/test/Verify/suite/proof_steps.cpp index aae3656a9f..2970a67080 100644 --- a/clang/test/Verify/suite/proof_steps.cpp +++ b/clang/test/Verify/suite/proof_steps.cpp @@ -58,7 +58,7 @@ void wrong_claim(int a, int b) sq_monotone(a, b); } } -// CHECK-DAG: error: verification failed: wrong_claim [{{.*}}::assertion@[[@LINE-4]]:3] +// CHECK-DAG: :[[@LINE-4]]:3: error: verification failed: wrong_claim [assertion] // A lemma's precondition is checked where the proof calls it. void lemma_outside_its_domain(int a, int b) @@ -68,7 +68,7 @@ void lemma_outside_its_domain(int a, int b) sq_monotone(a, b); } } -// CHECK-DAG: error: verification failed: lemma_outside_its_domain [{{.*}}::precondition@[[@LINE-3]]:5] +// CHECK-DAG: :[[@LINE-3]]:5: error: verification failed: lemma_outside_its_domain [precondition] void ghost_local_in_proof(int a) cppverify::pre(0 <= a && a <= 1000) @@ -144,7 +144,7 @@ void wrong_step(int a, int b) sq(a); } } -// CHECK-DAG: error: verification failed: wrong_step [{{.*}}::assertion@[[@LINE-4]]:5] +// CHECK-DAG: :[[@LINE-4]]:5: error: verification failed: wrong_step [assertion] // The chain's steps are local; its conclusion is not. void step_is_local(int a) diff --git a/clang/test/Verify/suite/range_for.cpp b/clang/test/Verify/suite/range_for.cpp index 5a217e247f..43d9ca0f77 100644 --- a/clang/test/Verify/suite/range_for.cpp +++ b/clang/test/Verify/suite/range_for.cpp @@ -37,7 +37,7 @@ int sum3_wrong() s = s + v; return s; } -// CHECK-DAG: error: verification failed: sum3_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: sum3_wrong [postcondition]{{.*}}[reason=counterexample] // A reference writes the element. int zeroed() @@ -60,7 +60,7 @@ int zeroed_wrong() x = 0; return a[0]; } -// CHECK-DAG: error: verification failed: zeroed_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// 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 { @@ -92,7 +92,7 @@ int total_wrong(const Ring *r) s = s + v; return s; } -// CHECK-DAG: error: verification failed: total_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: total_wrong [postcondition]{{.*}}[reason=counterexample] void clear(Ring *r) cv::pre(r != nullptr) @@ -127,7 +127,8 @@ int count_elements_wrong(Ring *r) 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] +// CHECK-DAG: :[[@LINE-5]]:3: error: verification failed: count_elements_wrong [dereference] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}r = 0{{( ->|,|$)}} // A constant table, break, continue, and a conversion to the variable's // type. @@ -166,7 +167,8 @@ long first_above_wrong(int bound) } return found; } -// CHECK-DAG: error: verification failed: first_above_wrong [{{.*}}bound [ssa=bound_0] [type=i32] = {{[0-9]+}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: first_above_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}bound = {{[0-9]+}}{{( ->|,|$)}} // The elements are read in each iteration; the range is located once. int doubled() @@ -234,7 +236,7 @@ int nested_wrong() s = s + x * y; return s; } -// CHECK-DAG: error: verification failed: nested_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: nested_wrong [postcondition]{{.*}}[reason=counterexample] int with_init() cv::post(cv::result == 6) @@ -269,7 +271,7 @@ int letters_wrong() n = n + 1; return n; } -// CHECK-DAG: error: verification failed: letters_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: letters_wrong [postcondition]{{.*}}[reason=counterexample] bool any_set() cv::post(cv::result) @@ -293,7 +295,7 @@ bool any_set_wrong() r = r || b; return r; } -// CHECK-DAG: error: verification failed: any_set_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// 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. @@ -344,7 +346,7 @@ int sum_x_wrong() s = s + p.x; return s; } -// CHECK-DAG: error: verification failed: sum_x_wrong [{{.*}}::postcondition@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: sum_x_wrong [postcondition]{{.*}}[reason=counterexample] int measured() cv::post(true) @@ -368,7 +370,7 @@ int measured_wrong() s = v; return s; } -// CHECK-DAG: error: verification failed: measured_wrong [{{.*}}::termination@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: measured_wrong [termination]{{.*}}[reason=counterexample] int may_diverge() cv::post(true) diff --git a/clang/test/Verify/suite/read_instances.cpp b/clang/test/Verify/suite/read_instances.cpp index 8aaa8574cf..98f1a36dff 100644 --- a/clang/test/Verify/suite/read_instances.cpp +++ b/clang/test/Verify/suite/read_instances.cpp @@ -69,7 +69,7 @@ void too_strong(const int *p, int n) { cppverify::check(p[2] >= 1); } -// Z3-DAG: error: verification failed: too_strong [{{.*}}::assertion@[[@LINE-2]]:3] +// Z3-DAG: :[[@LINE-2]]:3: error: verification failed: too_strong [assertion] void outside_range(const int *p, int n) cppverify::pre(valid(p, n + 1) && n >= 3 && n <= 100) @@ -77,11 +77,11 @@ void outside_range(const int *p, int n) { cppverify::check(p[n] >= 0); } -// Z3-DAG: error: verification failed: outside_range [{{.*}}::assertion@[[@LINE-2]]:3] +// Z3-DAG: :[[@LINE-2]]:3: error: verification failed: outside_range [assertion] void wrong_witness(const int *p, int n) cppverify::pre(valid(p, n) && n >= 3 && n <= 100 && p[2] == 7) cppverify::post(cppverify::exists(k, 3, n, p[k] == 7)) { } -// Z3-DAG: error: verification failed: wrong_witness [{{.*}}::postcondition@[[@LINE-3]]:19] +// Z3-DAG: :[[@LINE-3]]:19: error: verification failed: wrong_witness [postcondition] diff --git a/clang/test/Verify/suite/record_references.cpp b/clang/test/Verify/suite/record_references.cpp index 0a4eefdf87..e9ff167a3d 100644 --- a/clang/test/Verify/suite/record_references.cpp +++ b/clang/test/Verify/suite/record_references.cpp @@ -128,7 +128,8 @@ void self_frame(Counter &c) { frame(c, c); } -// CHECK-DAG: error: verification failed: self_frame [{{.*}}::aliasing@[[@LINE-2]]:3] (counterexample: c [ssa=c_0] [type=pointer] = +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: self_frame [aliasing] +// CHECK-DAG: note: counterexample: {{.*}}c = void may_alias(Counter &a, Counter &b) cppverify::aliases(a, b) @@ -137,7 +138,7 @@ void may_alias(Counter &a, Counter &b) { a.n = 5; } -// CHECK-DAG: error: verification failed: may_alias [{{.*}}::postcondition@[[@LINE-4]]:23] +// CHECK-DAG: :[[@LINE-4]]:23: error: verification failed: may_alias [postcondition] int sum(const Counter &a, const Counter &b) cppverify::pre(-1000 < a.n && a.n < 1000 && -1000 < b.n && b.n < 1000) @@ -160,21 +161,21 @@ void bad_frame(Counter &c) { c.limit = 3; } -// CHECK-DAG: error: verification failed: bad_frame [{{.*}}::frame@[[@LINE-2]]:11] +// CHECK-DAG: :[[@LINE-2]]:11: error: verification failed: bad_frame [frame] void narrow_caller(Counter &c) cppverify::modifies(c.n) { reset(c); } -// CHECK-DAG: error: verification failed: narrow_caller [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: narrow_caller [frame] void narrow_member(Pair &p) cppverify::modifies(p.left.n) { reset(p.left); } -// CHECK-DAG: error: verification failed: narrow_member [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: narrow_member [frame] // A callee's whole record is every byte of it, which only a footprint naming // the record, or an object around it, contains; its fields one by one do not. @@ -183,7 +184,7 @@ void fields_one_by_one(Pair &p) { reset(p.left); } -// CHECK-DAG: error: verification failed: fields_one_by_one [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: fields_one_by_one [frame] // Array members: indices bounded by their array, and pointers into them // bounded by it. @@ -202,7 +203,7 @@ void put_at(Ring &r, int i, int v) { r.buf[i] = v; } -// CHECK-DAG: error: verification failed: put_at [{{.*}}::bounds@[[@LINE-2]]:12] +// CHECK-DAG: :[[@LINE-2]]:12: error: verification failed: put_at [bounds] void set_first(int *q, int v) cppverify::pre(cppverify::valid(q, 4)) @@ -248,7 +249,7 @@ int past_head(const Ring &r) cppverify::check(h[1] == 0); return 0; } -// CHECK-DAG: error: verification failed: past_head [{{.*}}::bounds@[[@LINE-3]]:3] +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: past_head [bounds] // Local references to records bind a place as a parameter does. void local_deref(Counter *p) @@ -279,7 +280,7 @@ void local_wrong_member(Pair *p) Counter &r = p->right; r.n = 0; } -// CHECK-DAG: error: verification failed: local_wrong_member [{{.*}}::frame@[[@LINE-2]]:7] +// CHECK-DAG: :[[@LINE-2]]:7: error: verification failed: local_wrong_member [frame] void local_elements(Counter *a, int n) cppverify::pre(cppverify::valid(a, n) && n >= 0) @@ -304,7 +305,8 @@ void local_past(Counter *a, int i) Counter &r = a[i]; r.n = 3; } -// CHECK-DAG: error: verification failed: local_past [{{.*}}::dereference@[[@LINE-3]]:3] (counterexample: a [ssa=a_0] [type=pointer] = {{[0-9]+}}, i [ssa=i_0] [type=i32] = 4) +// CHECK-DAG: :[[@LINE-3]]:3: error: verification failed: local_past [dereference] +// CHECK-DAG: note: counterexample: a = {{[0-9]+}}, i = 4 // A local record bound to a reference is one object in memory. int local_object() @@ -326,7 +328,7 @@ int local_object_uninitialized() Counter &r = c; return r.limit; } -// CHECK-DAG: error: verification failed: local_object_uninitialized [{{.*}}::initialization@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: local_object_uninitialized [initialization] // A type invariant holds for a record by value and for one a reference or a // pointer names, at every function boundary. diff --git a/clang/test/Verify/suite/record_values_in_memory.cpp b/clang/test/Verify/suite/record_values_in_memory.cpp index 4012f3bb40..64d3f3cfa7 100644 --- a/clang/test/Verify/suite/record_values_in_memory.cpp +++ b/clang/test/Verify/suite/record_values_in_memory.cpp @@ -126,7 +126,7 @@ int past_element(const Bag *b, int k) { return limit_of(b->items[k]); } -// CHECK-DAG: error: verification failed: past_element [{{.*}}::bounds@[[@LINE-2]] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: past_element [bounds] // A copy reads every field, so each must be initialized. struct Pair { @@ -149,4 +149,4 @@ int half_initialized() a = 1; return pair_sum(p); } -// CHECK-DAG: error: verification failed: half_initialized [{{.*}}::initialization@[[@LINE-2]] +// CHECK-DAG: :[[@LINE-2]]:{{[0-9]+}}: error: verification failed: half_initialized [initialization] diff --git a/clang/test/Verify/suite/region_frames.cpp b/clang/test/Verify/suite/region_frames.cpp index fbfbf259c6..b230a1ad0b 100644 --- a/clang/test/Verify/suite/region_frames.cpp +++ b/clang/test/Verify/suite/region_frames.cpp @@ -47,7 +47,7 @@ void claims_too_much(int *p, int n, int lo) { zero(p + lo, n - lo); } -// CHECK-DAG: error: verification failed: claims_too_much [{{.*}}::initialization@ +// CHECK-DAG: error: verification failed: claims_too_much [initialization] // A function writing a range of its buffer. void zero_from(int *p, int n, int lo) @@ -79,7 +79,7 @@ void writes_outside(int *p, int n, int lo) { p[lo - 1] = 0; } -// CHECK-DAG: error: verification failed: writes_outside [{{.*}}::frame@[[@LINE-2]]:13] +// CHECK-DAG: :[[@LINE-2]]:13: error: verification failed: writes_outside [frame] void range_in_range(int *p, int n) cppverify::pre(valid(p, n) && n >= 4 && n <= 1000) @@ -95,7 +95,7 @@ void range_escapes(int *p, int n) { zero_from(p, n, 1); } -// CHECK-DAG: error: verification failed: range_escapes [{{.*}}::frame@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: range_escapes [frame] void writes_nothing(int *p, int n) cppverify::pre(valid(p, n) && n >= 1 && n <= 1000) @@ -153,7 +153,7 @@ void changes_sum(int *out, int n) { zero(out, n); } -// CHECK-DAG: error: verification failed: changes_sum [{{.*}}::postcondition@ +// CHECK-DAG: error: verification failed: changes_sum [postcondition] // BMC-DAG: Unresolved: keeps_prefix {{.*}}[reason=callee.contract] // BMC-DAG: error: verification failed: writes_outside diff --git a/clang/test/Verify/suite/replay_dependencies.cpp b/clang/test/Verify/suite/replay_dependencies.cpp index 040bfb02ad..3f43ee04bd 100644 --- a/clang/test/Verify/suite/replay_dependencies.cpp +++ b/clang/test/Verify/suite/replay_dependencies.cpp @@ -63,4 +63,4 @@ int loop_star(int n) // REPLAY-DAG: Proved obligations: loop_star (dependencies not checked in replay) [backend=z3] // JSON-DAG: "function":"bad_callee"{{.*}}"status":"failed" -// JSON-DAG: "function":"caller","message":"dependencies not checked in replay"{{.*}}"status":"proved-obligations" +// JSON-DAG: "function":"caller",{{.*}}"message":"dependencies not checked in replay"{{.*}}"status":"proved-obligations" diff --git a/clang/test/Verify/suite/ring_buffer.cpp b/clang/test/Verify/suite/ring_buffer.cpp new file mode 100644 index 0000000000..2d39ca8c40 --- /dev/null +++ b/clang/test/Verify/suite/ring_buffer.cpp @@ -0,0 +1,151 @@ +// RUN: %cpp-verify --jobs=1 %s -- 2>&1 | FileCheck %s +// RUN: not %cpp-verify --jobs=1 %s -- -DFORGET_WRAP 2>&1 | FileCheck %s --check-prefix=BUG +// +// The ring buffer case study (website/source/case-studies/ring-buffer.rst): +// a class template whose contract states it is a FIFO queue, through a +// view of its elements, and clients that rely on that contract alone. +// +// No proof here holds only because no execution reaches its end. +// CHECK-NOT: vacuous + +#include + +namespace cv = cppverify; + +template class Ring; +template cv::spec int size(const Ring *r); +template cv::spec int at(const Ring *r, int i); + +// A queue of at most N ints in a fixed array, as embedded code keeps one: +// its elements, oldest first, are buf[head], buf[head + 1], ..., wrapping +// around at N. +template +class Ring { +public: + Ring() + cv::post(size(this) == 0) + : head(0), count(0) + { + for (int i = 0; i < N; i = i + 1) + cv::invariant(0 <= i && i <= N && head == 0 && count == 0) + cv::invariant(cv::forall(k, 0, i, buf[k] == 0)) + cv::decreases(N - i) + { + buf[i] = 0; + } + } + + bool full() const + cv::post(cv::result == (size(this) == N)) + { + return count == N; + } + + // Appends v after the newest element. + void push(int v) + cv::pre(size(this) < N) + cv::post(size(this) == cv::old(size(this)) + 1) + cv::post(at(this, size(this) - 1) == v) + cv::post(cv::forall(k, 0, cv::old(size(this)), + at(this, k) == cv::old(at(this, k)))) + { + assert(count < N); + buf[(head + count) % N] = v; + count = count + 1; + } + + // Removes the oldest element and returns it. + int pop() + cv::pre(size(this) > 0) + cv::post(size(this) == cv::old(size(this)) - 1) + cv::post(cv::result == cv::old(at(this, 0))) + cv::post(cv::forall(k, 0, size(this), + at(this, k) == cv::old(at(this, k + 1)))) + { + int v = buf[head]; +#ifdef FORGET_WRAP + head = head + 1; +#else + head = (head + 1) % N; +#endif + count = count - 1; + return v; + } + +private: + int buf[N]; + int head; + int count; + + template friend cv::spec int size(const Ring *r); + template friend cv::spec int at(const Ring *r, int i); + +public: + cv::type_invariant(0 <= head && head < N && 0 <= count && count <= N); +}; + +// How many elements r holds. +template cv::spec int size(const Ring *r) { return r->count; } + +// The element i places after the oldest. +template cv::spec int at(const Ring *r, int i) +{ + return r->buf[(r->head + i) % N]; +} + +// The samples come out in the order they went in. +int first_two() + cv::post(cv::result == 12) +{ + cv::hide(at<4>); + Ring<4> r; + int samples[3] = {1, 2, 3}; + for (int s : samples) + cv::invariant(size(&r) == cv::index) + cv::invariant(cv::forall(k, 0, cv::index, at(&r, k) == samples[k])) + r.push(s); + if (!r.full()) + r.push(4); + int a = r.pop(); + int b = r.pop(); + return 10 * a + b; +} + +enum class Command { Push, Pop, Skip }; + +// Applies one command to r: a push of v, a pop, which returns the oldest +// element, or nothing. +int apply(Ring<4> &r, Command c, int v) + cv::pre(c != Command::Push || size(&r) < 4) + cv::pre(c != Command::Pop || size(&r) > 0) + cv::post(c != Command::Pop || cv::result == cv::old(at(&r, 0))) + cv::post(c != Command::Skip || size(&r) == cv::old(size(&r))) + cv::modifies(r) +{ + switch (c) { + case Command::Push: + r.push(v); + return 0; + case Command::Pop: + return r.pop(); + case Command::Skip: + break; + } + return 0; +} + +// CHECK-DAG: Verified: Ring<4>::Ring [backend=z3] +// CHECK-DAG: Verified: Ring<4>::full [backend=z3] +// CHECK-DAG: Verified: Ring<4>::push [backend=z3] +// CHECK-DAG: Verified: Ring<4>::pop [backend=z3] +// CHECK-DAG: Verified: first_two [backend=z3] +// CHECK-DAG: Verified: apply [backend=z3] +// CHECK: Summary: 6 verified + +// Without the wraparound, pop moves head past the array: the class +// invariant fails at its exit, and the path shows the store that breaks it. +// BUG: ring_buffer.cpp:{{[0-9]+}}:58: error: verification failed: Ring<4>::pop [type-invariant] [backend=z3] [reason=counterexample] +// BUG: note: counterexample: this = {{[0-9]+}} +// BUG: note: stores 4 at address {{[0-9]+}} +// BUG-NEXT: {{.*}}head = head + 1; +// BUG: Unresolved: first_two [backend=z3] [reason=callee.contract] (relies on the contract of Ring<4>::pop, which is not established) diff --git a/clang/test/Verify/suite/sequence_encoding_race.test b/clang/test/Verify/suite/sequence_encoding_race.test index a656356c78..9842872d44 100644 --- a/clang/test/Verify/suite/sequence_encoding_race.test +++ b/clang/test/Verify/suite/sequence_encoding_race.test @@ -6,4 +6,4 @@ # RUN: %python %S/Inputs/push_chain.py 200 > %t.cpp # RUN: not %cpp-verify --jobs=2 --timeout=10000 %t.cpp -- 2>&1 | FileCheck %s -# CHECK: error: verification failed: chain_bad [{{.*}}::assertion@{{.*}}] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +# CHECK: error: verification failed: chain_bad [assertion] [backend=z3] [reason=counterexample] diff --git a/clang/test/Verify/suite/sequence_induction.cpp b/clang/test/Verify/suite/sequence_induction.cpp index ee84e27f48..6a8740c90e 100644 --- a/clang/test/Verify/suite/sequence_induction.cpp +++ b/clang/test/Verify/suite/sequence_induction.cpp @@ -31,7 +31,7 @@ cppverify::proof void total_concat_wrong(seq s, seq t) if (t.len() > 0) total_concat_wrong(s, t.subrange(0, t.len() - 1)); } -// CHECK-DAG: error: verification failed: total_concat_wrong [{{.*}}::postcondition@[[@LINE-6]]:{{[0-9]+}}] (counterexample: {{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-6]]:{{[0-9]+}}: error: verification failed: total_concat_wrong [postcondition] [backend=z3] [reason=counterexample] cppverify::spec int count(seq s, int x) cppverify::decreases(s.len()) diff --git a/clang/test/Verify/suite/sequences.cpp b/clang/test/Verify/suite/sequences.cpp index 1a008e1b2c..09e3f4fbeb 100644 --- a/clang/test/Verify/suite/sequences.cpp +++ b/clang/test/Verify/suite/sequences.cpp @@ -56,7 +56,8 @@ cppverify::proof void update_wrong(seq s) { cppverify::check(s.update(1, 9)[2] == 9); } -// CHECK-DAG: error: verification failed: update_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: s [ssa=s_0] [type=seq] = [{{-?[0-9]+}}, {{-?[0-9]+}}, {{-?[0-9]+}}]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: update_wrong [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [{{-?[0-9]+}}, {{-?[0-9]+}}, {{-?[0-9]+}}] cppverify::proof void reverse_small() { @@ -71,7 +72,8 @@ cppverify::proof void reverse_wrong() cppverify::ghost seq s = cppverify::seq_of(1).push(2); cppverify::check(s.reverse()[0] == 1); } -// CHECK-DAG: error: verification failed: reverse_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: s [ssa=s_1] [type=seq] = [1, 2]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: reverse_wrong [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [1, 2] // reverse states its length; its elements need induction. cppverify::proof void reverse_index(seq s, long long k) @@ -99,7 +101,8 @@ cppverify::proof void elements_differ(seq s) { cppverify::check(s[0] == s[1]); } -// CHECK-DAG: error: verification failed: elements_differ [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: s [ssa=s_0] [type=seq] = [{{-?[0-9]+}}, {{-?[0-9]+}}]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: elements_differ [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [{{-?[0-9]+}}, {{-?[0-9]+}}] // JSON-DAG: "function":"elements_differ"{{.*}}"model":[{"name":"s","sort":"seq","source":{{.*}},"ssa_name":"s_0","value":"[{{-?[0-9]+}}, {{-?[0-9]+}}]"}] // Beyond its length a sequence reads 0, so finitely many indices decide an @@ -109,7 +112,8 @@ cppverify::proof void unbounded_elements(seq s) { cppverify::check(cppverify::forall(k, s[k] >= 0)); } -// CHECK-DAG: error: verification failed: unbounded_elements [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: s [ssa=s_0] [type=seq] = [{{.*}}-{{[0-9]+.*}}]) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: unbounded_elements [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: s = [{{.*}}-{{[0-9]+.*}}] cppverify::proof void unbounded_holds(seq s) cppverify::pre(cppverify::forall(k, 0, s.len(), s[k] >= 0)) @@ -136,7 +140,8 @@ cppverify::proof void sum_push_wrong(seq s, int x) cppverify::post(sum(s.push(x)) == sum(s)) { } -// CHECK-DAG: error: verification failed: sum_push_wrong [{{.*}}::postcondition@[[@LINE-3]]:{{[0-9]+}}] (counterexample: {{.*}}x [ssa=x_0] [type=i32] = {{-?[1-9][0-9]*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: sum_push_wrong [postcondition] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{-?[1-9][0-9]*}}{{( ->|,|$)}} cppverify::spec bool valid(const int *p, int n) { return true; } @@ -176,7 +181,8 @@ void ghost_loop_wrong(int n) } cppverify::check(s[0] == 1); } -// CHECK-DAG: error: verification failed: ghost_loop_wrong [{{.*}}::assertion@[[@LINE-2]]:3] (counterexample: {{.*}}s [ssa=s_2] [type=seq] = [{{-?[0-9]+}}]{{.*}}) [backend=z3] [reason=counterexample] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: ghost_loop_wrong [assertion] [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}s = {{.*}}[{{-?[0-9]+}}] // REPLAY-DAG: Proved obligations: basics (dependencies not checked in replay) // REPLAY-DAG: Proved obligations: reverse_index (dependencies not checked in replay) diff --git a/clang/test/Verify/suite/sequences_cvc5.test b/clang/test/Verify/suite/sequences_cvc5.test index 8800006c6b..d687f57085 100644 --- a/clang/test/Verify/suite/sequences_cvc5.test +++ b/clang/test/Verify/suite/sequences_cvc5.test @@ -25,7 +25,9 @@ # PORTFOLIO-DAG: Verified: sum_push [backend=portfolio] # PORTFOLIO-DAG: Verified: count_positive [backend=portfolio] # PORTFOLIO-DAG: Verified: reverse_index [backend=portfolio] -# PORTFOLIO-DAG: error: verification failed: reverse_wrong [{{.*}}] (counterexample: s [ssa=s_1] [type=seq] = [1, 2]) [backend=portfolio] [reason=counterexample] -# PORTFOLIO-DAG: error: verification failed: elements_differ [{{.*}}] (counterexample: s [ssa=s_0] [type=seq] = [{{.*}}]) [backend=portfolio] [reason=counterexample] -# PORTFOLIO-DAG: error: verification failed: sum_push_wrong [{{.*}}] (counterexample: {{.*}}) [backend=portfolio] [reason=counterexample] -# PORTFOLIO-DAG: error: verification failed: ghost_loop_wrong [{{.*}}] (counterexample: {{.*}}) [backend=portfolio] [reason=counterexample] +# PORTFOLIO-DAG: error: verification failed: reverse_wrong [{{.*}}] [backend=portfolio] [reason=counterexample] +# PORTFOLIO-DAG: note: counterexample: s = [1, 2] +# PORTFOLIO-DAG: error: verification failed: elements_differ [{{.*}}] [backend=portfolio] [reason=counterexample] +# PORTFOLIO-DAG: note: counterexample: s = [{{.*}}] +# PORTFOLIO-DAG: error: verification failed: sum_push_wrong [{{.*}}] [backend=portfolio] [reason=counterexample] +# PORTFOLIO-DAG: error: verification failed: ghost_loop_wrong [{{.*}}] [backend=portfolio] [reason=counterexample] diff --git a/clang/test/Verify/suite/shift_language_mode.cpp b/clang/test/Verify/suite/shift_language_mode.cpp index 19b45a3d71..38ed246338 100644 --- a/clang/test/Verify/suite/shift_language_mode.cpp +++ b/clang/test/Verify/suite/shift_language_mode.cpp @@ -13,7 +13,7 @@ int neg_shift() int m = -1; return m << 1; } -// CXX17-DAG: error: verification failed: neg_shift [{{.*}}::shift@ +// CXX17-DAG: error: verification failed: neg_shift [shift] // CXX20-DAG: Verified: neg_shift [backend=z3] int neg_shift_assign() @@ -23,7 +23,7 @@ int neg_shift_assign() m <<= 2; return m; } -// CXX17-DAG: error: verification failed: neg_shift_assign [{{.*}}::shift@ +// CXX17-DAG: error: verification failed: neg_shift_assign [shift] // CXX20-DAG: Verified: neg_shift_assign [backend=z3] // 2 * 2^31 fits neither int nor unsigned int; modulo 2^32 it is 0. @@ -33,7 +33,7 @@ int past_unsigned(int x) { return x << 31; } -// CXX17-DAG: error: verification failed: past_unsigned [{{.*}}::shift@ +// CXX17-DAG: error: verification failed: past_unsigned [shift] // CXX20-DAG: Verified: past_unsigned [backend=z3] // A contract is C++ too. @@ -43,7 +43,7 @@ int doubled(int x) { return x * 2; } -// CXX17-DAG: error: verification failed: doubled [{{.*}}::shift@ +// CXX17-DAG: error: verification failed: doubled [shift] // CXX20-DAG: Verified: doubled [backend=z3] // 2^31 fits unsigned int, so this is INT_MIN in both. @@ -60,7 +60,7 @@ int too_far(int x, int k) { return x << k; } -// CHECK-DAG: error: verification failed: too_far [{{.*}}::shift@ +// CHECK-DAG: error: verification failed: too_far [shift] int neg_right() cppverify::post(cppverify::result == -2) diff --git a/clang/test/Verify/suite/slice_argument_objects.cpp b/clang/test/Verify/suite/slice_argument_objects.cpp index 933e3c87af..fbec0b80df 100644 --- a/clang/test/Verify/suite/slice_argument_objects.cpp +++ b/clang/test/Verify/suite/slice_argument_objects.cpp @@ -118,7 +118,8 @@ int copied_past(const int *a) const int *q = a + 2; return first(q, 3); } -// CHECK-DAG: error: verification failed: copied_past [{{.*}}::bounds@[[@LINE-2]]:3] (counterexample: a [ssa=a_0] [type=pointer] = {{[0-9]+}}, q [ssa=q_1] [type=pointer] = {{[0-9]+}}; +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: copied_past [bounds] +// CHECK-DAG: note: counterexample: {{.*}}a = {{[0-9]+}}{{( ->|,|$)}}{{.*}}q = {{[0-9]+}}{{( ->|,|$)}} void copied_write_past(int *a) cppverify::pre(cppverify::valid(a, 4)) @@ -127,7 +128,7 @@ void copied_write_past(int *a) int *q = a + 2; zero2(q, 3); } -// CHECK-DAG: error: verification failed: copied_write_past [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: copied_write_past [bounds] // When c is true, a + 2 has two elements left; b has four. void chosen_past(int *a, int *b, bool c) @@ -137,7 +138,8 @@ void chosen_past(int *a, int *b, bool c) int *q = c ? a + 2 : b; zero2(q, 3); } -// CHECK-DAG: error: verification failed: chosen_past [{{.*}}::bounds@[[@LINE-2]]:3] (counterexample: {{.*}}c [ssa=c_0] [type=bool] = true, +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: chosen_past [bounds] +// CHECK-DAG: note: counterexample: {{.*}}c = true{{( ->|,|$)}} // The callee's contract does not say where its result points. int from_unknown(const int *a) @@ -146,7 +148,7 @@ int from_unknown(const int *a) const int *q = somewhere(a, 4); return first(q, 3); } -// CHECK-DAG: error: verification failed: from_unknown [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: from_unknown [bounds] // The elements must start inside the object too: this result is one element // before a, and the slice would end inside it. @@ -161,7 +163,8 @@ int from_before(const int *a) const int *q = before(a, 4); return first(q, 2); } -// CHECK-DAG: error: verification failed: from_before [{{.*}}::bounds@[[@LINE-2]]:3] (counterexample: a [ssa=a_0] [type=pointer] = {{[0-9]+}}, q [ssa=q_1] [type=pointer] = {{[0-9]+}}; +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: from_before [bounds] +// CHECK-DAG: note: counterexample: {{.*}}a = {{[0-9]+}}{{( ->|,|$)}}{{.*}}q = {{[0-9]+}}{{( ->|,|$)}} // A shorter walk leaves fewer elements than the call needs. int walked_past(const int *a, int n) @@ -178,4 +181,4 @@ int walked_past(const int *a, int n) } return first(q, 2); } -// CHECK-DAG: error: verification failed: walked_past [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: walked_past [bounds] diff --git a/clang/test/Verify/suite/smt2_dumps.test b/clang/test/Verify/suite/smt2_dumps.test new file mode 100644 index 0000000000..ae0a583c87 --- /dev/null +++ b/clang/test/Verify/suite/smt2_dumps.test @@ -0,0 +1,122 @@ +# --dump-smt2 writes each query a solver checks as a script the solver +# replays on its own: the options cpp-verify sets, the query, the check, and +# the answer cpp-verify got. Names depend on the query alone. +# +# RUN: rm -rf %t && mkdir -p %t +# RUN: not %cpp-verify --jobs=1 --dump-smt2=%t/z3 %S/Inputs/smt2_dumps.cpp -- 2>&1 \ +# RUN: | FileCheck %s --check-prefix=VERDICTS +# RUN: %python %S/Inputs/replay_smt2.py --list %t/z3 > %t/z3.files +# RUN: FileCheck %s --check-prefix=FILES --input-file=%t/z3.files +# RUN: %python -c "import os, sys; print(sorted(len(os.listdir(os.path.join(sys.argv[1], d))) > 0 for d in os.listdir(sys.argv[1]) if d.startswith('half-')))" %t/z3 \ +# RUN: | FileCheck %s --check-prefix=OVERLOADS +# RUN: cat %t/z3/clamp-*/postcondition-16-23.z3-*.smt2 | FileCheck %s --check-prefix=FAILED +# RUN: cat %t/z3/second-*/whole.z3-*.smt2 | FileCheck %s --check-prefix=QUANTIFIED +# +# A second run writes the same files. +# RUN: not %cpp-verify --jobs=1 --dump-smt2=%t/again %S/Inputs/smt2_dumps.cpp -- > /dev/null 2>&1 +# RUN: %python %S/Inputs/replay_smt2.py --list %t/again > %t/again.files +# RUN: diff %t/z3.files %t/again.files +# +# Each script replays in Z3's own shell with the answer cpp-verify got, and +# in cvc5 likewise. +# RUN: %if z3-shell %{ %python %S/Inputs/replay_smt2.py z3 %cppverify-z3 %t/z3 | FileCheck %s --check-prefix=Z3 %} +# RUN: %if cvc5 %{ not %cpp-verify --jobs=1 --backend=cvc5 --dump-smt2=%t/cvc5 %S/Inputs/smt2_dumps.cpp -- > /dev/null 2>&1 %} +# RUN: %if cvc5 %{ cat %t/cvc5/clamp-*/postcondition-16-23.cvc5-*.smt2 | FileCheck %s --check-prefix=CVC5 %} +# RUN: %if cvc5 %{ %python %S/Inputs/replay_smt2.py cvc5 cvc5 %t/cvc5 | FileCheck %s --check-prefix=CVC5-REPLAY %} +# +# A solver that has checked before prints the model converter its +# preprocessing left, `model-add` of symbols it never declares; the dump of +# a later check on it leaves that out. +# RUN: not %cpp-verify --jobs=1 --dump-smt2=%t/narrowed %S/Inputs/smt2_dumps_narrowed.cpp -- > /dev/null 2>&1 +# RUN: cat %t/narrowed/first_positive_bitwise-*/postcondition-*.smt2 \ +# RUN: | FileCheck %s --check-prefix=NARROWED --implicit-check-not=model-add +# +# An archive's replay writes the queries it checks. +# RUN: not %cpp-verify --jobs=1 --obligation-out=%t/q.cpv %S/Inputs/smt2_dumps.cpp -- > /dev/null 2>&1 +# RUN: not %cpp-verify --jobs=1 --obligation-in=%t/q.cpv --dump-smt2=%t/replay > /dev/null 2>&1 +# RUN: %python %S/Inputs/replay_smt2.py --list %t/replay | FileCheck %s --check-prefix=REPLAY +# +# A directory that cannot be written is a warning; the verdicts stand. +# RUN: touch %t/plain +# RUN: not %cpp-verify --jobs=1 --dump-smt2=%t/plain %S/Inputs/smt2_dumps.cpp -- 2>&1 \ +# RUN: | FileCheck %s --check-prefix=UNWRITABLE + +VERDICTS: Verified: inc [backend=z3] +VERDICTS: error: verification failed: clamp [postcondition] +VERDICTS: Verified: second [backend=z3] +VERDICTS: error: verification failed: closed [postcondition] +VERDICTS: Verified: half [backend=z3] +VERDICTS: Verified: half [backend=z3] + +A function's queries are in a directory named by the function and its +identity, each by its obligation and its script; the vacuity and contract +checks have their own. +FILES: clamp-[[CLAMP:[0-9a-f]+]]/missing-return-18-3.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: clamp-[[CLAMP]]/overflow-19-5.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: clamp-[[CLAMP]]/postcondition-16-23.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: clamp-[[CLAMP]]/whole.z3-{{[0-9a-f]+}}.smt2 +A query checked again, with the definition instances the certifier asked +for, is a file for each check. +FILES-NEXT: closed-[[CLOSED:[0-9a-f]+]]/missing-return-synthetic.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: closed-[[CLOSED]]/postcondition-39-23.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: closed-[[CLOSED]]/postcondition-39-23.z3-{{[0-9a-f]+}}.smt2 +FILES: closed-[[CLOSED]]/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: closed-[[CLOSED]]/whole.z3-{{[0-9a-f]+}}.smt2 +FILES: half-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: half-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: half_smoke-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: half_smoke-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: inc-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: inc_smoke-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: second-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: second_contract-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: second_smoke-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NEXT: tri_decreases-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 +FILES-NOT: {{.}} + +FAILED: ; cpp-verify {{.*}}, z3 {{[0-9]+\.[0-9]+\.[0-9]+}} +FAILED-NEXT: ; clamp: the postcondition check at {{.*}}smt2_dumps.cpp:16:23 ({{.*}}::postcondition@16:23) +FAILED-NEXT: ; replay: z3 clamp-{{[0-9a-f]+}}{{[/\\]}}postcondition-16-23.z3-{{[0-9a-f]+}}.smt2 +FAILED-NEXT: (set-option :timeout 30000) +FAILED-NEXT: (set-option :rlimit 6000000) +FAILED-NEXT: (set-option :smt.mbqi true) +FAILED-NEXT: (set-option :smt.qi.eager_threshold 0.0) +FAILED: (declare-fun x_0 () Int) +FAILED: (check-sat) +FAILED-NEXT: (get-model) +FAILED-NEXT: ; cpp-verify: sat +FAILED-NOT: {{.}} + +Each overload has a directory of its own: the hash is of its identity. +OVERLOADS: [True, True] + +A quantified query is solved by the smt tactic. +QUANTIFIED: ; second: every obligation at once +QUANTIFIED: (check-sat-using smt) +QUANTIFIED-NEXT: ; cpp-verify: unsat + +Z3: z3: {{[0-9]+}} scripts, {{[0-9]+}} with a recorded sat or unsat, 0 differing + +cvc5's command-line options are commands of the script. +CVC5: ; cpp-verify {{.*}}, cvc5 at {{.*}} +CVC5-NEXT: ; clamp: the postcondition check at {{.*}}smt2_dumps.cpp:16:23 +CVC5-NEXT: ; replay: cvc5 clamp-{{[0-9a-f]+}}{{[/\\]}}postcondition-16-23.cvc5-{{[0-9a-f]+}}.smt2 +CVC5-NEXT: (set-option :seed 0) +CVC5-NEXT: (set-option :sat-random-seed 0) +CVC5-NEXT: (set-option :dump-models true) +CVC5-NEXT: (set-option :tlimit-per 30000) +CVC5-NEXT: (set-logic ALL) +CVC5: (check-sat) +CVC5-NEXT: (exit) +CVC5-NEXT: ; cpp-verify: sat + +CVC5-REPLAY: cvc5: {{[0-9]+}} scripts, {{[0-9]+}} with a recorded sat or unsat, 0 differing + +NARROWED: (check-sat-using smt) +NARROWED: (check-sat-using smt) + +REPLAY: clamp-{{[0-9a-f]+}}/whole.z3-{{[0-9a-f]+}}.smt2 + +UNWRITABLE: Verified: inc [backend=z3] +UNWRITABLE: warning: --dump-smt2: cannot create {{.*}}plain{{.*}} +UNWRITABLE: Summary: 5 verified, 2 failed diff --git a/clang/test/Verify/suite/solver_race.cpp b/clang/test/Verify/suite/solver_race.cpp index 939ce8ddb1..b7be69b2c9 100644 --- a/clang/test/Verify/suite/solver_race.cpp +++ b/clang/test/Verify/suite/solver_race.cpp @@ -34,6 +34,7 @@ int wrong_bound(int x) return x + 1; } // CHECK-DAG: error: verification failed: wrong_bound {{.*}}[backend={{z3|cvc5}}] [reason=counterexample] +// CHECK-DAG: note: counterexample: x = 9, result = 10 // REPLAY-DAG: wrong_bound {{.*}}[reason=counterexample] cppverify::proof void pushed(seq s, int x) diff --git a/clang/test/Verify/suite/sound_modular_region_frame_fail.cpp b/clang/test/Verify/suite/sound_modular_region_frame_fail.cpp index 568cb506af..2b85a1bf08 100644 --- a/clang/test/Verify/suite/sound_modular_region_frame_fail.cpp +++ b/clang/test/Verify/suite/sound_modular_region_frame_fail.cpp @@ -21,4 +21,4 @@ void invalid_region_frame(int *p) } // VERIFY-DAG: Verified: clobber_offset -// VERIFY-DAG: error: verification failed: invalid_region_frame [{{.*}}::postcondition@ +// VERIFY-DAG: error: verification failed: invalid_region_frame [postcondition] diff --git a/clang/test/Verify/suite/sound_pointer_stride_fail.cpp b/clang/test/Verify/suite/sound_pointer_stride_fail.cpp index 781b811cbe..fcd2e360c3 100644 --- a/clang/test/Verify/suite/sound_pointer_stride_fail.cpp +++ b/clang/test/Verify/suite/sound_pointer_stride_fail.cpp @@ -87,4 +87,4 @@ bool past_object_record(const Pair *p, const Pair *q) // VERIFY-DAG: error: verification failed: invalid_adjacent_inequality // VERIFY-DAG: error: verification failed: invalid_char_adjacent_inequality // VERIFY-DAG: error: verification failed: invalid_record_stride -// VERIFY-DAG: error: verification failed: past_object_record [{{.*}}::bounds@ +// VERIFY-DAG: error: verification failed: past_object_record [bounds] diff --git a/clang/test/Verify/suite/spec_argument_modes.cpp b/clang/test/Verify/suite/spec_argument_modes.cpp index f223750aaa..426ad5b2f8 100644 --- a/clang/test/Verify/suite/spec_argument_modes.cpp +++ b/clang/test/Verify/suite/spec_argument_modes.cpp @@ -23,4 +23,5 @@ cppverify::proof void above_fails(int a) { cppverify::ghost { cppverify::reveal_with_fuel(above, 3); } } -// CHECK-DAG: error: verification failed: above_fails {{.*}}(counterexample: a {{.*}}= 2147483647) [backend=z3] [reason=counterexample] +// CHECK-DAG: error: verification failed: above_fails {{.*}} [backend=z3] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}a = 2147483647{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/spec_automation.cpp b/clang/test/Verify/suite/spec_automation.cpp index 303d5a3ac9..e9d212c8f2 100644 --- a/clang/test/Verify/suite/spec_automation.cpp +++ b/clang/test/Verify/suite/spec_automation.cpp @@ -115,9 +115,10 @@ void steps(int n) } // CHECK-DAG: Verified: computed -// CHECK-DAG: verification failed: computed_wrong [{{.*}}::assertion@ +// CHECK-DAG: verification failed: computed_wrong [assertion] // CHECK-DAG: Verified: bounded [backend= -// CHECK-DAG: verification failed: bounded_wrong {{.*}}n [ssa=n_0] [type=i32] = 137 +// CHECK-DAG: verification failed: bounded_wrong +// CHECK-DAG: note: counterexample: {{.*}}n = 137{{( ->|,|$)}} // CHECK-DAG: Verified: inductive // CHECK-DAG: Unresolved: accumulated [backend={{z3|bmc, bound=0}}] [reason=spec.fuel] {{.*}}by induction in a proof function // CHECK-DAG: Verified: accumulate_sum diff --git a/clang/test/Verify/suite/spec_fuel_counterexamples.cpp b/clang/test/Verify/suite/spec_fuel_counterexamples.cpp index 9375de6ad4..2785804e81 100644 --- a/clang/test/Verify/suite/spec_fuel_counterexamples.cpp +++ b/clang/test/Verify/suite/spec_fuel_counterexamples.cpp @@ -50,7 +50,8 @@ void not_inductive(int n) } // CHECK-DAG: Verified: closed_form [ -// CHECK-DAG: error: verification failed: closed_form_wrong [{{.*}}::postcondition@{{.*}}counterexample: {{.*}}n [ssa=n_0] [type=i32] = {{[1-6]}} +// CHECK-DAG: error: verification failed: closed_form_wrong [postcondition] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[1-6]}}{{( ->|,|$)}} // CHECK-DAG: Verified: unbounded [backend=z3] // CHECK-DAG: Unresolved: not_inductive [backend=z3] [reason=spec.fuel] diff --git a/clang/test/Verify/suite/spec_induction.cpp b/clang/test/Verify/suite/spec_induction.cpp index 31aea7edcd..d1f0296c75 100644 --- a/clang/test/Verify/suite/spec_induction.cpp +++ b/clang/test/Verify/suite/spec_induction.cpp @@ -47,7 +47,8 @@ cppverify::proof void sum_wrong_once(int n) cppverify::post(2 * sum(n) == n * (n + 1) + (n == 200 ? 1 : 0)) { } -// CHECK-DAG: error: verification failed: sum_wrong_once {{.*}}(counterexample: n [ssa=n_0] [type=i32] = 200) +// CHECK-DAG: error: verification failed: sum_wrong_once {{.*}} +// CHECK-DAG: note: counterexample: n = 200 cppverify::proof void pow2_bounded(int n) cppverify::pre(n >= 0) @@ -71,4 +72,5 @@ cppverify::proof void closed_form_everywhere(int n) cppverify::post(2 * sum(n) == n * (n + 1)) { } -// CHECK-DAG: error: verification failed: closed_form_everywhere {{.*}}(counterexample: n [ssa=n_0] [type=i32] = -{{[0-9]+}}) +// CHECK-DAG: error: verification failed: closed_form_everywhere {{.*}} +// CHECK-DAG: note: counterexample: n = -{{[0-9]+}} diff --git a/clang/test/Verify/suite/spec_machine_conversion.cpp b/clang/test/Verify/suite/spec_machine_conversion.cpp index 93d6f04613..3aa4c2cd28 100644 --- a/clang/test/Verify/suite/spec_machine_conversion.cpp +++ b/clang/test/Verify/suite/spec_machine_conversion.cpp @@ -222,33 +222,33 @@ int contract_division_by_zero(int x, int y) } // VERIFY-DAG: Verified: ghost_in_range -// VERIFY-DAG: error: verification failed: ghost_out_of_range [{{.*}}::overflow@ -// VERIFY-DAG: error: verification failed: ghost_no_wrap [{{.*}}::overflow@ -// VERIFY-DAG: error: verification failed: ghost_negative_unsigned [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: ghost_out_of_range [overflow] +// VERIFY-DAG: error: verification failed: ghost_no_wrap [overflow] +// VERIFY-DAG: error: verification failed: ghost_negative_unsigned [overflow] // VERIFY-DAG: Verified: ghost_machine_arithmetic // VERIFY-DAG: Verified: proof_in_range -// VERIFY-DAG: error: verification failed: proof_out_of_range [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: proof_out_of_range [overflow] // VERIFY-DAG: Verified: constexpr_argument_in_range -// VERIFY-DAG: error: verification failed: constexpr_argument_out_of_range [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: constexpr_argument_out_of_range [overflow] // VERIFY-DAG: Verified: constexpr_short_parameter [ -// VERIFY-DAG: error: verification failed: constexpr_short_parameter_out_of_range [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: constexpr_short_parameter_out_of_range [overflow] // VERIFY-DAG: Verified: spec_through_constexpr [ -// VERIFY-DAG: error: verification failed: spec_through_constexpr_out_of_range [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: spec_through_constexpr_out_of_range [overflow] // VERIFY-DAG: Verified: contract_exact // VERIFY-DAG: Verified: contract_implicit_widening // VERIFY-DAG: Verified: contract_mixed_arithmetic // VERIFY-DAG: Verified: contract_mixed_widening -// VERIFY-DAG: error: verification failed: contract_mixed_narrowing [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: contract_mixed_narrowing [overflow] // VERIFY-DAG: Verified: contract_implicit_unsigned // VERIFY-DAG: Verified: contract_mixed_order // VERIFY-DAG: Verified: contract_conditional // VERIFY-DAG: Verified: contract_explicit_cast [ -// VERIFY-DAG: error: verification failed: contract_explicit_cast_out_of_range [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: contract_explicit_cast_out_of_range [overflow] // VERIFY-DAG: Verified: contract_explicit_long -// VERIFY-DAG: error: verification failed: contract_explicit_unsigned_negative [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: contract_explicit_unsigned_negative [overflow] // VERIFY-DAG: Verified: contract_shift -// VERIFY-DAG: error: verification failed: contract_bitwise_out_of_range [{{.*}}::overflow@ -// VERIFY-DAG: error: verification failed: contract_division_by_zero [{{.*}}::division-by-zero@ +// VERIFY-DAG: error: verification failed: contract_bitwise_out_of_range [overflow] +// VERIFY-DAG: error: verification failed: contract_division_by_zero [division-by-zero] // Layer 1: the ghost materialization is an explicit conversion. // IR-LABEL: fn ghost_in_range diff --git a/clang/test/Verify/suite/spec_mutual_recursion.cpp b/clang/test/Verify/suite/spec_mutual_recursion.cpp index 21f347cd46..435bf308b5 100644 --- a/clang/test/Verify/suite/spec_mutual_recursion.cpp +++ b/clang/test/Verify/suite/spec_mutual_recursion.cpp @@ -55,7 +55,9 @@ void parity_wrong(int n) // CHECK-DAG: Verified: spec decreases: is_even // CHECK-DAG: Verified: spec decreases: is_odd // CHECK-DAG: Unresolved: spec decreases: ping [reason=spec.termination] (relies on the definition of pong, whose termination is not established) -// CHECK-DAG: error: spec decreases failed: pong {{.*}}[reason=counterexample] (n [type=math-i32] = {{[1-9][0-9]*}}) +// CHECK-DAG: error: spec decreases failed: pong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[1-9][0-9]*}}{{( ->|,|$)}} // CHECK-DAG: Verified: parity // CHECK-DAG: Verified: parity_exclusive -// CHECK-DAG: verification failed: parity_wrong {{.*}}n [ssa=n_0] [type=i32] = {{[0-9]+}} +// CHECK-DAG: verification failed: parity_wrong +// CHECK-DAG: note: counterexample: {{.*}}n = {{[0-9]+}}{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/spec_post.cpp b/clang/test/Verify/suite/spec_post.cpp index 21dfa9fa96..92c4e5791f 100644 --- a/clang/test/Verify/suite/spec_post.cpp +++ b/clang/test/Verify/suite/spec_post.cpp @@ -77,7 +77,8 @@ cppverify::spec int bad_clamp(int x) { return x > 100 ? 100 : x; } -// CHECK-DAG: error: spec post failed: bad_clamp [backend={{.*}}] [reason=counterexample] (x [type=math-i32] = {{-[0-9]+}}) +// CHECK-DAG: error: spec post failed: bad_clamp [backend={{.*}}] [reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = {{-[0-9]+}}{{( ->|,|$)}} void uses_bad_clamp(int x) { @@ -92,6 +93,7 @@ void unfolds_bad_clamp(int x) { cppverify::ghost { cppverify::check(bad_clamp(x) >= 0); } } -// CHECK-DAG: error: verification failed: unfolds_bad_clamp {{.*}}(counterexample: x [ssa=x_0] [type=i32] = {{-[0-9]+}}) +// CHECK-DAG: error: verification failed: unfolds_bad_clamp {{.*}} +// CHECK-DAG: note: counterexample: x = {{-[0-9]+}} // CHECK-DAG: Unresolved: uses_bad_clamp {{.*}}[reason=spec.post] (relies on the postcondition of bad_clamp, which is not established) // JSON-DAG: "function":"uses_bad_clamp"{{.*}}"reason":"spec.post"{{.*}}"status":"unresolved" diff --git a/clang/test/Verify/suite/spec_reads.cpp b/clang/test/Verify/suite/spec_reads.cpp index bc6ae82f89..3d159eb2ce 100644 --- a/clang/test/Verify/suite/spec_reads.cpp +++ b/clang/test/Verify/suite/spec_reads.cpp @@ -80,7 +80,7 @@ cppverify::spec int sum_short(const int *p, int n) return sum_short(p, n - 1) + p[n - 1]; } // CHECK-DAG: error: spec reads failed: sum_short {{.*}}[reason=counterexample] -// REPLAY-DAG: verification failed: sum_short.reads [{{.*}}::reads::frame@ +// REPLAY-DAG: verification failed: sum_short.reads [frame] void write_last(int *p, int n) cppverify::pre(n >= 1 && n <= 100 && valid(p, n)) diff --git a/clang/test/Verify/suite/spec_reads_exact.cpp b/clang/test/Verify/suite/spec_reads_exact.cpp index da83352a99..3a77a2a558 100644 --- a/clang/test/Verify/suite/spec_reads_exact.cpp +++ b/clang/test/Verify/suite/spec_reads_exact.cpp @@ -70,5 +70,5 @@ unsigned next(const unsigned *p) // CHECK-NEXT: Verified: spec reads: first // CHECK-NEXT: Verified: large_sum // CHECK-NEXT: error: verification failed: negative_sum {{.*}}[reason=counterexample] -// CHECK-NEXT: Verified: smallest_negated +// CHECK: Verified: smallest_negated // CHECK-NEXT: Verified: next [backend=z3]{{$}} diff --git a/clang/test/Verify/suite/spec_termination.cpp b/clang/test/Verify/suite/spec_termination.cpp index 90e97451a5..0fa5fb8b05 100644 --- a/clang/test/Verify/suite/spec_termination.cpp +++ b/clang/test/Verify/suite/spec_termination.cpp @@ -82,15 +82,19 @@ void all_below_small(int n) // CHECK-DAG: Verified: spec decreases: ack // CHECK-DAG: Verified: ack_values -// CHECK-DAG: error: spec decreases failed: diverges_at_one {{.*}}[reason=counterexample] (n [type=math-i32] = 1) +// CHECK-DAG: error: spec decreases failed: diverges_at_one {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = 1{{( ->|,|$)}} // CHECK-DAG: Unresolved: uses_divergent [backend={{z3|bmc, bound=[0-9]+}}]{{( \[vacuous\])?}} [reason=spec.termination] (relies on the definition of diverges_at_one, whose termination is not established) // CHECK-DAG: Unresolved: spec decreases unresolved: nested {{.*}}a definition cannot be used to prove its own termination -// CHECK-DAG: error: spec decreases failed: second_unbounded {{.*}}[reason=counterexample] (m [type=math-i32] = {{[1-9][0-9]*}}, n [type=math-i32] = {{-?[0-9]+}}) -// CHECK-DAG: error: spec decreases failed: first_unbounded {{.*}}[reason=counterexample] (m [type=math-i32] = {{-?[0-9]+}}, n [type=math-i32] = {{[1-9][0-9]*}}) +// CHECK-DAG: error: spec decreases failed: second_unbounded {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}m = {{[1-9][0-9]*}}{{( ->|,|$)}}{{.*}}n = {{-?[0-9]+}}{{( ->|,|$)}} +// CHECK-DAG: error: spec decreases failed: first_unbounded {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}m = {{-?[0-9]+}}{{( ->|,|$)}}{{.*}}n = {{[1-9][0-9]*}}{{( ->|,|$)}} // CHECK-DAG: Verified: spec decreases: all_below // CHECK-DAG: Verified: all_below_small -// CHECK-DAG: error: spec decreases failed: reaches_itself {{.*}}[reason=counterexample] (n [type=math-i32] = {{[0-9]+}}) +// CHECK-DAG: error: spec decreases failed: reaches_itself {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[0-9]+}}{{( ->|,|$)}} // JSON-DAG: "function":"diverges_at_one"{{.*}}"status":"failed" // JSON-DAG: "function":"ack"{{.*}}"status":"verified" diff --git a/clang/test/Verify/suite/spec_when.cpp b/clang/test/Verify/suite/spec_when.cpp index 236f4a5bc1..eccc38ada1 100644 --- a/clang/test/Verify/suite/spec_when.cpp +++ b/clang/test/Verify/suite/spec_when.cpp @@ -77,4 +77,5 @@ cppverify::spec int stalls(int n) { return n <= 1 ? 0 : stalls(n - (n % 2)); } -// CHECK-DAG: error: spec decreases failed: stalls {{.*}}[reason=counterexample] (n [type=math-i32] = {{[0-9]*[02468]}}) +// CHECK-DAG: error: spec decreases failed: stalls {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}n = {{[0-9]*[02468]}}{{( ->|,|$)}} diff --git a/clang/test/Verify/suite/std_min_max.cpp b/clang/test/Verify/suite/std_min_max.cpp index fe13d8e7ec..413d0fb9e1 100644 --- a/clang/test/Verify/suite/std_min_max.cpp +++ b/clang/test/Verify/suite/std_min_max.cpp @@ -42,7 +42,7 @@ int wrong(int a, int b) { return std::min(a, b); } -// CHECK-DAG: error: verification failed: wrong [{{.*}}::postcondition@[[@LINE-4]] +// CHECK-DAG: :[[@LINE-4]]:{{[0-9]+}}: error: verification failed: wrong [postcondition] int clamped(int x) cppverify::post(cppverify::result == std::clamp(x, 0, 9)) diff --git a/clang/test/Verify/suite/structured_output.test b/clang/test/Verify/suite/structured_output.test new file mode 100644 index 0000000000..df1effe0b5 --- /dev/null +++ b/clang/test/Verify/suite/structured_output.test @@ -0,0 +1,105 @@ +# Results as records: JSON Lines of cppverify.diagnostic/2, ending with a +# cppverify.summary/1 record; one SARIF 2.1.0 log made of the same records; +# and, in GitHub mode, the text with a workflow annotation after each error and +# warning. The compiler's own diagnostics are records too. A record's source +# spans its expression, from its first column to the one after its last. +# +# RUN: not %cpp-verify --jobs=1 --diagnostics-format=json %S/Inputs/structured_output.cpp -- 2>/dev/null \ +# RUN: | FileCheck %s --check-prefix=JSON +# RUN: not %cpp-verify --jobs=1 --diagnostics-format=sarif %S/Inputs/structured_output.cpp -- 2>/dev/null \ +# RUN: | FileCheck %s --check-prefix=SARIF +# RUN: not %cpp-verify --diagnostics-format=json %S/Inputs/structured_syntax_error.cpp -- 2>/dev/null \ +# RUN: | FileCheck %s --check-prefix=FRONTEND +# RUN: not %cpp-verify --jobs=1 --obligation-out=%t.cpv %S/Inputs/structured_output.cpp -- > /dev/null 2>&1 +# RUN: not %cpp-verify --obligation-in=%t.cpv --diagnostics-format=sarif 2>/dev/null \ +# RUN: | FileCheck %s --check-prefix=REPLAY +# RUN: cd %S/Inputs && not %cpp-verify --jobs=1 --diagnostics-format=github structured_output.cpp -- 2>/dev/null \ +# RUN: | FileCheck %s --check-prefix=GITHUB + +The compiler's warning comes first, with its notes as related locations. +JSON: {"message":"using the result of an assignment as a condition without parentheses","option":"-Wparentheses","related":[{"message":"place parentheses around the assignment to silence this warning","source":{"column":9,"end_column":9,"end_line":26,"file":"{{.*}}structured_output.cpp","line":26}},{"message":"use '==' to turn this assignment into an equality comparison","source":{{.*}}}],"schema":"cppverify.diagnostic/2","severity":"warning","source":{"column":7,"end_column":12,"end_line":26,"file":"{{.*}}structured_output.cpp","line":26},"status":"frontend"} +Every result names its function and where it is declared. +JSON-NEXT: {"backend":"z3","function":"inc","function_source":{"column":5,"end_column":8,"end_line":3,"file":"{{.*}}structured_output.cpp","line":3},"message":"inc [backend=z3]","schema":"cppverify.diagnostic/2","severity":"note","status":"verified"} +A failure spans the expression it checks, and gives its counterexample and +the steps of its path as the text prints them. +JSON-NEXT: {"backend":"z3","counterexample":"x = {{-[0-9]+}}, result = {{-?[0-9]+}}","function":"clamp" +JSON-SAME: "obligation":{"id":"{{.*}}::postcondition@11:23","kind":"postcondition","source":{"column":12,"end_column":27,"end_line":11,"file":"{{.*}}structured_output.cpp","line":11}} +JSON-SAME: "source":{"column":12,"end_column":27,"end_line":11,"file":"{{.*}}structured_output.cpp","line":11},"status":"failed" +JSON-SAME: "text":"takes the true branch" +JSON-SAME: "text":"returns {{-?[0-9]+}}" +JSON-NEXT: "function":"never"{{.*}}"status":"verified","vacuous":true} +JSON-NEXT: {"function":"never","function_source":{"column":5,"end_column":10,"end_line":18,"file":"{{.*}}structured_output.cpp","line":18},"message":"never: the precondition is unsatisfiable, so every claim about it holds vacuously","schema":"cppverify.diagnostic/2","severity":"warning","status":"warning"} +The run ends with its counts. +JSON-NEXT: {"bounded_safe":0,"exported":0,"failed":1,"interrupted":0,"lowered":0,"reasons":{},"schema":"cppverify.summary/1","trusted":0,"unresolved":0,"verified":2} +JSON-NOT: {{.}} + +SARIF: "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", +SARIF: "uri": "file://{{.*}}/Inputs/structured_output.cpp" +SARIF: "executionSuccessful": true, +SARIF-NEXT: "exitCode": 1 +SARIF: "summary": { +SARIF: "failed": 1, +SARIF: "verified": 2 +SARIF: "results": [ +The compiler's warning, its notes as related locations. +SARIF: "kind": "fail", +SARIF-NEXT: "level": "warning", +SARIF: "text": "using the result of an assignment as a condition without parentheses" +SARIF: "option": "-Wparentheses", +SARIF: "relatedLocations": [ +SARIF: "text": "place parentheses around the assignment to silence this warning" +SARIF: "ruleId": "frontend", +A proof passes. +SARIF: "kind": "pass", +SARIF-NEXT: "level": "none", +SARIF: "name": "inc" +SARIF: "text": "Verified: inc [backend=z3]" +SARIF: "ruleId": "verified", +A failure fails, its path a code flow and its rule the kind of check. +SARIF: "codeFlows": [ +SARIF: "text": "takes the true branch" +SARIF: "startLine": 13 +SARIF: "text": "returns {{-?[0-9]+}}" +SARIF: "kind": "fail", +SARIF-NEXT: "level": "error", +SARIF: "endColumn": 27, +SARIF-NEXT: "endLine": 11, +SARIF-NEXT: "startColumn": 12, +SARIF-NEXT: "startLine": 11 +SARIF: "text": "verification failed: clamp [postcondition] [backend=z3] [reason=counterexample]\ncounterexample: x = {{-[0-9]+}}, result = {{-?[0-9]+}}" +SARIF: "obligation": "{{.*}}::postcondition@11:23", +SARIF: "ruleId": "postcondition", +SARIF: "text": "Verified: never [backend=z3] [vacuous]" +A warning of the verifier. +SARIF: "kind": "fail", +SARIF-NEXT: "level": "warning", +SARIF: "ruleId": "warning", +SARIF: "tool": { +SARIF: "name": "cpp-verify", +SARIF: "rules": [ +SARIF: "id": "frontend", +SARIF: "id": "verified", +SARIF: "level": "error" +SARIF-NEXT: }, +SARIF: "id": "postcondition", +SARIF: "version": "2.1.0" + +A compiler error is a record of its own, and nothing is verified. +FRONTEND: {"message":"expected ';' after return statement","schema":"cppverify.diagnostic/2","severity":"error","source":{"column":11,"end_column":11,"end_line":4,"file":"{{.*}}structured_syntax_error.cpp","line":4},"status":"frontend"} +FRONTEND-NEXT: {{.*}}"failed":0,{{.*}}"schema":"cppverify.summary/1",{{.*}}"verified":0} + +An archive keeps the range a failure spans. +REPLAY: "kind": "fail", +REPLAY-NEXT: "level": "error", +REPLAY: "endColumn": 27, +REPLAY-NEXT: "endLine": 11, +REPLAY-NEXT: "startColumn": 12, +REPLAY-NEXT: "startLine": 11 +REPLAY: "ruleId": "postcondition", + +GitHub's annotations name files relative to the working directory. +GITHUB: ::warning file=structured_output.cpp,line=26,col=7,endLine=26,endColumn=12,title=clang::using the result of an assignment as a condition without parentheses +GITHUB: Verified: inc [backend=z3] +GITHUB: ::error file=structured_output.cpp,line=11,col=12,endLine=11,endColumn=27,title=cpp-verify%3A postcondition::verification failed: clamp [postcondition] [backend=z3] [reason=counterexample]%0Acounterexample: x = {{-[0-9]+}}, result = {{-?[0-9]+}} +GITHUB: ::warning file=structured_output.cpp,line=18,col=5,endLine=18,endColumn=10,title=cpp-verify::never: the precondition is unsatisfiable, so every claim about it holds vacuously +GITHUB: Summary: 2 verified, 1 failed diff --git a/clang/test/Verify/suite/subobject_pointers.cpp b/clang/test/Verify/suite/subobject_pointers.cpp index 1f062924ca..c5b6d544ee 100644 --- a/clang/test/Verify/suite/subobject_pointers.cpp +++ b/clang/test/Verify/suite/subobject_pointers.cpp @@ -275,14 +275,15 @@ int pass_too_long(const Ring *r) { return first(r->buf, 9); } -// CHECK-DAG: error: verification failed: pass_too_long [{{.*}}::bounds@[[@LINE-2]]:3#2] (counterexample: r [ssa=r_0] [type=pointer] = {{[0-9]+}} +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: pass_too_long [bounds] +// CHECK-DAG: note: counterexample: {{.*}}r = {{[0-9]+}}{{( ->|,|$)}} int pass_tail_too_long(const Ring *r) cppverify::pre(r != nullptr) { return first(r->buf + 5, 4); } -// CHECK-DAG: error: verification failed: pass_tail_too_long [{{.*}}::bounds@[[@LINE-2]]:3#2] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: pass_tail_too_long [bounds] // The end of buf is a pointer, but get reads the element there: r->head. int pass_end(const Ring *r) @@ -291,7 +292,7 @@ int pass_end(const Ring *r) const int *q = r->buf + 8; return get(q); } -// CHECK-DAG: error: verification failed: pass_end [{{.*}}::bounds@[[@LINE-2]]:3] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: pass_end [bounds] // q[-1] is r->items[3].w, inside *r but before other. int before_member(const Ring *r) @@ -300,7 +301,7 @@ int before_member(const Ring *r) const int *q = r->other; return q[-1]; } -// CHECK-DAG: error: verification failed: before_member [{{.*}}::bounds@[[@LINE-2]]:10#2] +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: before_member [bounds] // q[8] is r->head. int local_past(const Ring *r) @@ -309,7 +310,7 @@ int local_past(const Ring *r) const int *q = r->buf; return q[8]; } -// CHECK-DAG: error: verification failed: local_past [{{.*}}::bounds@[[@LINE-2]]:10#2] +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: local_past [bounds] int element_past(const Ring *r) cppverify::pre(r != nullptr) @@ -317,7 +318,7 @@ int element_past(const Ring *r) const int *q = &r->buf[2]; return q[6]; } -// CHECK-DAG: error: verification failed: element_past [{{.*}}::bounds@[[@LINE-2]]:10#2] +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: element_past [bounds] // q[1] is r->inner.x, next to head. void field_neighbor(Ring *r) @@ -327,7 +328,7 @@ void field_neighbor(Ring *r) int *q = &r->head; q[1] = 0; } -// CHECK-DAG: error: verification failed: field_neighbor [{{.*}}::bounds@[[@LINE-2]]:3#2] +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: field_neighbor [bounds] // q[4] is grid[i + 1][0]. int row_past(const Ring *r, int i) @@ -336,7 +337,7 @@ int row_past(const Ring *r, int i) const int *q = r->grid[i]; return q[4]; } -// CHECK-DAG: error: verification failed: row_past [{{.*}}::bounds@[[@LINE-2]]:10#2] +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: row_past [bounds] // it[1] is past items, at r->other. int item_past(const Ring *r) @@ -345,7 +346,7 @@ int item_past(const Ring *r) const Item *it = &r->items[3]; return it[1].v; } -// CHECK-DAG: error: verification failed: item_past [{{.*}}::bounds@[[@LINE-2]]:16#2] +// CHECK-DAG: :[[@LINE-2]]:16: error: verification failed: item_past [bounds] // When c is false, q addresses other, of two elements. int chosen_past(const Ring *r, bool c) @@ -354,7 +355,8 @@ int chosen_past(const Ring *r, bool c) const int *q = c ? r->buf : r->other; return q[2]; } -// CHECK-DAG: error: verification failed: chosen_past [{{.*}}::bounds@[[@LINE-2]]:10] (counterexample: c [ssa=c_0] [type=bool] = false, +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: chosen_past [bounds] +// CHECK-DAG: note: counterexample: {{.*}}c = false{{( ->|,|$)}} int walk_past(const Ring *r) cppverify::pre(r != nullptr) @@ -368,7 +370,7 @@ int walk_past(const Ring *r) } return s; } -// CHECK-DAG: error: verification failed: walk_past [{{.*}}::bounds@[[@LINE-4]]:10#2] +// CHECK-DAG: :[[@LINE-4]]:10: error: verification failed: walk_past [bounds] // Forming the address of an element is reading nothing, but the element // must exist: buf[8] does not. @@ -378,7 +380,8 @@ int element_one_past(const Ring *r, int i) const int *q = &r->buf[i]; return 0; } -// CHECK-DAG: error: verification failed: element_one_past [{{.*}}::bounds@[[@LINE-3]]:18] (counterexample: i [ssa=i_0] [type=i32] = 8, +// CHECK-DAG: :[[@LINE-3]]:18: error: verification failed: element_one_past [bounds] +// CHECK-DAG: note: counterexample: {{.*}}i = 8{{( ->|,|$)}} int null_root(const Ring *r) cppverify::post(cppverify::result == 0) @@ -386,7 +389,8 @@ int null_root(const Ring *r) const int *q = r->buf; return 0; } -// CHECK-DAG: error: verification failed: null_root [{{.*}}::dereference@[[@LINE-3]]:18] (counterexample: r [ssa=r_0] [type=pointer] = 0) +// CHECK-DAG: :[[@LINE-3]]:18: error: verification failed: null_root [dereference] +// CHECK-DAG: note: counterexample: r = 0 // Pointers into different members differ by nothing C++ defines. int across_members(const Ring *r) @@ -394,7 +398,7 @@ int across_members(const Ring *r) { return &r->head - r->buf; } -// CHECK-DAG: error: verification failed: across_members [{{.*}}::pointer-difference@[[@LINE-2]]:21] +// CHECK-DAG: :[[@LINE-2]]:21: error: verification failed: across_members [pointer-difference] void write_frame(Ring *r) cppverify::pre(r != nullptr) @@ -403,7 +407,7 @@ void write_frame(Ring *r) int *q = r->other; q[0] = 7; } -// CHECK-DAG: error: verification failed: write_frame [{{.*}}::frame@[[@LINE-2]]:8] +// CHECK-DAG: :[[@LINE-2]]:8: error: verification failed: write_frame [frame] // An operand that `&&`, `||`, or `?:` evaluates only on a path of its own // forms its pointer, and checks it, only there. @@ -436,7 +440,7 @@ void put_past_slot(Ring *r, int i, int v) int *q = i < 4 ? &r->buf[i] : &r->buf[7]; q[1] = v; } -// CHECK-DAG: error: verification failed: put_past_slot [{{.*}}::bounds@[[@LINE-2]]:3 +// CHECK-DAG: :[[@LINE-2]]:3: error: verification failed: put_past_slot [bounds] #else // A pointer that left the function would lose its bounds. struct Holder { diff --git a/clang/test/Verify/suite/switch_statements.cpp b/clang/test/Verify/suite/switch_statements.cpp index b22ec47786..783bad1f92 100644 --- a/clang/test/Verify/suite/switch_statements.cpp +++ b/clang/test/Verify/suite/switch_statements.cpp @@ -50,7 +50,8 @@ int fall_wrong(int x) } return r; } -// CHECK-DAG: error: verification failed: fall_wrong [{{.*}}::postcondition@[[@LINE-14]]:{{.*}}x [ssa=x_0] [type=i32] = 0{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-14]]:{{[0-9]+}}: error: verification failed: fall_wrong [postcondition] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 0{{( ->|,|$)}} // The default label may stand anywhere; it is reached only when no case // matches, and falls through like any other. @@ -90,7 +91,8 @@ int middle_default_wrong(int x) } return r; } -// CHECK-DAG: error: verification failed: middle_default_wrong [{{.*}}x [ssa=x_0] [type=i32] = 6{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: middle_default_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 6{{( ->|,|$)}} // Without a default label, a value no case matches runs nothing. int no_match(int x) @@ -157,7 +159,8 @@ int color_value_wrong(Color c) } return 0; } -// CHECK-DAG: error: verification failed: color_value_wrong [{{.*}}c [ssa=c_0] [type=u32] = 3{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: color_value_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}c = 3{{( ->|,|$)}} // Case values keep the width of the condition. int big_case(long long x) @@ -182,7 +185,8 @@ int big_case_wrong(long long x) return 0; } } -// CHECK-DAG: error: verification failed: big_case_wrong [{{.*}}x [ssa=x_0] [type=i64] = 5000000000{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: big_case_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 5000000000{{( ->|,|$)}} // A GNU case range matches every value from its first to its last. int digit_class(int x) @@ -207,7 +211,8 @@ int digit_class_wrong(int x) return 0; } } -// CHECK-DAG: error: verification failed: digit_class_wrong [{{.*}}x [ssa=x_0] [type=i32] = 9{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: digit_class_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 9{{( ->|,|$)}} // A break in a nested statement leaves the switch; the statements after it // run only where it did not. @@ -246,7 +251,8 @@ int nested_break_wrong(int x, int y) } return r; } -// CHECK-DAG: error: verification failed: nested_break_wrong [{{.*}}x [ssa=x_0] [type=i32] = 1{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: nested_break_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 1{{( ->|,|$)}} // A break in a loop inside a switch leaves the loop; a continue in a switch // inside a loop continues the loop. @@ -406,7 +412,8 @@ int nested_wrong(int a, int b) } return r; } -// CHECK-DAG: error: verification failed: nested_wrong [{{.*}}a [ssa=a_0] [type=i32] = 1{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: nested_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}a = 1{{( ->|,|$)}} // Statements before the first label never run; a declaration there is in // scope after it. @@ -503,7 +510,8 @@ int overflow_in_case(int x) return 0; } } -// CHECK-DAG: error: verification failed: overflow_in_case [{{.*}}::overflow@[[@LINE-5]]:{{.*}}x [ssa=x_0] [type=i32] = 1{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-5]]:{{[0-9]+}}: error: verification failed: overflow_in_case [overflow] {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 1{{( ->|,|$)}} // A spec, a lifted constexpr helper, a proof function, and ghost code may // switch too. @@ -542,7 +550,8 @@ int uses_select_wrong(int x) { return 6; } -// CHECK-DAG: error: verification failed: uses_select_wrong [{{.*}}x [ssa=x_0] [type=i32] = 0{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: uses_select_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 0{{( ->|,|$)}} constexpr int digit_value(char c) { switch (c) { @@ -569,7 +578,8 @@ int uses_digit_value_wrong(char c) { return digit_value(c); } -// CHECK-DAG: error: verification failed: uses_digit_value_wrong [{{.*}}c [ssa=c_0] [type=i8] = 48{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: uses_digit_value_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}c = 48{{( ->|,|$)}} cv::proof int positive_choice(int x) cv::post(cv::result > 0) @@ -620,7 +630,8 @@ int ghosted_wrong(int x) } return x; } -// CHECK-DAG: error: verification failed: ghosted_wrong [{{.*}}x [ssa=x_0] [type=i32] = 3{{[,;]}}{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: ghosted_wrong {{.*}}[reason=counterexample] +// CHECK-DAG: note: counterexample: {{.*}}x = 3{{( ->|,|$)}} // A local lent to a callee ends where a break leaves its block. void set(int *p, int v) @@ -678,7 +689,7 @@ int assumed(int x) [[assume(x > 0)]]; return x; } -// CHECK-DAG: error: verification failed: assumed [{{.*}}::assertion@[[@LINE-3]]:{{.*}}[reason=counterexample] +// CHECK-DAG: :[[@LINE-3]]:{{[0-9]+}}: error: verification failed: assumed [assertion]{{.*}}[reason=counterexample] int lent_directly(int x) cv::post(true) diff --git a/clang/test/Verify/suite/trusted_declarations.cpp b/clang/test/Verify/suite/trusted_declarations.cpp index 8440c5a3ce..c19b43b300 100644 --- a/clang/test/Verify/suite/trusted_declarations.cpp +++ b/clang/test/Verify/suite/trusted_declarations.cpp @@ -44,7 +44,8 @@ int uses_above(int x) return above(x); } -// CHECK: trusted_declarations.cpp:13:28: error: verification failed: clamp100 [{{.*}}::postcondition@13:28] (counterexample: {{.*}}result [ssa={{.*}}] [type=i32] = 101 +// CHECK: trusted_declarations.cpp:13:28: error: verification failed: clamp100 [postcondition] +// CHECK: note: counterexample: {{.*}}result = 101{{( ->|,|$)}} // CHECK: Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which is not established) // CHECK: Trusted: halved (contract assumed, not verified) // CHECK: Trusted: above (contract assumed, not verified) diff --git a/clang/test/Verify/suite/unbounded_quantifiers.cpp b/clang/test/Verify/suite/unbounded_quantifiers.cpp index 05155a3a0e..d2d7551e16 100644 --- a/clang/test/Verify/suite/unbounded_quantifiers.cpp +++ b/clang/test/Verify/suite/unbounded_quantifiers.cpp @@ -25,7 +25,7 @@ void false_over_integers() { cppverify::check(cppverify::forall(k, 2 * k + 1 > 0)); } -// CHECK-DAG: error: verification failed: false_over_integers [{{.*}}::assertion@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: false_over_integers [assertion]{{.*}}[reason=counterexample] void true_over_range() { @@ -47,7 +47,7 @@ void cell_claim(int *p) { cppverify::check(cppverify::forall(k, k < 0 || k > 1 || p[k] == 3)); } -// CHECK-DAG: error: verification failed: cell_claim [{{.*}}::assertion@{{.*}}[reason=counterexample] +// CHECK-DAG: error: verification failed: cell_claim [assertion]{{.*}}[reason=counterexample] void cell_claim_holds(int *p) cppverify::pre(valid(p, 4) && p[0] == 3) diff --git a/clang/test/Verify/suite/uncontracted_assertions.cpp b/clang/test/Verify/suite/uncontracted_assertions.cpp index 8345b57c6c..08a38c9b6c 100644 --- a/clang/test/Verify/suite/uncontracted_assertions.cpp +++ b/clang/test/Verify/suite/uncontracted_assertions.cpp @@ -37,7 +37,8 @@ int plain(int n) return n + 1; } -// CHECK-DAG: verification failed: asserts_false [{{.*}}::assertion@ -// CHECK-DAG: verification failed: ghost_false [{{.*}}::assertion@ -// CHECK-DAG: verification failed: asserts_argument [{{.*}}::assertion@{{.*}}n [ssa=n_0] [type=i32] = {{-?[0-9]+}} +// CHECK-DAG: verification failed: asserts_false [assertion] +// CHECK-DAG: verification failed: ghost_false [assertion] +// CHECK-DAG: verification failed: asserts_argument [assertion] +// CHECK-DAG: note: counterexample: {{.*}}n = {{-?[0-9]+}}{{( ->|,|$)}} // CHECK-DAG: Verified: loop_invariant diff --git a/clang/test/Verify/suite/valid_builtin.cpp b/clang/test/Verify/suite/valid_builtin.cpp index 7a07662fba..8d61e4a8ef 100644 --- a/clang/test/Verify/suite/valid_builtin.cpp +++ b/clang/test/Verify/suite/valid_builtin.cpp @@ -55,7 +55,7 @@ int past_end(const int *a, int n) { return a[n]; } -// CHECK-DAG: error: verification failed: past_end [{{.*}}::bounds@[[@LINE-2]]:10] +// CHECK-DAG: :[[@LINE-2]]:10: error: verification failed: past_end [bounds] // A user-declared valid works the same way. cppverify::spec bool valid(const long *p, int n) { return true; } diff --git a/clang/test/Verify/suite/z3_exec_recursion.cpp b/clang/test/Verify/suite/z3_exec_recursion.cpp index 06413e94ad..db591966e6 100644 --- a/clang/test/Verify/suite/z3_exec_recursion.cpp +++ b/clang/test/Verify/suite/z3_exec_recursion.cpp @@ -22,7 +22,7 @@ int invalid_nonterminating_exec(int n) } // VERIFY-DAG: Verified: valid_countdown -// VERIFY-DAG: error: verification failed: invalid_nonterminating_exec [{{.*}}::termination@ +// VERIFY-DAG: error: verification failed: invalid_nonterminating_exec [termination] // LOWER-DAG: Lowered: valid_countdown // LOWER-DAG: Lowered: invalid_nonterminating_exec diff --git a/clang/test/Verify/suite/z3_parameter_rebinding.cpp b/clang/test/Verify/suite/z3_parameter_rebinding.cpp index 924a393300..f67e990f33 100644 --- a/clang/test/Verify/suite/z3_parameter_rebinding.cpp +++ b/clang/test/Verify/suite/z3_parameter_rebinding.cpp @@ -45,7 +45,8 @@ int relies_on_force_zero(int value) return 0; } -// VERIFY-DAG: error: verification failed: force_zero {{.*}}(counterexample: value [ssa=value_0] [type=i32] = {{-?[1-9][0-9]*}} +// VERIFY-DAG: error: verification failed: force_zero {{.*}} +// VERIFY-DAG: note: counterexample: {{.*}}value = {{-?[1-9][0-9]*}}{{( ->|,|$)}} // VERIFY-DAG: error: verification failed: proof_force_zero // VERIFY-DAG: Verified: doubled // VERIFY-DAG: Verified: valid_after_rebinding diff --git a/clang/test/Verify/suite/z3_references_fail.cpp b/clang/test/Verify/suite/z3_references_fail.cpp index b756d4bf17..ea08e9b704 100644 --- a/clang/test/Verify/suite/z3_references_fail.cpp +++ b/clang/test/Verify/suite/z3_references_fail.cpp @@ -105,5 +105,5 @@ void implicit_effect_in_loop(int &value) // VERIFY-DAG: error: verification failed: alias_without_permission // VERIFY-DAG: error: verification failed: nullable_reference_actual // VERIFY-DAG: error: verification failed: implicit_effect_exceeds_frame -// VERIFY-DAG: error: verification failed: nonterminating_reference_store [{{.*}}::termination@ +// VERIFY-DAG: error: verification failed: nonterminating_reference_store [termination] // VERIFY-DAG: error: verification failed: implicit_effect_in_loop diff --git a/clang/test/Verify/suite/z3_spec_decreases_sound.cpp b/clang/test/Verify/suite/z3_spec_decreases_sound.cpp index 8018be74d1..ee8cdc3b56 100644 --- a/clang/test/Verify/suite/z3_spec_decreases_sound.cpp +++ b/clang/test/Verify/suite/z3_spec_decreases_sound.cpp @@ -49,4 +49,4 @@ cppverify::proof void nonterminating_proof(int n) // VERIFY-DAG: Verified: spec decreases: terminating_after_branch // VERIFY-DAG: error: spec decreases failed: nonterminating_descent // VERIFY-DAG: error: spec decreases failed: nonterminating_after_branch -// VERIFY-DAG: error: verification failed: nonterminating_proof [{{.*}}::termination@ +// VERIFY-DAG: error: verification failed: nonterminating_proof [termination] diff --git a/clang/test/Verify/suite/z3_undefined_behavior.cpp b/clang/test/Verify/suite/z3_undefined_behavior.cpp index cad4c1566e..12ba85f388 100644 --- a/clang/test/Verify/suite/z3_undefined_behavior.cpp +++ b/clang/test/Verify/suite/z3_undefined_behavior.cpp @@ -172,7 +172,7 @@ int unsafe_min_remainder(int x) // VERIFY-DAG: error: verification failed: unsafe_overflow_after_spec_result // VERIFY-DAG: error: verification failed: unsafe_overflow_after_spec_initialization // VERIFY-DAG: error: verification failed: unsafe_overflow_after_spec_assignment -// VERIFY-DAG: error: verification failed: invalid_out_of_range_spec_result [{{.*}}::overflow@ +// VERIFY-DAG: error: verification failed: invalid_out_of_range_spec_result [overflow] // VERIFY-DAG: error: verification failed: unsafe_signed_subtract // VERIFY-DAG: error: verification failed: unsafe_signed_multiply // VERIFY-DAG: error: verification failed: unsafe_signed_negation diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py index 077ccadd63..a350471cea 100644 --- a/clang/test/lit.cfg.py +++ b/clang/test/lit.cfg.py @@ -437,6 +437,11 @@ def calculate_arch_features(arch_string): if lit.util.which("cvc5", config.environment["PATH"]): config.available_features.add("cvc5") +# The Z3 shell of the Z3 cpp-verify links, for scripts cpp-verify writes. +if config.cppverify_z3_shell and os.path.exists(config.cppverify_z3_shell): + config.available_features.add("z3-shell") + config.substitutions.append(("%cppverify-z3", config.cppverify_z3_shell)) + if config.enable_backtrace: config.available_features.add("backtrace") diff --git a/clang/test/lit.site.cfg.py.in b/clang/test/lit.site.cfg.py.in index cb35118167..fd47295d88 100644 --- a/clang/test/lit.site.cfg.py.in +++ b/clang/test/lit.site.cfg.py.in @@ -26,6 +26,7 @@ config.clang_default_cxx_stdlib = "@CLANG_DEFAULT_CXX_STDLIB@" config.clang_staticanalyzer = @CLANG_ENABLE_STATIC_ANALYZER@ config.clang_staticanalyzer_z3 = @LLVM_WITH_Z3@ config.clang_staticanalyzer_z3_mock = @TEST_WITH_Z3_MOCK@ +config.cppverify_z3_shell = r"@CPPVERIFY_Z3_SHELL@" config.clang_enable_cir = @CLANG_ENABLE_CIR@ config.clang_examples = @CLANG_BUILD_EXAMPLES@ config.llvm_examples = @LLVM_INCLUDE_EXAMPLES@ diff --git a/clang/tools/cpp-verify/Main.cpp b/clang/tools/cpp-verify/Main.cpp index 5cb8cdb83f..0960081591 100644 --- a/clang/tools/cpp-verify/Main.cpp +++ b/clang/tools/cpp-verify/Main.cpp @@ -3,9 +3,12 @@ #include "../../lib/Verify/Backend/ObligationSerialization.h" #include "../../lib/Verify/Backend/ObligationSimplify.h" #include "../../lib/Verify/Backend/ProofCache.h" +#include "../../lib/Verify/Backend/QueryDump.h" #include "../../lib/Verify/Backend/VerifyBackend.h" +#include "Counterexample.h" #include "CppVerifyVersion.h" #include "DumpIR.h" +#include "StructuredOutput.h" #include "Verifier.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" @@ -13,6 +16,7 @@ #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" @@ -47,6 +51,12 @@ static cl::opt cl::value_desc("layers"), cl::ValueOptional, cl::cat(CppVerifyCategory)); +static cl::opt DumpSMT2( + "dump-smt2", + cl::desc("Write every query sent to a solver to this directory, as a " + "script the solver replays on its own"), + cl::value_desc("directory"), cl::cat(CppVerifyCategory)); + static cl::opt LowerOnly( "lower-only", cl::desc("Run through backend encoding without checking satisfiability"), @@ -267,13 +277,51 @@ static cl::opt ObligationIn( cl::desc("Validate and replay a backend-neutral obligation archive"), cl::value_desc("file"), cl::cat(CppVerifyCategory)); -static cl::opt DiagnosticsFormat( - "diagnostics-format", cl::desc("Verification diagnostics format"), - cl::values(clEnumValN(verify::DiagnosticFormat::Text, "text", - "Human-readable text (default)"), - clEnumValN(verify::DiagnosticFormat::Json, "json", - "Versioned JSON Lines")), - cl::init(verify::DiagnosticFormat::Text), cl::cat(CppVerifyCategory)); +enum class OutputFormat { Text, Json, Sarif, GitHub }; + +static cl::opt DiagnosticsFormat( + "diagnostics-format", cl::desc("Format of results and diagnostics"), + cl::values( + clEnumValN(OutputFormat::Text, "text", "Human-readable text (default)"), + clEnumValN(OutputFormat::Json, "json", + "JSON Lines (cppverify.diagnostic/2), ending with a " + "summary record"), + clEnumValN(OutputFormat::Sarif, "sarif", + "One SARIF 2.1.0 log at the end of the run"), + clEnumValN(OutputFormat::GitHub, "github", + "Text, with a GitHub workflow annotation for each error and " + "warning")), + cl::init(OutputFormat::Text), cl::cat(CppVerifyCategory)); + +/// Whether results are records: JSON Lines, or the SARIF made of them. +static bool structuredOutput() { + return DiagnosticsFormat == OutputFormat::Json || + DiagnosticsFormat == OutputFormat::Sarif; +} + +static verify::DiagnosticFormat verifierFormat() { + switch (DiagnosticsFormat) { + case OutputFormat::Text: + return verify::DiagnosticFormat::Text; + case OutputFormat::Json: + case OutputFormat::Sarif: + return verify::DiagnosticFormat::Json; + case OutputFormat::GitHub: + return verify::DiagnosticFormat::GitHub; + } + return verify::DiagnosticFormat::Text; +} + +// In SARIF mode the records are collected here and translated at the end. +static std::string gRecords; +static llvm::raw_string_ostream gRecordStream(gRecords); + +/// Where results go: standard output, or the records of a SARIF log. +static llvm::raw_ostream &out() { + if (DiagnosticsFormat == OutputFormat::Sarif) + return gRecordStream; + return llvm::outs(); +} static cl::opt Quiet("quiet", @@ -375,6 +423,53 @@ static void printSummary(std::chrono::steady_clock::time_point Start) { : llvm::join(Parts, ", ")) << llvm::formatv(" in {0:f1} s", Seconds) << "\n"; } +/// The summary as a record: the counts of the text line, by name. It has no +/// time, so that records do not depend on the run; --time-report gives it. +static void printSummaryRecord() { + llvm::json::Object Record; + Record["schema"] = verify::SummarySchema; + Record["verified"] = gSummary.Verified; + Record["failed"] = gSummary.Failed; + Record["unresolved"] = gSummary.Unresolved; + llvm::json::Object Reasons; + for (const auto &[Reason, N] : gSummary.Reasons) + Reasons[Reason] = N; + Record["reasons"] = std::move(Reasons); + Record["bounded_safe"] = gSummary.BoundedSafe; + Record["trusted"] = gSummary.Trusted; + Record["lowered"] = gSummary.Lowered; + Record["exported"] = gSummary.Exported; + Record["interrupted"] = gSummary.Interrupted; + out() << llvm::formatv("{0}\n", llvm::json::Value(std::move(Record))); +} + +static std::vector gArguments; + +/// Ends the run's output: in SARIF mode, the log of the records collected. +static int finishOutput(int ExitCode) { + if (std::string Error = verify::queryDumpError(); !Error.empty()) + llvm::errs() << "warning: --dump-smt2: " << Error << "\n"; + if (DiagnosticsFormat != OutputFormat::Sarif) + return ExitCode; + gRecordStream.flush(); + std::vector Records; + for (llvm::StringRef Line : llvm::split(gRecords, '\n')) { + if (Line.trim().empty()) + continue; + if (llvm::Expected Record = llvm::json::parse(Line)) + Records.push_back(std::move(*Record)); + else + llvm::consumeError(Record.takeError()); + } + verify::SarifInvocation Invocation; + Invocation.Version = verify::releaseVersion().str(); + Invocation.Arguments = gArguments; + Invocation.ExitCode = ExitCode; + llvm::outs() << llvm::formatv("{0:2}\n", + verify::sarifLog(Records, Invocation)); + return ExitCode; +} + static llvm::raw_ostream *gObligationOut = nullptr; static uint64_t proofCacheMaxBytes() { @@ -451,9 +546,13 @@ static void setLeanOptions(verify::VerifyOptions &VOpts) { VOpts.LeanJobs = Jobs.getNumOccurrences() ? Jobs.getValue() : 0; } +static void flushFrontendRecords(); + class VerifyConsumer : public ASTConsumer { public: void HandleTranslationUnit(ASTContext &Ctx) override { + // The compiler's records come before the verifier's. + flushFrontendRecords(); if (!Ctx.getDiagnostics().getClient()->getNumErrors()) { verify::VerifyOptions VOpts; if (DumpIR.getNumOccurrences() > 0) @@ -490,7 +589,7 @@ class VerifyConsumer : public ASTConsumer { VOpts.ProofCacheMaxEntries = ProofCacheMaxEntries.getValue(); VOpts.CheckUB = CheckUB.getValue() && !NoCheckUB.getValue(); VOpts.ProfileQuantifiers = ProfileQuantifiers.getValue(); - VOpts.Diagnostics = DiagnosticsFormat.getValue(); + VOpts.Diagnostics = verifierFormat(); VOpts.ObligationOut = gObligationOut; VOpts.Quiet = Quiet.getValue(); VOpts.Verbose = Verbose.getValue(); @@ -505,7 +604,7 @@ class VerifyConsumer : public ASTConsumer { VOpts.ConfirmBound = ConfirmBound.getValue(); VOpts.Summary = &gSummary; VOpts.CrossFile = gCrossFile.get(); - if (!verify::verifyTranslationUnit(Ctx, llvm::outs(), VOpts)) + if (!verify::verifyTranslationUnit(Ctx, out(), VOpts)) ++gVerifyFailures; } } @@ -520,12 +619,141 @@ class VerifyAction : public ASTFrontendAction { } }; +/// The compiler's diagnostics as the run's format takes them: records of +/// status "frontend" in JSON and SARIF, each with the notes that follow it as +/// related locations; and in GitHub mode the text, with an annotation after +/// each error and warning. +class StructuredDiagnostics : public DiagnosticConsumer { +public: + explicit StructuredDiagnostics(DiagnosticConsumer *Text) : Text(Text) {} + ~StructuredDiagnostics() override { flush(); } + + void BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP) override { + if (Text) + Text->BeginSourceFile(LangOpts, PP); + } + void EndSourceFile() override { + flush(); + if (Text) + Text->EndSourceFile(); + } + /// Writes the record still waiting for its notes. + void finish() { flush(); } + + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const Diagnostic &Info) override { + DiagnosticConsumer::HandleDiagnostic(Level, Info); + if (Level == DiagnosticsEngine::Ignored) + return; + llvm::SmallString<256> Message; + Info.FormatDiagnostic(Message); + verify::ObligationSource Source = sourceOf(Info); + if (Text) { + Text->HandleDiagnostic(Level, Info); + if (Level >= DiagnosticsEngine::Warning) + llvm::outs() << verify::githubCommand( + Level == DiagnosticsEngine::Warning ? "warning" : "error", Source, + "clang", Message); + return; + } + if (Level == DiagnosticsEngine::Note && Pending) { + llvm::json::Object Note; + Note["message"] = verify::jsonText(Message); + if (Source.isValid()) + Note["source"] = verify::sourceJSON(Source); + Related.push_back(std::move(Note)); + return; + } + flush(); + llvm::json::Object Record; + Record["schema"] = verify::DiagnosticSchema; + Record["status"] = "frontend"; + Record["severity"] = Level == DiagnosticsEngine::Remark ? "remark" + : Level == DiagnosticsEngine::Note ? "note" + : Level == DiagnosticsEngine::Warning ? "warning" + : "error"; + Record["message"] = verify::jsonText(Message); + llvm::StringRef Option = + Info.getDiags()->getDiagnosticIDs()->getWarningOptionForDiag( + Info.getID()); + if (!Option.empty()) + Record["option"] = ("-W" + Option).str(); + if (Source.isValid()) + Record["source"] = verify::sourceJSON(Source); + Pending = std::move(Record); + } + +private: + void flush() { + if (!Pending) + return; + if (!Related.empty()) + (*Pending)["related"] = std::move(Related); + out() << llvm::formatv("{0}\n", llvm::json::Value(std::move(*Pending))); + Pending.reset(); + Related = llvm::json::Array(); + } + + /// The diagnostic's source: its location, through its first range. + static verify::ObligationSource sourceOf(const Diagnostic &Info) { + verify::ObligationSource Source; + if (!Info.hasSourceManager() || Info.getLocation().isInvalid()) + return Source; + const SourceManager &SM = Info.getSourceManager(); + PresumedLoc At = SM.getPresumedLoc(Info.getLocation()); + if (!At.isValid()) + return Source; + Source.File = At.getFilename(); + Source.Line = Source.EndLine = At.getLine(); + Source.Column = Source.EndColumn = At.getColumn(); + if (Info.getNumRanges() == 0) + return Source; + CharSourceRange Range = Info.getRange(0); + SourceLocation Begin = SM.getExpansionLoc(Range.getBegin()); + SourceLocation End = SM.getExpansionLoc(Range.getEnd()); + if (Range.isTokenRange()) + End = Lexer::getLocForEndOfToken(End, 0, SM, LangOptions()); + PresumedLoc First = SM.getPresumedLoc(Begin); + PresumedLoc Last = SM.getPresumedLoc(End); + if (First.isValid() && Last.isValid() && + llvm::StringRef(First.getFilename()) == Source.File && + llvm::StringRef(Last.getFilename()) == Source.File) { + if (First.getLine() < Source.Line || + (First.getLine() == Source.Line && + First.getColumn() < Source.Column)) { + Source.BeginLine = First.getLine(); + Source.BeginColumn = First.getColumn(); + } + if (Last.getLine() > Source.Line || + (Last.getLine() == Source.Line && Last.getColumn() > Source.Column)) { + Source.EndLine = Last.getLine(); + Source.EndColumn = Last.getColumn(); + } + } + return Source; + } + + DiagnosticConsumer *Text; + std::optional Pending; + llvm::json::Array Related; +}; + +static StructuredDiagnostics *gStructured = nullptr; + +static void flushFrontendRecords() { + if (gStructured) + gStructured->finish(); +} + /// Verifies each file whose command line clang accepts. An argument error /// stops the file, as it stops clang; tooling would print it and go on. +/// Files report to Files when it is set, else with their own options. class VerifyActionFactory : public FrontendActionFactory { public: - explicit VerifyActionFactory(const DiagnosticConsumer &Arguments) - : Arguments(Arguments) {} + VerifyActionFactory(const DiagnosticConsumer &Arguments, + DiagnosticConsumer *Files) + : Arguments(Arguments), Files(Files) {} std::unique_ptr create() override { return std::make_unique(); } @@ -540,13 +768,13 @@ class VerifyActionFactory : public FrontendActionFactory { ArgumentErrors = Arguments.getNumErrors(); return false; } - // The file reports with its own diagnostic options. return FrontendActionFactory::runInvocation( - std::move(Invocation), Files, std::move(PCHContainerOps), nullptr); + std::move(Invocation), Files, std::move(PCHContainerOps), this->Files); } private: const DiagnosticConsumer &Arguments; + DiagnosticConsumer *Files; unsigned ArgumentErrors = 0; }; @@ -577,30 +805,15 @@ static llvm::StringRef replayStatusCode(verify::VerifyStatus Status) { return "unresolved"; } -static std::string jsonText(llvm::StringRef Text) { - return llvm::json::isUTF8(Text) ? Text.str() : llvm::json::fixUTF8(Text); -} - -static llvm::json::Object -replaySourceJSON(const verify::ObligationSource &Source) { - llvm::json::Object Result; - if (!Source.isValid()) - return Result; - Result["file"] = jsonText(Source.File); - Result["line"] = Source.Line; - Result["column"] = Source.Column; - Result["end_line"] = Source.EndLine != 0 ? Source.EndLine : Source.Line; - Result["end_column"] = - Source.EndColumn != 0 ? Source.EndColumn : Source.Column; - return Result; -} +using verify::jsonText; +using verify::sourceJSON; static void printReplayJSON(const verify::ObligationModule &Module, const verify::VerifyResult &Result, llvm::StringRef SemanticHash, unsigned LeanAutomatic) { llvm::json::Object Record; - Record["schema"] = "cppverify.diagnostic/1"; + Record["schema"] = verify::DiagnosticSchema; Record["status"] = replayStatusCode(Result.Status); Record["severity"] = Result.Status == verify::VerifyStatus::BoundedSafe ? "warning" @@ -609,9 +822,21 @@ static void printReplayJSON(const verify::ObligationModule &Module, ? "error" : "note"; Record["function"] = jsonText(Module.FunctionName); - Record["message"] = Result.Status == verify::VerifyStatus::Verified - ? ReplayProofNote - : jsonText(Result.Message); + std::string Message = Result.Message; + if (Result.Status == verify::VerifyStatus::Verified) { + Message = ReplayProofNote; + } else if (Result.Status == verify::VerifyStatus::Failed) { + Message = "verification failed: " + Module.FunctionName; + if (Result.ObligationType) + Message += std::string(" [") + + verify::obligationKindName(*Result.ObligationType) + "]"; + if (!Result.Message.empty()) + Message += " (" + Result.Message + ")"; + if (std::string Values = verify::counterexampleText(Result); + !Values.empty()) + Record["counterexample"] = jsonText(Values); + } + Record["message"] = jsonText(Message); Record["semantic_hash"] = ("sha256:" + SemanticHash).str(); if (!Result.BackendName.empty()) Record["backend"] = Result.BackendName; @@ -639,14 +864,14 @@ static void printReplayJSON(const verify::ObligationModule &Module, if (!Result.CacheError.empty()) Record["cache_error"] = jsonText(Result.CacheError); if (Result.Source.isValid()) - Record["source"] = replaySourceJSON(Result.Source); + Record["source"] = sourceJSON(Result.Source); if (!Result.ObligationId.empty()) { llvm::json::Object Obligation; Obligation["id"] = jsonText(Result.ObligationId); if (Result.ObligationType) Obligation["kind"] = verify::obligationKindName(*Result.ObligationType); if (Result.Source.isValid()) - Obligation["source"] = replaySourceJSON(Result.Source); + Obligation["source"] = sourceJSON(Result.Source); Record["obligation"] = std::move(Obligation); } if (!Result.Model.empty()) { @@ -659,7 +884,9 @@ static void printReplayJSON(const verify::ObligationModule &Module, Entry["value"] = Value.Value ? llvm::json::Value(jsonText(*Value.Value)) : llvm::json::Value(nullptr); if (Value.Source.isValid()) - Entry["source"] = replaySourceJSON(Value.Source); + Entry["source"] = sourceJSON(Value.Source); + if (Value.OnPath == false) + Entry["on_path"] = false; Model.push_back(std::move(Entry)); } Record["model"] = std::move(Model); @@ -681,10 +908,11 @@ static void printReplayJSON(const verify::ObligationModule &Module, : "branch"; Entry["kind"] = Kind; Entry["message"] = jsonText(Event.Message); + Entry["text"] = jsonText(verify::traceStepText(Event)); Entry["active"] = Event.Active ? llvm::json::Value(*Event.Active) : llvm::json::Value(nullptr); if (Event.Source.isValid()) - Entry["source"] = replaySourceJSON(Event.Source); + Entry["source"] = sourceJSON(Event.Source); llvm::json::Array Values; for (const verify::VerifyTraceValue &Value : Event.Values) { llvm::json::Object Item; @@ -700,7 +928,7 @@ static void printReplayJSON(const verify::ObligationModule &Module, } Record["trace"] = std::move(Trace); } - llvm::outs() << llvm::formatv("{0}\n", llvm::json::Value(std::move(Record))); + out() << llvm::formatv("{0}\n", llvm::json::Value(std::move(Record))); } /// Selects the backend that the only backend an option applies to, when no @@ -912,17 +1140,16 @@ static int replayObligationArchive() { setLeanOptions(VOpts); Project = verify::replayLeanProject(Modules, VOpts); for (const std::string &Warning : Project->Warnings) { - if (DiagnosticsFormat != verify::DiagnosticFormat::Json) { + if (!structuredOutput()) { llvm::outs() << "warning: " << Warning << "\n"; continue; } llvm::json::Object Record; - Record["schema"] = "cppverify.diagnostic/1"; + Record["schema"] = verify::DiagnosticSchema; Record["status"] = "warning"; Record["severity"] = "warning"; Record["message"] = jsonText(Warning); - llvm::outs() << llvm::formatv("{0}\n", - llvm::json::Value(std::move(Record))); + out() << llvm::formatv("{0}\n", llvm::json::Value(std::move(Record))); } if (!Project->Error.empty()) { llvm::errs() << "error: " << Project->Error << "\n"; @@ -964,7 +1191,7 @@ static int replayObligationArchive() { } const std::string Hash = verify::obligationSemanticHash(Module); - if (DiagnosticsFormat == verify::DiagnosticFormat::Json) { + if (structuredOutput()) { printReplayJSON(Module, Result, Hash, LeanAutomatic); if (Result.Status != verify::VerifyStatus::Lowered && Result.Status != verify::VerifyStatus::Verified && @@ -1032,11 +1259,26 @@ static int replayObligationArchive() { AllOk = false; printResultSource(); llvm::outs() << "error: verification failed: " << Module.FunctionName; - if (!Result.ObligationId.empty()) + if (Result.ObligationType) + llvm::outs() << " [" + << verify::obligationKindName(*Result.ObligationType) + << "]"; + else if (!Result.ObligationId.empty()) llvm::outs() << " [" << Result.ObligationId << "]"; if (!Result.Message.empty()) - llvm::outs() << " (counterexample: " << Result.Message << ")"; + llvm::outs() << " (" << Result.Message << ")"; llvm::outs() << Suffix << "\n"; + if (std::string Values = verify::counterexampleText(Result); + !Values.empty()) { + printResultSource(); + llvm::outs() << "note: counterexample: " << Values << "\n"; + } + for (const verify::VerifyTraceEvent &Event : Result.Trace) { + if (Event.Source.isValid()) + llvm::outs() << Event.Source.File << ":" << Event.Source.Line << ":" + << Event.Source.Column << ": "; + llvm::outs() << "note: " << verify::traceStepText(Event) << "\n"; + } break; case verify::VerifyStatus::Unresolved: AllOk = false; @@ -1084,6 +1326,7 @@ static int replayObligationArchive() { int main(int argc, const char **argv) { const auto Start = std::chrono::steady_clock::now(); llvm::InitLLVM X(argc, argv); + gArguments.assign(argv, argv + argc); // The first Ctrl-C stops the solvers and reports what is not finished; a // second one ends the process. llvm::sys::SetInterruptFunction(verify::requestInterrupt); @@ -1128,6 +1371,9 @@ int main(int argc, const char **argv) { CommonOptionsParser &OptionsParser = ExpectedParser.get(); if (!resolveOptions(!ObligationIn.empty())) return 1; + if (!DumpSMT2.empty()) + verify::setQueryDumpDirectory( + DumpSMT2, ("cpp-verify " + verify::releaseVersion()).str()); if (CheckContracts) return verify::checkContracts(OptionsParser.getSourcePathList(), llvm::outs()) != 0; @@ -1143,7 +1389,7 @@ int main(int argc, const char **argv) { "source files\n"; return 1; } - return replayObligationArchive(); + return finishOutput(replayObligationArchive()); } std::unique_ptr ObligationFile; @@ -1204,27 +1450,44 @@ int main(int argc, const char **argv) { std::unique_ptr ArgumentOptions = CreateAndPopulateDiagOpts({"cpp-verify"}); TextDiagnosticPrinter ArgumentDiagnostics(llvm::errs(), *ArgumentOptions); - Tool.setDiagnosticConsumer(&ArgumentDiagnostics); - VerifyActionFactory Factory(ArgumentDiagnostics); + // Records and annotations need every file to report to one consumer. + std::optional Structured; + if (DiagnosticsFormat != OutputFormat::Text) + Structured.emplace(DiagnosticsFormat == OutputFormat::GitHub + ? &ArgumentDiagnostics + : nullptr); + gStructured = Structured ? &*Structured : nullptr; + DiagnosticConsumer &Arguments = + Structured ? static_cast(*Structured) + : ArgumentDiagnostics; + Tool.setDiagnosticConsumer(&Arguments); + VerifyActionFactory Factory(Arguments, Structured ? &*Structured : nullptr); // Lean exports and --list-functions have no verdicts to settle. if (OptionsParser.getSourcePathList().size() > 1 && BackendOpt != "lean" && LeanFallback.empty() && !ListFunctions) gCrossFile = std::make_unique(); int RC = Tool.run(&Factory); if (gCrossFile) - gVerifyFailures += gCrossFile->finish(llvm::outs(), &gSummary); - if (DiagnosticsFormat == verify::DiagnosticFormat::Text && !ListFunctions) - printSummary(Start); + gVerifyFailures += gCrossFile->finish(out(), &gSummary); + if (Structured) + Structured->finish(); + gStructured = nullptr; + if (!ListFunctions) { + if (structuredOutput()) + printSummaryRecord(); + else + printSummary(Start); + } if (verify::interruptRequested()) - return 130; + return finishOutput(130); if (RC != 0) - return RC; + return finishOutput(RC); if (ObligationFile) { ObligationFile->flush(); if (ObligationFile->has_error()) { llvm::errs() << "error: cannot write obligation archive\n"; - return 1; + return finishOutput(1); } } - return gVerifyFailures > 0 ? 1 : 0; + return finishOutput(gVerifyFailures > 0 ? 1 : 0); } diff --git a/clang/unittests/Verify/CertifyTest.cpp b/clang/unittests/Verify/CertifyTest.cpp index 9769016bcf..4a6d325d54 100644 --- a/clang/unittests/Verify/CertifyTest.cpp +++ b/clang/unittests/Verify/CertifyTest.cpp @@ -967,6 +967,94 @@ TEST(CertifyTest, FreeSymbolsComeFromTheModel) { EXPECT_NE(Missing.Detail.find("x"), std::string::npos); } +TEST(CertifyTest, CounterexamplesAreDescribedAlongTheFailingPath) { + // int f(int x, bool c, bool d) { int y = 0; if (c) y = 7; else y = 3; + // if (d) {} return y; } with two obligations, the second failing. + const LogicSort I32 = LogicSort::bitVector(32, true); + ObligationModule Module; + auto declare = [&](llvm::StringRef Name, llvm::StringRef Display, + LogicSort Sort, Expr Guard) { + DiagnosticVariable Variable; + Variable.DisplayName = Display.str(); + Variable.Sort = Sort; + if (Guard) + Variable.Guard = LogicExprRef(std::move(Guard)); + Module.DiagnosticVariables.emplace(Name.str(), std::move(Variable)); + }; + auto c = [] { return variable("c_0", LogicSort::boolSort()); }; + declare("y_2", "y", I32, negation(c())); + declare("x_0", "x", I32, nullptr); + declare("y_1", "y", I32, c()); + declare("c_0", "c", LogicSort::boolSort(), nullptr); + auto event = [&](llvm::StringRef Message, Expr Guard) { + DiagnosticTraceEvent Event; + Event.Message = Message.str(); + Event.Guard = std::move(Guard); + Module.TraceEvents.push_back(std::move(Event)); + }; + event("then", c()); + event("else", negation(c())); + event("then", variable("d_0", LogicSort::boolSort())); + event("return", boolLiteral(true)); + Module.TraceEvents.back().Values.push_back( + {"value", node(LogicExpr::Ite, I32, c(), variable("y_1", I32), + variable("y_2", I32))}); + event("after", boolLiteral(true)); + auto query = [&] { + std::vector Terms; + Terms.push_back( + boolean(LogicExpr::Ne, variable("y_2", I32), literal("0", I32))); + Terms.push_back( + boolean(LogicExpr::Eq, variable("x_0", I32), literal("5", I32))); + Terms.push_back( + boolean(LogicExpr::Ne, variable("y_1", I32), literal("0", I32))); + Terms.push_back(c()); + return node(LogicExpr::And, LogicSort::boolSort(), std::move(Terms)); + }; + for (uint64_t Count : {1, 4}) { + Obligation Item; + Item.TraceEventCount = Count; + Item.Goal = negation(query()); + Item.CounterexampleQuery = query(); + Module.Obligations.push_back(std::move(Item)); + } + TableModel Model; + Model.Constants["x_0"] = integerValue(5); + Model.Constants["y_1"] = integerValue(7); + Model.Constants["y_2"] = integerValue(3); + Model.Constants["c_0"] = LogicValue::boolean(true); + + VerifyResult Result; + describeCounterexample(Module, *Module.Obligations[1].CounterexampleQuery, + Model, Result); + std::vector Names; + for (const VerifyModelValue &Value : Result.Model) + Names.push_back(Value.InternalName + "=" + Value.Value.value_or("?") + + (Value.OnPath ? (*Value.OnPath ? " on" : " off") : "")); + EXPECT_EQ(Names, (std::vector{"c_0=true", "x_0=5", "y_1=7 on", + "y_2=3 off"})); + // The path of the second obligation: its first four events, without the + // branch the model does not take, and one it does not decide marked so. + ASSERT_EQ(Result.Trace.size(), 3u); + EXPECT_EQ(Result.Trace[0].Message, "then"); + EXPECT_EQ(Result.Trace[0].Active, std::optional(true)); + EXPECT_EQ(Result.Trace[1].Message, "then"); + EXPECT_EQ(Result.Trace[1].Active, std::nullopt); + EXPECT_EQ(Result.Trace[2].Message, "return"); + ASSERT_EQ(Result.Trace[2].Values.size(), 1u); + EXPECT_EQ(Result.Trace[2].Values[0].Value, std::optional("7")); + // The goal's variables, as a Lean refutation states them. + ASSERT_TRUE(Result.Assignment); + EXPECT_EQ(Result.Assignment->Values.size(), 4u); + EXPECT_EQ(Result.Assignment->Values.at("y_1").key(), integerValue(7).key()); + + VerifyResult First; + describeCounterexample(Module, *Module.Obligations[0].CounterexampleQuery, + Model, First); + ASSERT_EQ(First.Trace.size(), 1u); + EXPECT_EQ(First.Trace[0].Message, "then"); +} + // --- Logical functions // -------------------------------------------------------- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4cb50ca1a3..5e65034f1c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -68,7 +68,7 @@ C++ source with contracts │ - Source names + types │ │ - Guarded event traces │ │ - Stable reason codes │ -│ - Text or JSON Lines │ +│ - Text, JSON, SARIF │ │ - Re-check recommends │ │ on failure → warnings │ └──────────────────────────┘ @@ -742,6 +742,22 @@ well-formedness once, in the head's arbitrary state, from the invariants before it, which suffices where storage is always initialized; here it is proved where the invariant is. +**Class invariants at loops.** The converter appends to every loop's +invariants (`ASTConverter::appendObjectInvariants`, after the user's) the +class invariants of the objects the loop can reach: each local object alive +at the loop (`AutomaticScopeStack`), at its address, and each object a +reference or pointer parameter of the function addresses, guarded by +`p == nullptr ||` for a pointer, skipping one with an extent as the entry +assumption does. Each is the invariant instantiated with `ThisSubstitute`, +as for a parameter at entry, and is checked at entry and preserved like any +other invariant, so it can make no false claim verify. Only the class's +members and code writing a public field change what an invariant states, +and a public member keeps it, so it holds at every iteration of a loop +outside the class; a member function's loops get none of their class's +invariants (`ConvertingDecl` names the function), since a member may break +one between calls. Without them the loop havoc forgot the invariant, and +the next public call failed its `type-invariant` check. + **Termination.** An executable loop without `cppverify::decreases` contributes no measure obligation, and the driver reports the function `Unresolved` with `decreases.missing` unless BMC proved the unwinding or the loop's condition is @@ -1124,7 +1140,7 @@ and obligation kinds. ### Canonical archives and semantic identity `--obligation-out=FILE` writes one or more deterministic -`cppverify.obligation/3` records after source attribution. The binary format has +`cppverify.obligation/5` records after source attribution. The binary format has the `CPVOBL\r\n` magic, an explicit schema version, little-endian fixed-width integers, length-delimited strings/collections, and stable hand-assigned wire tags rather than C++ enum ordinals. The reader bounds strings, collections, @@ -1148,9 +1164,16 @@ functions a measure applies, so a module never names one it does not declare. Before, a spec only a measure applied was removed, and the measure could not be printed. -Schema v2 adds the precise obligation kinds, and schema v3 the measures and -premises; readers still read schema v2 and schema v1, whose records name -only assertions, postconditions, and unwinding checks. Records +Schema v2 adds the precise obligation kinds, schema v3 the measures and +premises, and schema v4, with the diagnostic metadata, where each variable +version is defined (`DiagnosticVariable::Guard`); readers still read schemas +v3, v2, and v1, whose records name only assertions, postconditions, and +unwinding checks. A record before v4 gives no version a guard, so its +counterexamples list every version, as before 0.2. Schema v5 adds, after each +source's end, the begin of its range (`ObligationSource::BeginLine`, +`BeginColumn`); from v5 the end is the column after the last character +(`Lexer::getLocForEndOfToken`), where before it was the first column of the +last token, and a record before v5 keeps its end as written. Records limit strings to 64 MiB, collections and expression nodes/edges to 100,000 per record, expression nesting to 4096, canonical integer numerals to 4096 decimal characters, and integer sorts to 4096 bits. @@ -2453,7 +2476,20 @@ Passivization also records guarded branch, modular-call, loop, heap-write, allocation, lifetime-end, deletion, and return events. Each obligation owns the event prefix that existed when it was emitted. The Z3 model removes events with false guards, retains true events, and marks undetermined guards explicitly -unknown. Addresses, stored values, allocation identities, and return values use +unknown. + +cvc5's models are read by the adapter, not by a solver API, so the certifier +describes them (`describeCounterexample`, `Certify.cpp`) once it has certified +one: in the model it checked, `evaluateTerm` gives each source variable of the +obligation's query (`candidateValues`, listed by internal name, the order of +Z3's models), the guard of each version (`OnPath`), each of the obligation's +trace events up to its `TraceEventCount` (dropped when the guard is false, +marked undetermined when the model does not decide it) and its values, and +the assignment of the goal's variables that a Lean refutation states. The +evaluation is the certifier's own, the semantics the counterexample was +certified in, so a value shown is one the check used; it never completes the +model. cvc5 solves each obligation alone, so the query is always one +obligation's. Addresses, stored values, allocation identities, and return values use the same typed, non-completing evaluation. A complete-query counterexample is attributed to a specific ordered obligation whenever its model evaluates that obligation's counterexample query to true. @@ -2461,12 +2497,64 @@ obligation's counterexample query to true. Public obligation identities are source anchored: `function-identity::kind@line:column`, with `#N` only for obligations sharing one anchor. This keeps later IDs stable when an unrelated obligation is inserted -or reordered without moving the source anchor. The ordinary diagnostic is -Clang-style text; `--diagnostics-format=json` emits one -`cppverify.diagnostic/1` JSON object per verification result, including source +or reordered without moving the source anchor; JSON records carry it, and text +names only the kind (`[overflow]`), since the location prefixes the line. + +Every result is described once as Clang diagnostics (`Verifier::partsOf`, +`VerifyDiagnosticPart`): an error at the failing check, with notes; a +warning; or a verdict's status line, an error when unresolved and otherwise +a remark. The error's caret is the obligation's location and its range the +expression it checks when that lies on the caret's line: every `VExpr` +carries the start of its source (`BeginLoc`, Clang's `getBeginLoc`) beside +`Loc` (`getExprLoc`, an operator's position) and `EndLoc`, and lowering keeps +it to `Obligation::BeginLoc` and `VerifyResult::BeginLocation`. A failure's +notes are its counterexample at the check (`counterexampleText`: values by +display name, ordered by the declaration's position with `result` last, each +variable with the values its SSA versions take in version order, `0 -> 1`, a +version that keeps the value before it adding none, and a reference shown as +`&r`, since its value is the address it refers to) and each trace event at +its source location (`traceStepText`, in the static analyzer's words: "takes +the false branch", "returns 151"; a path of more than `TraceStepLimit` steps +keeps its first and last five, as `-ftemplate-backtrace-limit` does), then its +own notes. A note inside a macro is placed at the macro's use: the error +already shows the expansion. Only the versions the path defines count: +passivization records with each version of a source variable the path +condition active where it is defined (`PassiveModelVariable::Guard`, lowered +to `DiagnosticVariable::Guard`, which archives do not record), Z3 evaluates +it in the model without completion (`VerifyModelValue::OnPath`, JSON +`"on_path": false`), and a version whose condition is false is left out: it +equals the version before it there, so listing it showed `y = 0 -> 1 -> 0 -> +1` for a `y` that never returned to 0. A store, a deletion, and a return are +traced after the checks of their own operation, so a check that fails there +does not list the operation as a step taken. A failed result's +`Message` explains only what its model and trace do not, such as a +confirmation by proof; the values are never rendered into it. The standalone +tool prints errors and warnings with `TextDiagnostic`, source line and caret +included, and verdicts as one line each; the compile path reports every part +through the compiler's `DiagnosticsEngine` (see Compile-Time Integration). +`--diagnostics-format=json` emits one +`cppverify.diagnostic/2` JSON object per verification result, including source ranges, typed model values, trace events, backend/bound, and a stable reason -code. Command-line/frontend errors and output combined with IR dumps are not a -pure JSON stream. A construct the converter cannot represent is a +code. A source (`ObligationSource`, annotated by `Verifier::sourceOf`) keeps +the anchor that stable IDs and carets use (`Line`, `Column`) and spans its +expression from `BeginLoc` to the end of its last token; records print the +span. Every record of a function carries its declaration +(`VerifyDiagnostic::FunctionLoc`, also for conversion errors through +`ConversionError::FunctionLoc`), and a failure the counterexample and step +texts the text output prints. In `cpp-verify` the compiler's diagnostics go to +`StructuredDiagnostics`, which writes them as records of status `frontend` +with their notes as `related`, before the verifier's records of the file, and +a `cppverify.summary/1` record ends the run. No record holds a time, so the +stream is the same at any `--jobs`. `--diagnostics-format=sarif` writes the +same records into a buffer (`out()` in `Main.cpp`) and translates them at the +end (`sarifLog`, `Driver/StructuredOutput.cpp`): a result per record, its +SARIF `kind` from the status as Frama-C maps property statuses (pass, fail, +open), its rule the failing check's kind, the trace a code flow, and the +summary a property of the run. The logs of `structured_output.test`, of +`inductive_counterexamples.cpp`, and of a replay validate against the SARIF +2.1.0 schema. `--diagnostics-format=github` is the text with a +workflow command (`githubCommand`) after each error and warning. Output +combined with IR dumps is not a pure JSON stream. A construct the converter cannot represent is a `ConversionError` at a source range: the innermost declaration, statement, or expression being converted (`ASTConverter::CurrentRange`), unless the site names a place of its own, such as a function for the checks made after every @@ -2641,7 +2729,7 @@ The driver selects a backend via `VerifyOptions` (`Verifier.h` / `cpp-verify --b | Backend | Implementation | Notes | |---------|----------------|-------| | **Z3** | `Z3VerifyBackend` | Default. Consumes `ObligationModule`; counterexamples come from models. | -| **cvc5** | `CVC5VerifyBackend` + standalone SMT-LIB2 | Encodes the same canonical sorts, C++ truncating math division/remainder, bit-vectors, signed overflow, total heap, bounded and unbounded quantifiers (marked triggers as `:pattern`), heap frames, sequences (`full-saturate-quant` when sequences meet quantifiers), and finite ground spec equations. Sets, multisets, and maps are arrays over all integers, as for Z3 (cvc5's own set theory is finite, which would prove facts false of an infinite set): `(Array Int Bool)`, `(Array Int Int)` with a count of `max(0, cell)` and equality count by count, and `(Array Int cppverify.option)` over a declared `none | some(value)` datatype; union, intersection, and difference are declared functions each defined pointwise by one quantified axiom, exact by array extensionality, and subset is a quantified formula. A model cvc5 prints with `unknown` is certified like one after `sat`. Solver process failures and malformed output are unresolved. | +| **cvc5** | `CVC5VerifyBackend` + standalone SMT-LIB2 | Encodes the same canonical sorts, C++ truncating math division/remainder, bit-vectors, signed overflow, total heap, bounded and unbounded quantifiers (marked triggers as `:pattern`), heap frames, sequences (`full-saturate-quant` when sequences meet quantifiers), and finite ground spec equations. Sets, multisets, and maps are arrays over all integers, as for Z3 (cvc5's own set theory is finite, which would prove facts false of an infinite set): `(Array Int Bool)`, `(Array Int Int)` with a count of `max(0, cell)` and equality count by count, and `(Array Int cppverify.option)` over a declared `none | some(value)` datatype; union, intersection, and difference are declared functions each defined pointwise by one quantified axiom, exact by array extensionality, and subset is a quantified formula. A model cvc5 prints with `unknown` is certified like one after `sat`, and a certified one is described in source terms as Z3's is (`describeCounterexample`). Solver process failures and malformed output are unresolved. | | **Strict portfolio** | `PortfolioVerifyBackend` | Runs ordered Z3 and cvc5 queries. Matching UNSAT proves; matching certified counterexamples fail and retain the Z3 model; disagreement or an unresolved side is unresolved. | | **Race** | `RaceVerifyBackend` | Runs the Z3 and cvc5 backends at once; the first proof or certified counterexample stands and cancels the other (`cancel`/`resume`: a backend-wide `Race` of Z3 encoders, each forwarding an interrupt to the pass it runs, and a flag the cvc5 process loop polls). Otherwise the obligations either proved are joined, `Verified` (`z3+cvc5`) when they cover the module. | | **BMC** | `LoopUnroll` on VCR, then shared obligation/Z3 path | Source verification grows bounds from zero through `--unroll=N`, stopping on a counterexample, complete unwinding, unresolved query, or the maximum frontier. Safety with failed unwinding is `BoundedSafe(N)`; only proved unwinding is `Verified`. A spec's checks have no loops: the driver solves them with a Z3 backend (`SpecBackend`), as on the default path, and keeps the bound on their archived modules for replay. | @@ -3018,6 +3106,11 @@ fail-closed; so does the decay of an array member through a pointer (`ASTConverter::arrayDecayError` names which). Automatic metadata transitions are private and a modular caller may frame them away. +In a contract, `&x` of a local object in memory (`AddressableLocals`) is +its address (`convertAutomaticLocalAddress`), which a spec it is passed to +cannot keep and which compares as a value; elsewhere, and in ghost code, +which could keep it, the address of a local is an argument of a call only. + Local reference declarations snapshot their VCR address in a marked `VAssignStmt`. The marker survives cloning and BMC loop unrolling and lets the checked-scalar callee scan track immutable aliases transitively without @@ -3091,7 +3184,7 @@ semantics. ## Compile-Time Integration -`CppVerifyIntegration` (`clang/lib/Verify/Driver/CppVerifyIntegration.cpp`) hooks **CodeGen**: when `-fverify-contracts` is on and `-fno-verify` is off, verification runs asynchronously while LLVM IR is generated. Failures surface as `diag::err_fe_cppverify_failed`. Zero ghost/spec code in the object file. +`CppVerifyIntegration` (`clang/lib/Verify/Driver/CppVerifyIntegration.cpp`) hooks **CodeGen**: when `-fverify-contracts` is on and `-fno-verify` is off, verification runs asynchronously while LLVM IR is generated. The verifier's thread collects its results as `VerifyDiagnosticPart`s (`VerifyOptions::Parts`) instead of printing them, and `finishCppVerify` reports each through the compiler's `DiagnosticsEngine` on the main thread, which the engine requires: `err_cppverify_result` for failures and unresolved results, `warn_cppverify_result` in `-Wcppverify`, `remark_cppverify_result` in `-Rcppverify` for proofs, and `note_cppverify_result` for their notes. A result without a location of its own, as a proof has none, points at its function's declaration (`VerifyDiagnostic::FunctionLoc`). So `-Werror`, `-w`, colors, `-fdiagnostics-format=sarif`, and clangd see them as Clang's own; `err_fe_cppverify_failed` is reported only when the run fails without a result of its own. Zero ghost/spec code in the object file. ## Tooling Integration @@ -3165,6 +3258,33 @@ encoding error remains a hard failure; success is reported as `Lowered`, never `Verified`. This is intentionally distinct from Clang's `-fno-verify`, which stops before VCR conversion. +`cpp-verify --dump-smt2=DIR` writes every query sent to a solver as a +standalone SMT-LIB script (`Backend/QueryDump.cpp`): Z3's checks all go +through `Z3Encoder::check`, which writes `Z3_solver_to_string` of a solver +holding the checked solver's assertions (a solver that has checked before +also prints the model converter its preprocessing left, `model-add` of +symbols it never declares), after the options `freshSolver` gave it as +`set-option` commands and +followed by `(check-sat)`, or `(check-sat-using smt)` for a quantified query, +whose solver is the `smt` tactic's; Z3's benchmark printer (`to_smt2`) is not +used, since it leaves symbols such as the quantifier ids `q@line:col` +unquoted, which no parser reads. cvc5's checks go through `runCVC5`, and the +script it runs is written after its command-line options as `set-option` +commands (`cvc5Options` builds both). The script is written before the +check and the answer appended after it. A file is +`-
/.-.smt2`: the function's name made safe +for every file system and the start of the SHA-256 of its identity, the +obligation's stable id as `kind-line-col` (`whole` for the complete query), +and the start of the SHA-256 of the script, so a name depends only on the +query, as Frama-C keys its per-goal files and Verus its logs (Boogie's +`/proverLog` names by time), and the options are in the file, which Verus's +logs and Frama-C's files leave on the solver's command line. A replay builds +Z3's terms in another order than the encoder did, and Z3's search on a +quantified query depends on that order: the vacuity query of +`constructors.cpp`'s `Copy4` is `sat` in the encoder's context, and in a +fresh context, the API's or `z3`'s, the same assertions are `unknown` +(incomplete quantifiers). + ## Regression and Coverage - Tests: `clang/test/Verify/` (frontend) and `clang/test/Verify/suite/` diff --git a/docs/DESIGN.md b/docs/DESIGN.md index ea57b0106a..fde2459ae4 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1746,6 +1746,12 @@ For by-value parameters this is purely an optimization — correctness is identi checkpoint — matching how Dafny/Verus check constructors. - Pointer-to-record parameters are not injected (their field access lowers to a heap Load). +- On classes with member functions, a loop outside the class keeps the class + invariant of every object it reaches (the local objects alive at it, and + the objects its function's reference and pointer parameters address): it + is added to the loop's invariants and proved there like the user's. + Members of the class may break it between calls, so their loops get none + of their class's. ## View Functions (idiomatic spec abstraction) @@ -1890,7 +1896,7 @@ int safe_fib(int n) cppverify::pre(...) cppverify::post(cppverify::result == fib ## hide / reveal -- `cppverify::hide(fn_name)` and `cppverify::reveal(fn_name)` are statements, accepted anywhere in a function body or ghost block, that control whether the body of a spec function is visible to Z3 in the whole function. +- `cppverify::hide(fn_name)` and `cppverify::reveal(fn_name)` are statements, accepted anywhere in a function body or ghost block, that control whether the body of a spec function is visible to Z3 in the whole function. A specialization of a spec template is named with its arguments (`cppverify::hide(at<4>)`), resolved as C++ resolves a template-id; the template's name alone is an error. - Default for non-recursive specs: visible (body inlined into queries). - Default for recursive specs: one finite unfolding step. Deeper unfolding requires `cppverify::reveal_with_fuel`. @@ -2242,6 +2248,10 @@ logical-function declarations with compact step definitions and exact finite fuel levels. Layer 3 dumps this exact module; Layer 4 and ordinary verification encode the same in-memory object. There is no separate dump-only weakest-precondition implementation and no backend may borrow VCR declarations. +`--dump-smt2=DIR` writes each query a solver checks, as that solver received +it, as a standalone SMT-LIB script with the solver's options as commands and +the answer cpp-verify got, named by the function, the obligation, and a hash +of the script. Diagnostic metadata is non-semantic: names, source ranges, internal positional and public source-anchored IDs, and trace events survive archive replay but do @@ -2281,10 +2291,12 @@ dead declarations or trivial Boolean structure. It does not rewrite arithmetic, quantifiers, pointer/heap expressions, or assumptions. `--obligation-out=FILE` publishes deterministic, concatenable -`cppverify.obligation/3` binary records with stable wire tags and portable +`cppverify.obligation/5` binary records with stable wire tags and portable source attribution; schema v3 adds each logical function's termination measures and, with source attribution, where each assumption of a goal comes -from, neither hashed, and records of schemas v1 and v2 remain readable. Publication includes an +from, schema v4 where each variable version of a counterexample is defined, +schema v5 where each source range begins, none of them hashed, and records of +schemas v1 to v4 remain readable. Publication includes an exact serialize/deserialize/validate/reserialize check. SHA-256 module and per-obligation semantic hashes exclude source paths, display-only names, both positional and source-anchored obligation IDs, and every obligation kind except @@ -2328,9 +2340,12 @@ their human message: `counterexample`, `solver.timeout`, `solver.unknown`, `spec.reads`, `spec.post`, `spec.inductive`, `proof.cycle`, `counterexample.unchecked`, `construct.unsupported`, `callee.contract`, `decreases.missing`, and `interrupted`. -`--diagnostics-format=json` serializes verification results as versioned JSON -Lines (`cppverify.diagnostic/1`) for both source verification and archive -replay. +`--diagnostics-format=json` serializes verification results and compiler +diagnostics as versioned JSON Lines (`cppverify.diagnostic/2`, ending with a +`cppverify.summary/1` record) for both source verification and archive +replay; `--diagnostics-format=sarif` translates the same records into one +SARIF 2.1.0 log, and `--diagnostics-format=github` adds a GitHub workflow +annotation to the text for each error and warning. `--jobs=N` runs one pool of exactly `N` workers (default: the available physical cores, or `CPPVERIFY_JOBS`; 1 for `--backend=lean` and for the @@ -2390,7 +2405,8 @@ the finite ground equations of recursive ones. It runs an installed executable w seed/resource options and bounded output; missing tools, invocation failures, timeouts, `unknown`, extra diagnostics, and malformed tokens are unresolved. The model cvc5 prints after `sat`, and often after `unknown`, is checked by the -same certifier as Z3's, and +same certifier as Z3's, which then evaluates in it the counterexample's +source values and path, so a cvc5 failure reads like a Z3 one, and refinement re-runs cvc5 with definition instances. Once a hidden instance has been given, non-recursive definitions are given whole; recursive ones never are, because cvc5 does not decide queries over them. diff --git a/website/source/book/part-i/ch07-memory-aliasing-frames.rst b/website/source/book/part-i/ch07-memory-aliasing-frames.rst index fe85fe7e50..4956451e55 100644 --- a/website/source/book/part-i/ch07-memory-aliasing-frames.rst +++ b/website/source/book/part-i/ch07-memory-aliasing-frames.rst @@ -79,7 +79,19 @@ same. The swap by sums is not: .. code-block:: text - sums.cpp:6:30: error: verification failed: swap_by_sums [...] (counterexample: a [ssa=a_0] [type=pointer] = 1, b [ssa=b_0] [type=pointer] = 1; ...) + sums.cpp:6:30: error: verification failed: swap_by_sums [postcondition] [backend=z3] [reason=counterexample] + 6 | cv::post(*a == cv::old(*b) && *b == cv::old(*a)) + | ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ + sums.cpp:6:30: note: counterexample: a = 1, b = 1 + sums.cpp:8:6: note: stores 2000 at address 1 + 8 | *a = *a + *b; *b = *a - *b; *a = *a - *b; + | ^ + sums.cpp:8:20: note: stores 0 at address 1 + 8 | *a = *a + *b; *b = *a - *b; *a = *a - *b; + | ^ + sums.cpp:8:34: note: stores 0 at address 1 + 8 | *a = *a + *b; *b = *a - *b; *a = *a - *b; + | ^ With ``a == b`` the first statement doubles the value and the second sets it to 0. 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 4a8cb9583f..0034b20421 100644 --- a/website/source/book/part-ii/ch12-loops-in-practice.rst +++ b/website/source/book/part-ii/ch12-loops-in-practice.rst @@ -282,8 +282,8 @@ Two loops may each declare their counter, ``for (int i = 0; ...)``, and a block may declare a name an outer one already has: each declaration is a variable of its own, so the second ``i`` starts uninitialized and neither loop's invariant is about the other's counter. A counterexample shows the -name as written, with the verifier's own name for the second one, ``i#2``, in -``[ssa=...]``. +name as written, with the line of its declaration when both appear in it: +``i (line 8) = 0 -> 1, i (line 14) = 0 -> 1``. Lexicographic measures ---------------------- 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 57033d1c10..0aa1950b17 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 @@ -180,7 +180,10 @@ is checked like any other: .. code-block:: text - stuck.cpp:4:42: error: spec decreases failed: stuck [backend=z3] [reason=counterexample] (n [type=math-i32] = 1) + stuck.cpp:4:42: error: spec decreases failed: stuck [backend=z3] [reason=counterexample] + 4 | return n <= 0 ? 0 : cv::choose(k, k == stuck(n) + 1); + | ^ + stuck.cpp:4:42: note: counterexample: n = 1 Unresolved: forty_two [backend=z3] [reason=spec.termination] (relies on the definition of stuck, whose termination is not established) At ``n = 1`` the definition says that ``stuck(1)`` equals ``stuck(1) + 1``, a @@ -547,7 +550,10 @@ solver proves it, the counterexample is confirmed: .. code-block:: text - error: verification failed: small_power [...::postcondition@...] (counterexample: n [ssa=n_0] [type=i32] = 1000023594; confirmed by a proof at this input, since its check could not compute pow2(1000023594)) [backend=z3] [reason=counterexample] + example.cpp:11:20: error: verification failed: small_power [postcondition] (confirmed by a proof at this input, since its check could not compute pow2(1000023594)) [backend=z3] [reason=counterexample] + 11 | cv::post(pow2(n) < 1000) + | ~~~~~~~~^~~~~~ + example.cpp:11:20: note: counterexample: n = 1000023594 The failure rests on the facts the proof used (here ``pow2``'s postcondition); if one of them is not established, the verdict is not a @@ -616,7 +622,10 @@ Which to do depends on what you believe: .. code-block:: text Verified: fibo_at_least_5 [backend=z3] - error: verification failed: fibo_small [...::postcondition@...] (counterexample: n [ssa=n_0] [type=i32] = 1000000000; confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) [backend=z3] [reason=counterexample] + example.cpp:21:20: error: verification failed: fibo_small [postcondition] (confirmed by a proof at this input that uses the contract of fibo_at_least_5, since its check could not compute fibo(1000000000)) [backend=z3] [reason=counterexample] + 21 | cv::post(fibo(n) < 5) + | ~~~~~~~~^~~ + example.cpp:21:20: note: counterexample: n = 1000000000 The proof instantiates a lemma where its postcondition speaks of the same spec application as the claim, here ``fibo_at_least_5`` at @@ -661,7 +670,10 @@ those assumptions. A lemma over memory is used the same way: .. code-block:: text Verified: total_nonneg [backend=z3] - error: verification failed: total_negative [...::postcondition@...] (counterexample: n [ssa=n_0] [type=i32] = 1000000000, a [ssa=a_0] [type=pointer] = 1; confirmed by a proof at this input that uses the contract of total_nonneg, since its check could not compute total(1, 1000000000)) [backend=z3] [reason=counterexample] + example.cpp:20:24: error: verification failed: total_negative [postcondition] (confirmed by a proof at this input that uses the contract of total_nonneg, since its check could not compute total(1, 1000000000)) [backend=z3] [reason=counterexample] + 20 | cv::post(total(a, n) < 0) + | ~~~~~~~~~~~~^~~ + example.cpp:20:24: note: counterexample: a = 1, n = 1000000000 Only contracts that are established count: a confirmation runs after every function is verified, and a lemma that failed, or that rests on a fact that @@ -999,8 +1011,17 @@ it means the same: .. code-block:: text - choose.cpp:5:43: error: verification failed: write_inside [...] (counterexample: a [ssa=a_0] [type=pointer] = ..., n [ssa=n_0] [type=i32] = 1, ...) - choose.cpp:12:33: error: verification failed: written_differently [...] (counterexample: n [ssa=n_0] [type=i32] = ) + choose.cpp:4:43: error: verification failed: write_inside [postcondition] [backend=z3] [reason=counterexample] + 4 | cv::post(cv::choose(k, 0, n, a[k] == x) == + | ^ + choose.cpp:4:43: note: counterexample: a = 8856, n = 1, x = -1 + choose.cpp:7:8: note: stores 0 at address 8856 + 7 | a[0] = x == 0 ? 1 : 0; + | ^ + choose.cpp:11:33: error: verification failed: written_differently [postcondition] [backend=z3] [reason=counterexample] + 11 | cv::post(cv::choose(k, k > n) == cv::choose(k, n < k)) + | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~ + choose.cpp:11:33: note: counterexample: n = In every counterexample to ``write_inside`` the write changes ``a[0]``, a cell the condition reads, so the choice may move. When a proof needs equivalent conditions to choose alike, write @@ -1174,9 +1195,10 @@ where a finite derivation shows it. Verified: spec post: reach Verified: three_reaches_twelve [backend=z3] Verified: no_way_back [backend=z3] - book.cpp:25:14: error: verification failed: wrong_way [...::postcondition@25:14] - (counterexample: a [ssa=a_0] [type=i32] = 38, b [ssa=b_0] [type=i32] = 38) - [backend=z3] [reason=counterexample] + book.cpp:25:14: error: verification failed: wrong_way [postcondition] [backend=z3] [reason=counterexample] + 25 | cv::post(a < b) + | ~~^~~ + book.cpp:25:14: note: counterexample: a = 38, b = 38 A proof sees ``reach`` through its body, unfolded once at each application it names: ``reach(6, 12)`` holds by the edge ``6 -> 12`` and ``reach(12, 12)``, 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 4acdea77d3..0c37e03c47 100644 --- a/website/source/book/part-ii/ch14-pointers-frames-modifies.rst +++ b/website/source/book/part-ii/ch14-pointers-frames-modifies.rst @@ -319,7 +319,10 @@ and footprints name it, and the same frame reasoning as for a buffer applies: Verified: push [backend=z3] Verified: clear [backend=z3] - stack.cpp:36:21: error: verification failed: push_full [...::bounds@36:21] (counterexample: s [ssa=s_0] [type=pointer] = 1; trace: heap-write.store@36:21 address=65 value=) [backend=z3] [reason=counterexample] + stack.cpp:36:21: error: verification failed: push_full [bounds] [backend=z3] [reason=counterexample] + 36 | s->items[s->size] = v; + | ^ + stack.cpp:36:21: note: counterexample: s = 1 ``push`` writes exactly two cells, so its postcondition can promise that every item below the old size is unchanged. ``clear`` writes a range of the member @@ -379,7 +382,16 @@ that runs one element too far reads ``s->size`` and fails: .. code-block:: text Verified: count_zero [backend=z3] - stack.cpp:31:10: error: verification failed: count_past [...::bounds@31:10#2] (counterexample: n [ssa=n_1] [type=i32] = 0, n [ssa=n_2] [type=i32] = 0, q [ssa=q_1] [type=pointer] = 1, q [ssa=q_2] [type=pointer] = 65, s [ssa=s_0] [type=pointer] = 1; trace: loop.entry@26:3 -> loop.inductive iteration@26:3 condition=true) [backend=z3] [reason=counterexample] + stack.cpp:31:10: error: verification failed: count_past [bounds] [backend=z3] [reason=counterexample] + 31 | if (*q == 0) + | ^ + stack.cpp:31:10: note: counterexample: s = 1, n = 0, q = 1 -> 65 + stack.cpp:26:3: note: enters the loop + 26 | for (const int *q = s->items; q != s->items + s->size + 1; q = q + 1) + | ^ + stack.cpp:26:3: note: starts an arbitrary iteration of the loop, where its condition is true + note: the counterexample's state at the loop at 26:3 is any state its invariants allow, not necessarily one an execution reaches + note: no execution fails this check within 10 iterations of each loop In ``count_past`` the solver chooses a full stack, and ``q`` reaches 65, the address of ``s->size`` with ``s`` at 1. The verifier itself states that a 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 21970d2e8e..76e60dc614 100644 --- a/website/source/book/part-ii/ch15-toolchain-and-flags.rst +++ b/website/source/book/part-ii/ch15-toolchain-and-flags.rst @@ -104,7 +104,10 @@ Other flags - ``cpp-verify --proof-cache=DIR`` — keep proofs of single obligations between runs (``--proof-cache-max-mb``, ``--proof-cache-max-entries`` bound it) - ``cpp-verify --diagnostics-format=json`` — emit versioned - ``cppverify.diagnostic/1`` JSON Lines for verification results + ``cppverify.diagnostic/2`` JSON Lines for results and compiler diagnostics, + ending with a summary record; ``sarif`` prints one SARIF 2.1.0 log, and + ``github`` the text with a GitHub Actions annotation for each error and + warning - ``cpp-verify --lean-out=FILE`` — write a standalone Lean scratch-pad (``--backend=lean``, which it selects) - ``cpp-verify --lean-project=DIR`` — generate a preserved, pinned Lean project @@ -117,6 +120,8 @@ Other flags - ``cpp-verify --dump-ir[=1,2,3,4]`` — dump VCR / passive / Obligation IR / Z3 layers - ``cpp-verify --lower-only`` — construct and encode VCs without invoking Z3's satisfiability check +- ``cpp-verify --dump-smt2=DIR`` — write every query sent to Z3 or cvc5 as a + script the solver runs on its own, with the answer cpp-verify got - ``cpp-verify --obligation-out=FILE`` — write validated, versioned canonical obligation records with semantic hashes - ``cpp-verify --obligation-in=FILE`` — validate and replay records through Z3, diff --git a/website/source/book/part-ii/ch16-when-verification-fails.rst b/website/source/book/part-ii/ch16-when-verification-fails.rst index 4a056a2812..89dadd2b37 100644 --- a/website/source/book/part-ii/ch16-when-verification-fails.rst +++ b/website/source/book/part-ii/ch16-when-verification-fails.rst @@ -7,12 +7,19 @@ that as actionable feedback on the code, the contracts, or both. Understanding the report ------------------------ -Diagnostics name the source-anchored obligation that failed and often include a -**counterexample**: typed source-name values that violate the claim, followed by -guarded branch, call, loop, heap/provenance, lifetime, and return events. Use -those values and events to see which assumption or branch is wrong. Values that -Z3 does not determine remain ````; CppVerify does not complete the -model by inventing them. +A failure is a compiler error at the check that fails, with its kind +(``[postcondition]``, ``[overflow]``, ...) and the source line, the failing +expression underlined. Its notes give the **counterexample**: first the values +by their source names, each variable with the values it takes along the path +(``c = 0 -> 1``), then each step of that path at its line, as Clang's static +analyzer reports a path: the branches it takes, the calls with their +arguments, the loops it enters and leaves, the memory it writes, the objects +it creates and ends, and the value it returns. Use those values and steps to +see which assumption or branch is wrong. Values that the solver's model does +not determine remain ````; CppVerify does not complete the model by +inventing them. +``clang++ -fverify-contracts`` reports the same errors and notes as its own +diagnostics. For editor, CI, and agent integration, run: @@ -20,14 +27,22 @@ For editor, CI, and agent integration, run: cpp-verify --diagnostics-format=json file.cpp -Each verification result is one ``cppverify.diagnostic/1`` JSON object with a -stable reason code, backend and optional BMC bound, inclusive source range, -obligation ID/kind, typed model values, and trace. An unknown model value is -JSON ``null``. An undetermined path guard is ``"active": null`` rather than an -arbitrary branch choice. Command-line and frontend parse errors may still be -text, and IR dumps intentionally make the stream mixed. Malformed display bytes -are replaced with the Unicode replacement character, so JSON output remains -valid UTF-8. +Each verification result is one ``cppverify.diagnostic/2`` JSON object with a +stable reason code, backend and optional BMC bound, the function and where it +is declared, the source range of the failing expression (to the column after +its last character), obligation ID/kind, typed model values, the +counterexample and each step of the path as the text prints them, and the +trace. An unknown model value is JSON ``null``. An undetermined path guard is +``"active": null`` rather than an arbitrary branch choice. The compiler's own +errors and warnings are records too, and a summary record ends the run. IR +dumps intentionally make the stream mixed. Malformed display bytes are +replaced with the Unicode replacement character, so JSON output remains valid +UTF-8. + +In a GitHub workflow, ``--diagnostics-format=sarif`` writes a SARIF log that +``github/codeql-action/upload-sarif`` turns into code-scanning alerts, each +failure with its path, and ``--diagnostics-format=github`` annotates the +failing lines of a pull request directly (see :doc:`../../language/tooling`). A counterexample after a loop is only as real as the loop's invariants. At the loop, the verifier knows what the invariants say and nothing else, so a @@ -52,7 +67,17 @@ loop with too weak an invariant fails a correct function: .. code-block:: text - count_up.cpp:5:23: error: verification failed: count_up [fn_...::postcondition@5:23] (counterexample: result [ssa=__result_1] [type=i32] = 1, c [ssa=c_1] [type=i32] = 0, c [ssa=c_2] [type=i32] = 1, c [ssa=c_3] [type=i32] = 1, n [ssa=n_0] [type=i32] = 0; trace: loop.entry@8:3 -> loop.exit@8:3 -> return.return@13:3 value=1) [backend=z3] [reason=counterexample] + count_up.cpp:5:23: error: verification failed: count_up [postcondition] [backend=z3] [reason=counterexample] + 5 | cv::post(cv::result == n) + | ~~~~~~~~~~~^~~~ + count_up.cpp:5:23: note: counterexample: n = 0, c = 0 -> 1, result = 1 + count_up.cpp:8:3: note: enters the loop + 8 | while (c < n) + | ^ + count_up.cpp:8:3: note: leaves the loop + count_up.cpp:13:3: note: returns 1 + 13 | return c; + | ^ note: the counterexample's state at the loop at 8:3 is any state its invariants allow, not necessarily one an execution reaches note: no execution fails this check: every loop ends within 5 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof @@ -138,6 +163,14 @@ true on the cycle, so no unfolding settles it, and a postcondition query or a fact the verifier gave it, which is a solver fault and says nothing about your program. +To report such a fault, or a query a solver takes too long on, to the +solver's developers, run with ``--dump-smt2=DIR``: every query is written to +``DIR`` as a script that ``z3 FILE`` or ``cvc5 FILE`` runs on its own, named +by the function and the obligation (``clamp-14e9c9/postcondition-5-23...``), +and ending with the answer cpp-verify got (see +:doc:`../../language/tooling`). The file is the report: it needs neither +cpp-verify nor your source. + Every reason code at a glance ----------------------------- @@ -391,7 +424,13 @@ be its cause: .. code-block:: text - vacuity.cpp:2:28: error: verification failed: contradictory [...::postcondition@2:28] (...) [backend=z3] [reason=counterexample] + vacuity.cpp:2:28: error: verification failed: contradictory [postcondition] [backend=z3] [reason=counterexample] + 2 | cv::post(cv::result == x && cv::result == x + 1) // no result satisfies this + | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ + vacuity.cpp:2:28: note: counterexample: x = 2147483647, result = 2147483647 + vacuity.cpp:4:3: note: returns 2147483647 + 4 | return x; + | ^ Unresolved: relies_on_it [backend=z3] [reason=callee.contract] (relies on the contract of contradictory, which is not established) ``relies_on_it`` proves its postcondition only because the contract of diff --git a/website/source/book/part-ii/ch17-backends-modular-calls.rst b/website/source/book/part-ii/ch17-backends-modular-calls.rst index cd186a853a..d0c463f721 100644 --- a/website/source/book/part-ii/ch17-backends-modular-calls.rst +++ b/website/source/book/part-ii/ch17-backends-modular-calls.rst @@ -73,6 +73,42 @@ malformed process output, and unsupported encoding are ``Unresolved``. cvc5 is not vendored because it is an optional independent implementation; install it through the platform package manager or select an executable explicitly. +A failure cvc5 finds reads like one Z3 finds. Its model is a counterexample +only once the certifier has checked it against the true definitions, and the +certifier then evaluates, in that model, the values of the source variables +along the path and each step of the path to the failing check: + +.. code-block:: cpp + + namespace cv = cppverify; + + int clamp(int x) + cv::pre(x > -100 && x < 100) + cv::post(cv::result >= 0) + { + if (x < 0) + return x + 1; + return x; + } + +.. code-block:: text + + clamp.cpp:5:23: error: verification failed: clamp [postcondition] [backend=cvc5] [reason=counterexample] + 5 | cv::post(cv::result >= 0) + | ~~~~~~~~~~~^~~~ + clamp.cpp:5:23: note: counterexample: x = -2, result = -1 + clamp.cpp:7:3: note: takes the true branch + 7 | if (x < 0) + | ^ + clamp.cpp:8:5: note: returns -1 + 8 | return x + 1; + | ^ + +The JSON record has the same ``counterexample``, ``model``, and ``trace`` +as Z3's, its variables in the same order, and so do SARIF, GitHub +annotations, and archive replay with ``--backend=cvc5``. The values may +differ from Z3's: each solver picks its own counterexample. + Race mode trusts each solver alone, which is sound: a proof is a proof, and a counterexample is reported only once the certifier has checked it against the true definitions, whichever solver found it. The first decisive answer @@ -410,7 +446,13 @@ checks a proof, with the kernel and Lean's foundations alone, and says so: .. code-block:: text - error: verification failed: add_one [fn_5f5a376164645f6f6e6569::postcondition@3:37] (counterexample: result [ssa=__result_1] [type=i32] = 6, x [ssa=x_0] [type=i32] = 4; trace: return.return@5:3 value=6) [backend=z3] [reason=counterexample] (confirmed in Lean) + example.cpp:3:37: error: verification failed: add_one [postcondition] [backend=z3] [reason=counterexample] (confirmed in Lean) + 3 | cppverify::post(cppverify::result == x + 1) + | ~~~~~~~~~~~~~~~~~~^~~~~~~~ + example.cpp:3:37: note: counterexample: x = 4, result = 6 + example.cpp:5:3: note: returns 6 + 5 | return x + 2; + | ^ The refutation applies the goal to the model's values and decides what is left false, the definition instances the goal states with it, since they hold @@ -1087,7 +1129,7 @@ The canonical backend boundary can be persisted and replayed: cpp-verify --obligation-in=goals.cpv --backend=lean --lean-out=goals.lean cpp-verify --obligation-in=goals.cpv --lean-project=proof --lean-certify -Each ``cppverify.obligation/3`` record uses stable wire tags, defensive bounds, +Each ``cppverify.obligation/5`` record uses stable wire tags, defensive bounds, portable source points, and path-independent SHA-256 semantic hashes. Replay revalidates every sort, expression, logical declaration, call signature, feature declaration, and identity before backend dispatch. Multiple records may diff --git a/website/source/book/part-ii/ch18-undefined-behavior.rst b/website/source/book/part-ii/ch18-undefined-behavior.rst index b15fe689ab..b811591e5f 100644 --- a/website/source/book/part-ii/ch18-undefined-behavior.rst +++ b/website/source/book/part-ii/ch18-undefined-behavior.rst @@ -102,8 +102,10 @@ The classic example — the tool tells you the precondition you forgot: .. code-block:: text - abs.cpp:2:3: error: verification failed: abs_unguarded [...::overflow@2:3] - (counterexample: x [type=i32] = -2147483648) + abs.cpp:2:3: error: verification failed: abs_unguarded [overflow] [backend=z3] [reason=counterexample] + 2 | { return x < 0 ? -x : x; } + | ^ + abs.cpp:2:3: note: counterexample: x = -2147483648 Verified: abs_guarded [backend=z3] Negating ``INT_MIN`` overflows, so the first version fails at that input; @@ -135,8 +137,10 @@ base is ``p`` then carries the obligation ``0 <= i < n``: .. code-block:: text Verified: get [backend=z3] - error: verification failed: last [...::bounds@11:10] (counterexample: - n = 1, p = 1) + example.cpp:10:10: error: verification failed: last [bounds] [backend=z3] [reason=counterexample] + 10 | { return p[n]; } // FAILS: p[n] is one past the end + | ^ + example.cpp:10:10: note: counterexample: p = 1, n = 1 ```` declares ``valid`` for every pointee type, so the same marker describes an ``int`` buffer, a ``char`` buffer, or an array of @@ -287,8 +291,10 @@ very input it means to exclude: .. code-block:: text - per.cpp:2:17: error: verification failed: contract of per_item - [...::contract::division-by-zero@2:17] (counterexample: items [type=i32] = 0) + per.cpp:2:17: error: verification failed: contract of per_item [division-by-zero] [backend=z3] [reason=counterexample] + 2 | cv::pre(total / items <= 100 && items > 0) + | ^ + per.cpp:2:17: note: counterexample: items = 0 Verified: per_item [backend=z3] The body verifies, since its proof assumes the precondition. The contract does @@ -325,7 +331,14 @@ therefore holds, and an assert documents a fact the proof checks. With .. 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] + ratio.cpp:7:3: error: verification failed: ratio [unreachable] [backend=z3] [reason=counterexample] + 7 | assert(parts > 0 && "parts must be positive"); + | ^ + /usr/include/assert.h:105:9: note: expanded from macro 'assert' + 105 | : __assert_fail (#expr, __ASSERT_FILE, __ASSERT_LINE, \ + | ^ + ratio.cpp:7:3: note: counterexample: total = 0, parts = 0 + ratio.cpp:7:3: note: takes the false branch The division needs ``parts != 0``, which the assert establishes, and the assert can fail: a caller may pass ``parts == 0``. Adding diff --git a/website/source/book/part-ii/ch20-mathematics-to-code.rst b/website/source/book/part-ii/ch20-mathematics-to-code.rst index e51f7347a3..a3adc2f9d6 100644 --- a/website/source/book/part-ii/ch20-mathematics-to-code.rst +++ b/website/source/book/part-ii/ch20-mathematics-to-code.rst @@ -253,10 +253,14 @@ Both are reported with the input that shows it: .. code-block:: text - error: verification failed: clamp_overlap [...::assertion@10:3] - (counterexample: hi = 0, lo = 0, x = 0) - error: verification failed: clamp_gap [...::assertion@27:3] - (counterexample: hi = 0, lo = 0, x = 0) + example.cpp:10:3: error: verification failed: clamp_overlap [assertion] [backend=z3] [reason=counterexample] + 10 | cv::disjoint_behaviors + | ^ + example.cpp:10:3: note: counterexample: x = 0, lo = 0, hi = 0 + example.cpp:27:3: error: verification failed: clamp_gap [assertion] [backend=z3] [reason=counterexample] + 27 | cv::complete_behaviors + | ^ + example.cpp:27:3: note: counterexample: x = 0, lo = 0, hi = 0 Universal statements: sorted arrays ----------------------------------- @@ -434,8 +438,14 @@ fails: .. code-block:: text - error: verification failed: which [...::postcondition@3:15] - (counterexample: result = 0, a = 536, n = 2; trace: call.argmax ...) + example.cpp:3:23: error: verification failed: which [postcondition] [backend=z3] [reason=counterexample] + 3 | cv::post(cv::result == some_max(a, n)) + | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~ + example.cpp:3:23: note: counterexample: a = 536, n = 2, result = 0 + example.cpp:5:3: note: calls argmax(536, 2) + 5 | return argmax(a, n); + | ^ + example.cpp:5:3: note: returns 0 In the counterexample both elements are maxima, ``argmax`` returns 0, and ``cppverify::choose`` picks 1. diff --git a/website/source/case-studies/binary-search.rst b/website/source/case-studies/binary-search.rst index 9860d81ee6..b9440560ea 100644 --- a/website/source/case-studies/binary-search.rst +++ b/website/source/case-studies/binary-search.rst @@ -30,14 +30,26 @@ is always on, so the encoder generates the obligation itself and it fails: .. code-block:: text - error: verification failed: bsearch_broken [...::overflow@...] - (counterexample: lo = 1073741824, hi = 1073741824) - -Those two values sum to 2,147,483,648, which is ``INT_MAX + 1``. The verifier -did not merely report that something might overflow; it named the check that -fails and produced the concrete state that breaks it. The solver chooses the -witness, so another version may report a different pair with the same -overflow. + binary_search.cpp:28:5: error: verification failed: bsearch_broken [overflow] [backend=z3] [reason=counterexample] + 28 | int mid = (lo + hi) / 2; + | ^ + binary_search.cpp:28:5: note: counterexample: a = 1, n = 1073741825, lo = 0 -> 1073741824, hi = 1073741824, res = -1 -> 1073741824 + binary_search.cpp:24:3: note: enters the loop + 24 | while (lo <= hi) + | ^ + binary_search.cpp:24:3: note: starts an arbitrary iteration of the loop, where its condition is true + note: the counterexample's state at the loop at 24:3 is any state its invariants allow, not necessarily one an execution reaches + note: an execution fails this check within 2 iterations of each loop (counterexample: a = 1, n = 2147483647, key = -2147483647, lo = 0 -> 1073741824, hi = 2147483646, res = -1, mid = 1073741823) + +In the iteration the counterexample starts, ``lo`` and ``hi`` are both +1073741824, which sum to 2,147,483,648, ``INT_MAX + 1``. That state comes from +the loop's invariant, so the verifier then unrolls the loop and finds an +execution that gets there: in an array of ``INT_MAX`` elements, the first +midpoint is 1073741823, the key is larger, and the second iteration adds +1073741824 and 2147483646. The verifier did not merely report that something +might overflow; it named the check that fails and produced the concrete state +that breaks it. The solver chooses the witness, so another version may report +a different pair with the same overflow. Replacing the midpoint with the standard safe form makes the same function verify: diff --git a/website/source/case-studies/index.rst b/website/source/case-studies/index.rst index 2e24661d9d..8604419859 100644 --- a/website/source/case-studies/index.rst +++ b/website/source/case-studies/index.rst @@ -9,4 +9,5 @@ Measured verification artifacts built from real systems algorithms. llvm-uleb128 utf8-validation binary-search + ring-buffer diff --git a/website/source/case-studies/ring-buffer.rst b/website/source/case-studies/ring-buffer.rst new file mode 100644 index 0000000000..e4758539cd --- /dev/null +++ b/website/source/case-studies/ring-buffer.rst @@ -0,0 +1,264 @@ +Ring buffer +=========== + +A queue in a fixed array, specified as a queue. + +Embedded code keeps its queues in fixed arrays: a UART's received bytes, the +samples of a sensor, the commands of a control loop. The array never moves, +two counters say where the queue starts and how long it is, and the indices +wrap around at the end. This case study is such a ring as a C++ class +template, ``Ring``, with its fields private and an invariant over them, +and a contract that says what the class means rather than how it stores it: +it is a FIFO queue of at most ``N`` integers. + +It exercises the class and template support of 0.2: a class template with a +non-type parameter, a constructor whose loop initializes an array member, a +class invariant, contracts written through spec functions that read the +private fields (*views*), ``assert`` inside a member function, and clients +that use a range-based ``for``, a ``switch`` over a scoped enumeration, and +an object passed by reference. + +The code +-------- + +.. code-block:: cpp + + #include + + namespace cv = cppverify; + + template class Ring; + template cv::spec int size(const Ring *r); + template cv::spec int at(const Ring *r, int i); + + // A queue of at most N ints in a fixed array, as embedded code keeps one: + // its elements, oldest first, are buf[head], buf[head + 1], ..., wrapping + // around at N. + template + class Ring { + public: + Ring() + cv::post(size(this) == 0) + : head(0), count(0) + { + for (int i = 0; i < N; i = i + 1) + cv::invariant(0 <= i && i <= N && head == 0 && count == 0) + cv::invariant(cv::forall(k, 0, i, buf[k] == 0)) + cv::decreases(N - i) + { + buf[i] = 0; + } + } + + bool full() const + cv::post(cv::result == (size(this) == N)) + { + return count == N; + } + + // Appends v after the newest element. + void push(int v) + cv::pre(size(this) < N) + cv::post(size(this) == cv::old(size(this)) + 1) + cv::post(at(this, size(this) - 1) == v) + cv::post(cv::forall(k, 0, cv::old(size(this)), + at(this, k) == cv::old(at(this, k)))) + { + assert(count < N); + buf[(head + count) % N] = v; + count = count + 1; + } + + // Removes the oldest element and returns it. + int pop() + cv::pre(size(this) > 0) + cv::post(size(this) == cv::old(size(this)) - 1) + cv::post(cv::result == cv::old(at(this, 0))) + cv::post(cv::forall(k, 0, size(this), + at(this, k) == cv::old(at(this, k + 1)))) + { + int v = buf[head]; + #ifdef FORGET_WRAP + head = head + 1; + #else + head = (head + 1) % N; + #endif + count = count - 1; + return v; + } + + private: + int buf[N]; + int head; + int count; + + template friend cv::spec int size(const Ring *r); + template friend cv::spec int at(const Ring *r, int i); + + public: + cv::type_invariant(0 <= head && head < N && 0 <= count && count <= N); + }; + + // How many elements r holds. + template cv::spec int size(const Ring *r) { return r->count; } + + // The element i places after the oldest. + template cv::spec int at(const Ring *r, int i) + { + return r->buf[(r->head + i) % N]; + } + + // The samples come out in the order they went in. + int first_two() + cv::post(cv::result == 12) + { + cv::hide(at<4>); + Ring<4> r; + int samples[3] = {1, 2, 3}; + for (int s : samples) + cv::invariant(size(&r) == cv::index) + cv::invariant(cv::forall(k, 0, cv::index, at(&r, k) == samples[k])) + r.push(s); + if (!r.full()) + r.push(4); + int a = r.pop(); + int b = r.pop(); + return 10 * a + b; + } + + enum class Command { Push, Pop, Skip }; + + // Applies one command to r: a push of v, a pop, which returns the oldest + // element, or nothing. + int apply(Ring<4> &r, Command c, int v) + cv::pre(c != Command::Push || size(&r) < 4) + cv::pre(c != Command::Pop || size(&r) > 0) + cv::post(c != Command::Pop || cv::result == cv::old(at(&r, 0))) + cv::post(c != Command::Skip || size(&r) == cv::old(size(&r))) + cv::modifies(r) + { + switch (c) { + case Command::Push: + r.push(v); + return 0; + case Command::Pop: + return r.pop(); + case Command::Skip: + break; + } + return 0; + } + +``at(r, i)`` is the element ``i`` places after the oldest, and ``size(r)`` +the number of elements: together they are the queue the ring stands for. +They are spec functions, which exist only for verification, and friends of +the class, so that they may read its private fields while no executable +code outside the class can. + +What is proved +-------------- + +.. code-block:: text + + $ cpp-verify ring_buffer.cpp + Verified: Ring<4>::Ring [backend=z3] + Verified: Ring<4>::full [backend=z3] + Verified: Ring<4>::push [backend=z3] + Verified: Ring<4>::pop [backend=z3] + Verified: first_two [backend=z3] + Verified: apply [backend=z3] + Summary: 6 verified in 1.9 s + +A template is verified in each specialization a translation unit uses, here +``Ring<4>``. For it: + +- every access to ``buf`` lies in the array, and no arithmetic overflows; +- the class invariant holds at the boundary of every public member function: + the constructor establishes it, and ``push`` and ``pop`` keep it; +- ``push`` appends: the queue grows by one, its newest element is ``v``, + and every older one keeps its place; +- ``pop`` removes the oldest element and returns it, and every other one + moves one place forward; +- the constructor's loop initializes every element of ``buf``, so no read of + the array is of an uninitialized value; +- ``assert(count < N)`` in ``push`` holds, from the precondition. + +The clients use only these contracts. ``first_two`` loads three samples in +a range-based ``for`` whose invariants say where each one is in the queue, +adds a fourth if the ring is not full, and takes the two oldest out: the +contract alone shows that they are 1 and 2, so the function returns 12. +``apply`` runs one command on a ring a reference names; its contract states +what each command does, and ``cv::modifies(r)`` that it changes the ring. + +A bug +----- + +The classic one is to forget the wraparound when the oldest element leaves: + +.. cppverify-example: fragment + +.. code-block:: cpp + + head = head + 1; + +instead of ``(head + 1) % N``. Compiled with ``-DFORGET_WRAP``: + +.. code-block:: text + + $ cpp-verify ring_buffer.cpp -- -DFORGET_WRAP + Verified: Ring<4>::Ring [backend=z3] + Verified: Ring<4>::full [backend=z3] + Verified: Ring<4>::push [backend=z3] + ring_buffer.cpp:74:58: error: verification failed: Ring<4>::pop [type-invariant] [backend=z3] [reason=counterexample] + 74 | cv::type_invariant(0 <= head && head < N && 0 <= count && count <= N); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~ + ring_buffer.cpp:74:58: note: counterexample: this = 1, v = -2147483648, result = -2147483648 + ring_buffer.cpp:57:10: note: stores 4 at address 17 + 57 | head = head + 1; + | ^ + ring_buffer.cpp:61:11: note: stores 1 at address 21 + 61 | count = count - 1; + | ^ + ring_buffer.cpp:62:5: note: returns -2147483648 + 62 | return v; + | ^ + ring_buffer.cpp:99:13: note: in instantiation of member function 'Ring<4>::pop' requested here + 99 | int a = r.pop(); + | ^ + Unresolved: first_two [backend=z3] [reason=callee.contract] (relies on the contract of Ring<4>::pop, which is not established) + Unresolved: apply [backend=z3] [reason=callee.contract] (relies on the contract of Ring<4>::pop, which is not established) + Summary: 3 verified, 1 failed, 2 unresolved (callee.contract 2) in 1.9 s + +The class invariant fails when ``pop`` returns, at ``head < N``. The ring +is at address 1: ``buf`` takes its first 16 bytes, so ``head`` is at +address 17 and ``count`` at 21, and the path stores 4 into ``head``, one +past the last index of a ring of 4, and 1 into ``count``. The clients are +not verified while ``pop``'s contract is not. + +What it took +------------ + +Writing the case study found four things the verifier did not support, and +0.2 does: + +- a contract may take the address of a local object, ``size(&r)``, since a + spec it is passed to cannot keep it (:doc:`../language/structs`); +- a loop keeps the class invariants of the objects it reaches, here + ``r``'s, without restating them, which with private fields it could not + (:doc:`../language/structs`); +- ``cv::hide`` and ``cv::reveal_with_fuel`` name a specialization of a spec + template, ``at<4>`` (:doc:`../language/ghost-proofs`); +- ``first_two`` hides ``at``: its proof needs only the contracts of ``push`` + and ``pop``, which speak of ``at``, and the definition, with its ``%`` + inside every quantifier, kept the solver from finishing within the time + limit. + +Limits +------ + +- A view must be a free function that takes a pointer. A spec member + function (``cv::spec int at(int i) const``) and a spec parameter of class + reference type (``const Ring &``) are not supported yet. +- The counterexample names the object, ``this = 1``, and the path shows the + stores into it; the values of ``head`` and ``count`` are not listed by + name yet. diff --git a/website/source/language/functions-loops.rst b/website/source/language/functions-loops.rst index f82e325f61..fc295d3207 100644 --- a/website/source/language/functions-loops.rst +++ b/website/source/language/functions-loops.rst @@ -26,9 +26,12 @@ A local may take the name of a parameter or of another local, in an inner block or a later one (two ``for (int i = ...)`` loops, say): each declaration is a variable of its own, uninitialized until it is assigned whatever an earlier one with its name held. A counterexample shows its name as written, -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``. +with the line of its declaration when another variable of the +counterexample has that name (``i (line 8) = 0 -> 1, i (line 14) = 0 -> 1`` +for two loops that each declare ``i``); JSON records give the verifier's name +for each version as ``ssa_name``, ``i#2_1`` for the first of 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 @@ -172,7 +175,20 @@ otherwise, as within one file. $ cpp-verify src/a.cpp src/mathx.cpp -- -Iinclude Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of clamp100, which its verification in src/mathx.cpp does not establish) - include/mathx.h:2:42: error: verification failed: clamp100 [fn_...::postcondition@2:42] (counterexample: result [ssa=__result_1] [type=i32] = 101, x [ssa=x_0] [type=i32] = 101; ...) [backend=z3] [reason=counterexample] + In file included from src/mathx.cpp:1: + include/mathx.h:2:28: error: verification failed: clamp100 [postcondition] [backend=z3] [reason=counterexample] + 2 | cv::post(cv::result >= 0 && cv::result <= 100); + | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ + include/mathx.h:2:28: note: counterexample: x = 101, result = 101 + src/mathx.cpp:5:3: note: takes the false branch + 5 | if (x < 0) + | ^ + src/mathx.cpp:7:3: note: takes the true branch + 7 | if (x > 100) + | ^ + src/mathx.cpp:8:5: note: returns 101 + 8 | return 101; + | ^ Summary: 1 failed, 1 unresolved (callee.contract 1) in 0.0 s With ``return 100;`` both verify, and the warning that ``clamp100`` has no @@ -326,16 +342,22 @@ that may not be as ``contract of f``, at the operation: .. code-block:: text - defined.cpp:2:17: error: verification failed: contract of average - [...::contract::division-by-zero@2:17] (counterexample: count [type=i32] = 0) + defined.cpp:2:17: error: verification failed: contract of average [division-by-zero] [backend=z3] [reason=counterexample] + 2 | cv::pre(total / count >= 0 && count > 0) + | ^ + defined.cpp:2:17: note: counterexample: count = 0 Verified: average [backend=z3] Verified: average_checked [backend=z3] - defined.cpp:16:30: error: verification failed: contract of pick - [...::contract::bounds@16:30] (counterexample: i [type=i32] = -1, ...) + defined.cpp:16:30: error: verification failed: contract of pick [bounds] [backend=z3] [reason=counterexample] + 16 | cv::pre(cv::valid(p, 8) && p[i] > 0 && 0 <= i && i < 8) + | ^ + defined.cpp:16:30: note: counterexample: p = 1, i = -1 Verified: pick [backend=z3] defined.cpp:22:28: Trusted: read_sensor (contract assumed, not verified) - defined.cpp:23:17: error: verification failed: contract of read_sensor - [...::contract::division-by-zero@23:17] (counterexample: result [type=i32] = 0) + defined.cpp:23:17: error: verification failed: contract of read_sensor [division-by-zero] [backend=z3] [reason=counterexample] + 23 | cv::post(1000 / cv::result > 0); + | ^ + defined.cpp:23:17: note: counterexample: result = 0 Unresolved: scaled [backend=z3] [reason=callee.contract] (relies on the contract of read_sensor (its clauses are not shown to be defined), which is not established) @@ -439,12 +461,24 @@ result. Each call runs where C++ evaluates it: Verified: sq [backend=z3] Verified: sum_sq [backend=z3] Verified: guarded [backend=z3] - calls.cpp:38:7: error: verification failed: unguarded [...::precondition@38:7] - (counterexample: x [type=i32] = -1001; trace: call.sq@38:7 arg0=-1001) - calls.cpp:48:10: error: verification failed: count_up [...::precondition@48:10] - (counterexample: ..., i [type=i32] = 1000, n [type=i32] = 1000; - trace: loop.entry@48:3 -> loop.inductive iteration@48:3 condition=true - -> call.inc@48:10 arg0=1000) + calls.cpp:34:7: error: verification failed: unguarded [precondition] [backend=z3] [reason=counterexample] + 34 | if (sq(x) > 4 && x <= 1000) + | ^ + calls.cpp:34:7: note: counterexample: x = -1001 + calls.cpp:34:7: note: calls sq(-1001) + calls.cpp:44:10: error: verification failed: count_up [precondition] [backend=z3] [reason=counterexample] + 44 | while (inc(i) <= n) + | ^ + calls.cpp:44:10: note: counterexample: n = 1000, i = 0 -> 1000 + calls.cpp:44:3: note: enters the loop + 44 | while (inc(i) <= n) + | ^ + calls.cpp:44:3: note: starts an arbitrary iteration of the loop, where its condition is true + calls.cpp:44:10: note: calls inc(1000) + 44 | while (inc(i) <= n) + | ^ + note: the counterexample's state at the loop at 44:3 is any state its invariants allow, not necessarily one an execution reaches + note: no execution fails this check within 10 iterations of each loop ``guarded`` calls ``sq`` only where ``-1000 <= x && x <= 1000`` holds, so ``sq``'s precondition holds at every call it makes. ``unguarded`` calls @@ -597,8 +631,16 @@ named with its arguments. A template value parameter is its argument, and .. code-block:: text Verified: twice [backend=z3] - templates.cpp:4:23: error: verification failed: twice [...::postcondition@4:23] (counterexample: ...) [backend=z3] [reason=counterexample] + templates.cpp:4:23: error: verification failed: twice [postcondition] [backend=z3] [reason=counterexample] + 4 | cv::post(cv::result == 2 * x) + | ~~~~~~~~~~~^~~~~~~~ + templates.cpp:4:23: note: counterexample: x = 16384, result = -32768 + templates.cpp:6:3: note: returns -32768 + 6 | return x + x; + | ^ templates.cpp:20:13: note: in instantiation of function template specialization 'twice' requested here + 20 | short r = twice(x); + | ^ Verified: twice_int [backend=z3] Unresolved: twice_short [backend=z3] [reason=callee.contract] (relies on the contract of twice, which is not established) @@ -747,7 +789,17 @@ execution that fails the same check, with callees still by their contracts: .. code-block:: text - count_up.cpp:5:23: error: verification failed: count_up [fn_...::postcondition@5:23] (counterexample: result [ssa=__result_1] [type=i32] = 1, c [ssa=c_1] [type=i32] = 0, c [ssa=c_2] [type=i32] = 1, c [ssa=c_3] [type=i32] = 1, n [ssa=n_0] [type=i32] = 0; trace: loop.entry@8:3 -> loop.exit@8:3 -> return.return@13:3 value=1) [backend=z3] [reason=counterexample] + count_up.cpp:5:23: error: verification failed: count_up [postcondition] [backend=z3] [reason=counterexample] + 5 | cv::post(cv::result == n) + | ~~~~~~~~~~~^~~~ + count_up.cpp:5:23: note: counterexample: n = 0, c = 0 -> 1, result = 1 + count_up.cpp:8:3: note: enters the loop + 8 | while (c < n) + | ^ + count_up.cpp:8:3: note: leaves the loop + count_up.cpp:13:3: note: returns 1 + 13 | return c; + | ^ note: the counterexample's state at the loop at 8:3 is any state its invariants allow, not necessarily one an execution reaches note: no execution fails this check: every loop ends within 5 iterations, and unrolling proves it there; the loops' invariants are too weak for an unbounded proof @@ -785,7 +837,14 @@ counterexample, see :doc:`tooling`): .. code-block:: text - divide.cpp:13:7: error: verification failed: divide [fn_...::overflow@13:7] (counterexample: a [ssa=a_0] [type=i32] = -2147483648, c [ssa=c_1] [type=i32] = 0, c [ssa=c_2] [type=i32] = 2, n [ssa=n_0] [type=i32] = 3, q [ssa=q_1] [type=i32] = 0; trace: loop.entry@9:3 -> loop.inductive iteration@9:3 condition=true) [backend=z3] [reason=counterexample] + divide.cpp:13:7: error: verification failed: divide [overflow] [backend=z3] [reason=counterexample] + 13 | q = a / (1 - c); + | ^ + divide.cpp:13:7: note: counterexample: a = -2147483648, n = 3, c = 0 -> 2, q = 0 + divide.cpp:9:3: note: enters the loop + 9 | while (c < n) + | ^ + divide.cpp:9:3: note: starts an arbitrary iteration of the loop, where its condition is true note: the counterexample's state at the loop at 9:3 is any state its invariants allow, not necessarily one an execution reaches note: no execution fails this check without failing an earlier one first (the division-by-zero check at 13:7 fails): those that pass the checks before it end every loop within 2 iterations, and unrolling proves it there @@ -1014,7 +1073,25 @@ other object keeps its value: .. code-block:: text Verified: other_array [backend=z3] - frames.cpp:18:23: error: verification failed: overwritten [...::postcondition@18:23] (counterexample: ...) [backend=z3] [reason=counterexample] + frames.cpp:18:23: error: verification failed: overwritten [postcondition] [backend=z3] [reason=counterexample] + 18 | cv::post(cv::result == 7) + | ~~~~~~~~~~~^~~~ + frames.cpp:18:23: note: counterexample: i = 0 -> 4, result = -2147483648 + frames.cpp:20:3: note: creates a local object at address 4 + 20 | int a[4]; + | ^ + frames.cpp:21:8: note: stores 7 at address 16 + 21 | a[3] = 7; + | ^ + frames.cpp:22:3: note: enters the loop + 22 | for (int i = 0; i < 4; i = i + 1) + | ^ + frames.cpp:22:3: note: leaves the loop + frames.cpp:28:3: note: returns -2147483648 + 28 | return a[3]; + | ^ + note: the counterexample's state at the loop at 22:3 is any state its invariants allow, not necessarily one an execution reaches + note: an execution fails this check within 4 iterations of each loop (counterexample: i = 0 -> 1 -> 2 -> 3 -> 4, result = 0) ``overwritten`` fails because the loop writes ``a[3]``, which no invariant describes. Before 0.2 a loop that wrote local or dynamic storage forgot the @@ -1065,7 +1142,19 @@ contract can say directly: .. code-block:: text Verified: filled [backend=z3] - fill.cpp:26:3: error: verification failed: partially_filled [...::initialization@26:3] (counterexample: ...) [backend=z3] [reason=counterexample] + fill.cpp:25:3: error: verification failed: partially_filled [initialization] [backend=z3] [reason=counterexample] + 25 | return a[3]; + | ^ + fill.cpp:25:3: note: counterexample: i = 0 -> 3 + fill.cpp:17:3: note: creates a local object at address 9132 + 17 | int a[4]; + | ^ + fill.cpp:18:3: note: enters the loop + 18 | for (int i = 0; i < 3; i = i + 1) + | ^ + fill.cpp:18:3: note: leaves the loop + note: the counterexample's state at the loop at 18:3 is any state its invariants allow, not necessarily one an execution reaches + note: an execution fails this check within 3 iterations of each loop (counterexample: i = 0 -> 1 -> 2 -> 3) No lifetime begins or ends inside a loop, so what was initialized before it stays initialized in every iteration and after it; an element the loop may @@ -1135,7 +1224,13 @@ The path ends there, so after ``assert(c)`` the condition holds. With .. 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] + aborts.cpp:16:5: error: verification failed: aborts [unreachable] [backend=z3] [reason=counterexample] + 16 | abort(); + | ^ + aborts.cpp:16:5: note: counterexample: x = -1 + aborts.cpp:15:3: note: takes the true branch + 15 | if (x < 0) + | ^ 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 @@ -1217,7 +1312,22 @@ switch over its enumerators may match none: .. code-block:: text - channel.cpp:4:12: error: verification failed: channel [...::postcondition@4:12] (counterexample: result [ssa=__result_1] [type=i32] = -1, c [ssa=c_0] [type=u32] = 3; ...) [backend=z3] [reason=counterexample] + channel.cpp:4:23: error: verification failed: channel [postcondition] [backend=z3] [reason=counterexample] + 4 | cv::post(cv::result >= 0) + | ~~~~~~~~~~~^~~~ + channel.cpp:4:23: note: counterexample: c = 3, result = -1 + channel.cpp:7:3: note: takes the false branch + 7 | case Red: + | ^ + channel.cpp:9:3: note: takes the false branch + 9 | case Green: + | ^ + channel.cpp:11:3: note: takes the false branch + 11 | case Blue: + | ^ + channel.cpp:14:3: note: returns -1 + 14 | return -1; + | ^ ``Color`` holds 0 to 3, the values of the smallest bit-field that fits its enumerators, and ``channel(Color(3))`` is defined C++ that returns -1. diff --git a/website/source/language/ghost-proofs.rst b/website/source/language/ghost-proofs.rst index 260db63f3a..42e4a8d3b5 100644 --- a/website/source/language/ghost-proofs.rst +++ b/website/source/language/ghost-proofs.rst @@ -171,7 +171,10 @@ integer, and adding to it never wraps. .. code-block:: text Verified: large [backend=z3] - reads.cpp:13:24: error: verification failed: wraps [...::postcondition@13:24] (counterexample: ...) [backend=z3] [reason=counterexample] + reads.cpp:13:24: error: verification failed: wraps [postcondition] [backend=z3] [reason=counterexample] + 13 | cv::post(pair_sum(p) < 0) + | ~~~~~~~~~~~~^~~ + reads.cpp:13:24: note: counterexample: p = -1 Before 0.2, a value read in a spec body kept its machine type, so the sum above wrapped to a negative ``int``: ``wraps`` verified, and ``large`` could @@ -284,7 +287,10 @@ among ``a[0]`` to ``a[n - 1]``, inside its frame; ``index_past`` also reads .. code-block:: text Verified: spec reads: 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) + reads.cpp:10:34: error: spec reads failed: index_past [backend=z3] [reason=counterexample] + 10 | return cv::choose(k, 0, n + 1, a[k] == x); + | ^ + reads.cpp:10:34: note: counterexample: a = 0, n = 0 The counterexample is the empty buffer: with ``n = 0``, ``index_past`` reads ``a[0]``, and ``cppverify::reads(a, 0)`` declares no cell. @@ -317,6 +323,13 @@ wherever the statement appears: return x + x + x; } +A specialization of a spec template is named with its arguments, +``cv::hide(at<4>)`` or ``cv::reveal_with_fuel(steps<2>, 4)``. The template's +name alone names none, which is an error (``reference to overloaded function +could not be resolved``). Before 0.2 a specialization could not be named at +all: the function stopped with ``unsupported C++ type in verification: +``. + With fuel 1 a proof sees one unfolding of each application it names, so ``pow2(3) == 8`` needs three more: diff --git a/website/source/language/integers.rst b/website/source/language/integers.rst index f68dad1eae..cde4303c9c 100644 --- a/website/source/language/integers.rst +++ b/website/source/language/integers.rst @@ -130,12 +130,19 @@ contract that means the wrapped value says so with ``% 2^N``. .. code-block:: text - abs.cpp:2:3: error: verification failed: abs_unguarded [...::overflow@2:3] - (counterexample: x [type=i32] = -2147483648) + abs.cpp:2:3: error: verification failed: abs_unguarded [overflow] [backend=z3] [reason=counterexample] + 2 | { return x < 0 ? -x : x; } + | ^ + abs.cpp:2:3: note: counterexample: x = -2147483648 Verified: abs_guarded [backend=z3] Verified: mix [backend=z3] - abs.cpp:10:64: error: verification failed: mix_wrong [...::postcondition@10:64] - (counterexample: result [type=u32] = 0, a [type=u32] = 1, ...) + abs.cpp:10:64: error: verification failed: mix_wrong [postcondition] [backend=z3] [reason=counterexample] + 10 | unsigned mix_wrong(unsigned a, unsigned b) cv::post(cv::result == a + b) + | ~~~~~~~~~~~^~~~~~~~ + abs.cpp:10:64: note: counterexample: a = 1, b = 4294967295, result = 0 + abs.cpp:11:3: note: returns 0 + 11 | { return a + b; } + | ^ Negating ``INT_MIN`` overflows, so ``abs_unguarded`` fails at the overflow check with that input, and the precondition the tool asks for makes @@ -194,7 +201,10 @@ shift is undefined, and the verifier finds the input: .. code-block:: text - shift.cpp:5:3: error: verification failed: doubled [...::shift@5:3] (counterexample: x [ssa=x_0] [type=i32] = -1; trace: return.return@5:3 value=-2) [backend=z3] [reason=counterexample] + shift.cpp:5:3: error: verification failed: doubled [shift] [backend=z3] [reason=counterexample] + 5 | return x << 1; + | ^ + shift.cpp:5:3: note: counterexample: x = -1 Compiled as C++20, with ``cpp-verify shift.cpp -- -std=c++20``, the same function verifies: @@ -302,7 +312,10 @@ of range, so the conversion is an ``overflow`` obligation: Verified: code [backend=z3] Verified: from_code [backend=z3] - colors.cpp:18:3: error: verification failed: unchecked [...::overflow@18:3] (counterexample: v [ssa=v_0] [type=i32] = -1; ...) [backend=z3] [reason=counterexample] + colors.cpp:18:3: error: verification failed: unchecked [overflow] [backend=z3] [reason=counterexample] + 18 | return static_cast(v); + | ^ + colors.cpp:18:3: note: counterexample: v = -1 ``code`` may return ``3``: no enumerator has that value, but ``Color`` does. With a fixed underlying type, every value of that type is possible: @@ -321,7 +334,13 @@ With a fixed underlying type, every value of that type is possible: .. code-block:: text - levels.cpp:4:28: error: verification failed: level [...::postcondition@4:28] (counterexample: result [ssa=__result_1] [type=i32] = 3, l [ssa=l_0] [type=u8] = 3; ...) [backend=z3] [reason=counterexample] + levels.cpp:4:28: error: verification failed: level [postcondition] [backend=z3] [reason=counterexample] + 4 | cv::post(0 <= cv::result && cv::result <= 2) + | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~ + levels.cpp:4:28: note: counterexample: l = 3, result = 3 + levels.cpp:6:3: note: returns 3 + 6 | return static_cast(l); + | ^ The range is a fact about machine values. In a ``cv::spec``, a parameter of an enumeration type is a mathematical integer like any other, so a spec's claim diff --git a/website/source/language/limitations.rst b/website/source/language/limitations.rst index 4b081ebfa8..0e85aecf39 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -152,8 +152,8 @@ C++ feature boundaries temporaries/lifetime extension, reference members, and forwarding; a local not fully initialized passed to a callee (``construct.unsupported``), the address of a local stored in a - variable, and a pointer formed through a local reference to a local - record. + variable or taken in ghost code, and a pointer formed through a local + reference to a local record. * - Arrays - Pointer-parameter indexing, quantified abstract buffers, fixed local arrays (including arrays of records and multidimensional arrays) @@ -260,7 +260,11 @@ CppVerify does not yet model: - base subobjects, vtables, or virtual dispatch; - temporary materialization and lifetime extension; - exception cleanup and exactly-once destruction; -- placement construction, lifetime restart, or ``std::launder``. +- placement construction, lifetime restart, or ``std::launder``; +- spec member functions (``cv::spec int at(int i) const``), and spec + parameters of class reference type (``const Ring &``): a view of a + class is a free spec function that takes a pointer, a friend when it reads + private fields (:doc:`../case-studies/ring-buffer`). Scalar parameter references, local scalar aliases, and scalar field/element bindings into promoted local objects are addressable aliases. General aggregate @@ -618,8 +622,8 @@ remain future work. **cvc5 and strict portfolio** consume the same canonical obligations as Z3 through an independent SMT-LIB2 emitter. cvc5 is optional and system-installed; -its models are checked against the definitions like Z3's but are not yet shown -as source-level values. Strict portfolio mode requires matching decisive +its models are checked against the definitions like Z3's and shown like Z3's, +in source names with the steps of the failing path. Strict portfolio mode requires matching decisive results and preserves Z3's model only after both solvers produce checked counterexamples. cvc5 may return ``unknown`` on quantified or heap-heavy formulas that Z3 solves; strict mode then remains ``Unresolved``. Recursive @@ -704,7 +708,8 @@ Diagnostics and developer tooling Diagnostics name each obligation they report by a stable source-anchored ID and range, show counterexamples with source variable names and a trace of branches, calls, loops, heap writes, and lifetimes, and are also emitted as -JSON Lines (``--diagnostics-format=json``). ``--profile-quantifiers`` reports +JSON Lines (``--diagnostics-format=json``), SARIF (``sarif``), and GitHub +annotations (``github``). ``--profile-quantifiers`` reports quantifier instantiations, ``--solver-rlimit`` sets reproducible solver budgets, and clangd, built with the tool, provides editor integration. Production readiness still needs: @@ -776,8 +781,8 @@ P4 — libraries and application ecosystem ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Add verified/conformance-tested standard-library models, mathematical views, -build-system integration, SARIF diagnostics, and stable multi-file incremental -verification. clangd support and JSON Lines diagnostics already ship. +build-system integration, and stable multi-file incremental verification. +clangd support, JSON Lines and SARIF diagnostics already ship. P5 — advanced systems C++ ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/website/source/language/pointers.rst b/website/source/language/pointers.rst index 77d3685988..dffe2dc817 100644 --- a/website/source/language/pointers.rst +++ b/website/source/language/pointers.rst @@ -173,10 +173,24 @@ since the callee may read it first. Verified: copy_to [backend=z3] Verified: seven [backend=z3] Verified: five_or_zero [backend=z3] - out.cpp:48:3: error: verification failed: read_after_miss [...::initialization@48:3] (...) [backend=z3] [reason=counterexample] + out.cpp:48:3: error: verification failed: read_after_miss [initialization] [backend=z3] [reason=counterexample] + 48 | return a; + | ^ + out.cpp:46:3: note: creates a local object at address 4 + 46 | int a; + | ^ + out.cpp:47:3: note: calls try_get(4, false) + 47 | try_get(a, false); + | ^ Verified: copied [backend=z3] out.cpp:65:3: Unresolved: copied_garbage [backend=z3] [reason=construct.unsupported] (the verifier cannot model a construct here: binding a reference or pointer parameter to storage that is not initialized, which the callee may read before writing it) - out.cpp:74:3: error: verification failed: read_too_early [...::initialization@74:3] (...) [backend=z3] [reason=counterexample] + out.cpp:74:3: error: verification failed: read_too_early [initialization] [backend=z3] [reason=counterexample] + 74 | int copy = r; + | ^ + out.cpp:74:3: note: counterexample: &r = 4 + out.cpp:72:3: note: creates a local object at address 4 + 72 | int a; + | ^ - ``copy_to``'s ``to`` is an out-parameter and ``from`` an input: ``copied_garbage`` passes ``b`` uninitialized, which ``copy_to`` reads. @@ -413,7 +427,11 @@ a pointer into an object the caller may write: Verified: put [backend=z3] Verified: set_third [backend=z3] Verified: set_through_copy [backend=z3] - frames.cpp:30:3: error: verification failed: set_fourth [...::frame@30:3] (counterexample: a [ssa=a_0] [type=pointer] = 1; trace: call.put@30:3 arg0=13 arg1=5) [backend=z3] [reason=counterexample] + frames.cpp:30:3: error: verification failed: set_fourth [frame] [backend=z3] [reason=counterexample] + 30 | put(a + 3, 5); + | ^ + frames.cpp:30:3: note: counterexample: a = 1 + frames.cpp:30:3: note: calls put(13, 5) ``set_fourth`` may write only ``a[2]``, at address 9 when ``a`` is 1, but passes ``a + 3``, address 13. A pointer-taking @@ -528,7 +546,10 @@ end but may not start one before the beginning: .. code-block:: text Verified: total [backend=z3] - walk.cpp:22:3: error: verification failed: back_one [...] (counterexample: ...) [backend=z3] [reason=counterexample] + walk.cpp:22:3: error: verification failed: back_one [bounds] [backend=z3] [reason=counterexample] + 22 | --q; + | ^ + walk.cpp:22:3: note: counterexample: a = 1, n = 1, q = 1 The value of a step inside an expression, as in ``*q++`` or ``while (*q++ != 0)``, is not supported yet: write the read and the step as @@ -577,7 +598,10 @@ Each index must lie in its array, an obligation of kind ``bounds``: 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] + tables.cpp:21:3: error: verification failed: outside [bounds] [backend=z3] [reason=counterexample] + 21 | return NibbleBits[x]; + | ^ + tables.cpp:21:3: note: counterexample: x = -1 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 @@ -687,7 +711,10 @@ with one null operand is undefined and fails: .. code-block:: text Verified: two_nulls [backend=z3] - nulls.cpp:11:3: error: verification failed: one_null [...::pointer-difference@11:3] (counterexample: p [ssa=p_0] [type=pointer] = 0, q [ssa=q_0] [type=pointer] = 1; ...) [backend=z3] [reason=counterexample] + nulls.cpp:11:3: error: verification failed: one_null [pointer-difference] [backend=z3] [reason=counterexample] + 11 | return p - q; + | ^ + nulls.cpp:11:3: note: counterexample: p = 0, q = 1 Before 0.2, ``two_nulls`` failed the same way. @@ -764,7 +791,10 @@ type: .. code-block:: text Verified: get [backend=z3] - error: verification failed: past_end [...::bounds@10:10] + example.cpp:10:10: error: verification failed: past_end [bounds] [backend=z3] [reason=counterexample] + 10 | { return p[n]; } + | ^ + example.cpp:10:10: note: counterexample: p = 1, n = 1 The marker means ``n >= 0``. A positive extent also means ``p`` is non-null and abstractly valid; an extent of zero permits null. Every ``p[i]`` or ``*(p + i)`` @@ -981,7 +1011,11 @@ where it points; the elements must lie in the objects it may address: Verified: first [backend=z3] Verified: second [backend=z3] - slices.cpp:20:3: error: verification failed: third [...::bounds@20:3] (counterexample: a [ssa=a_0] [type=pointer] = 1, q [ssa=q_1] [type=pointer] = 9; trace: call.first@20:3 arg0=9 arg1=3) [backend=z3] [reason=counterexample] + slices.cpp:20:3: error: verification failed: third [bounds] [backend=z3] [reason=counterexample] + 20 | return first(q, 3); + | ^ + slices.cpp:20:3: note: counterexample: a = 1, q = 9 + slices.cpp:20:3: note: calls first(9, 3) ``third`` passes three elements from ``a + 2``, of which only two are in ``a``: with ``a`` at address 1, ``q`` is 9, and the call would read up to diff --git a/website/source/language/structs.rst b/website/source/language/structs.rst index 296e2830d1..e91d96e0b1 100644 --- a/website/source/language/structs.rst +++ b/website/source/language/structs.rst @@ -84,7 +84,10 @@ footprint, bind a reference to it, and pass it to a reference parameter. Verified: peek [backend=z3] Verified: put [backend=z3] - ring.cpp:25:3: error: verification failed: slot [...::bounds@25:3] (counterexample: i [ssa=i_0] [type=i32] = 8, r [ssa=r_0] [type=pointer] = 1; ...) [backend=z3] [reason=counterexample] + ring.cpp:25:3: error: verification failed: slot [bounds] [backend=z3] [reason=counterexample] + 25 | return r->buf[i]; + | ^ + ring.cpp:25:3: note: counterexample: r = 1, i = 8 Each index is checked against its own array, as Frama-C's RTE guards are, and not only against the object. ``r->buf[8]`` has the address of @@ -196,7 +199,10 @@ of bounds: Verified: first [backend=z3] Verified: oldest [backend=z3] Verified: total [backend=z3] - ring.cpp:41:10: error: verification failed: past [...::bounds@41:10#2] (counterexample: q [ssa=q_1] [type=pointer] = 1, r [ssa=r_0] [type=pointer] = 1) [backend=z3] [reason=counterexample] + ring.cpp:41:10: error: verification failed: past [bounds] [backend=z3] [reason=counterexample] + 41 | return q[8]; + | ^ + ring.cpp:41:10: note: counterexample: r = 1, q = 1 The rules follow C++: @@ -311,7 +317,11 @@ parameter may be bound to a reference, to ``*p``, to a field such as Verified: reset [backend=z3] Verified: reset_left [backend=z3] Verified: bump_left [backend=z3] - pair.cpp:46:3: error: verification failed: reset_count [...::frame@46:3] (counterexample: p [ssa=p_0] [type=pointer] = 1; trace: call.reset@46:3 arg0=1) [backend=z3] [reason=counterexample] + pair.cpp:46:3: error: verification failed: reset_count [frame] [backend=z3] [reason=counterexample] + 46 | reset(p.left); + | ^ + pair.cpp:46:3: note: counterexample: &p = 1 + pair.cpp:46:3: note: calls reset(1) ``reset_left`` may write ``p.left``, which holds the whole object ``reset`` may write, so ``p.right`` keeps its value. ``reset_count`` may write only @@ -431,9 +441,13 @@ and every other local keeps its value. The caller's own leave ``c.limit`` uninitialized; ``limit_of`` reads ``c.limit``, which ``no_limit`` never initializes. -The address of a local is supported as a call argument only. Stored in a -pointer variable (``Counter *q = &c;``), or formed through a local reference -to a local (``&r.n``, ``r.buf``), it is still an error. Under +The address of a local is supported as a call argument, and in a contract, +where a spec it is passed to cannot keep it: ``size(&r)`` in a loop +invariant or a ``cv::check`` of a local object ``r`` (the case study +:doc:`../case-studies/ring-buffer` reads its ring through such views). Stored +in a pointer variable (``Counter *q = &c;``), or formed through a local +reference to a local (``&r.n``, ``r.buf``), it is still an error, and so is +an address taken in ghost code, which could keep it. Under ``--no-check-ub`` the callee's accesses are not proved to lie in its object, so a call that passes a local record is ``construct.unsupported``. @@ -509,7 +523,22 @@ leaves ``b.len`` alone; two buffers through pointers to const may be one. Verified: zero [backend=z3] Verified: zero_prefix [backend=z3] - buf.cpp:39:3: error: verification failed: zero_more [...::bounds@39:3] (counterexample: n [ssa=n_0] [type=i32] = 0, p [ssa=p_0] [type=pointer] = 1; trace: allocation.automatic allocation@36:3 address=231056 provenance=18 -> heap-write.store@37:10 address=231056 value=1 -> heap-write.store@38:9 address=231064 value=1 -> call.zero@39:3 arg0=231056) [backend=z3] [reason=counterexample] + buf.cpp:39:3: error: verification failed: zero_more [bounds] [backend=z3] [reason=counterexample] + 39 | zero(b); + | ^ + buf.cpp:39:3: note: counterexample: p = 1, n = 0 + buf.cpp:36:3: note: creates a local object at address 231056 + 36 | Buf b; + | ^ + buf.cpp:37:10: note: stores 1 at address 231056 + 37 | b.data = p; + | ^ + buf.cpp:38:9: note: stores 1 at address 231064 + 38 | b.len = n + 1; + | ^ + buf.cpp:39:3: note: calls zero(231056) + 39 | zero(b); + | ^ ``zero_more`` passes a buffer one element longer than the extent of ``p``. A field given another pointer in the body (``b.data = q;``) addresses that @@ -605,8 +634,16 @@ free function. Verified: Counter::reset [backend=z3] Verified: Counter::copy_from [backend=z3] Verified: counted [backend=z3] - counter.cpp:49:5: error: verification failed: reset_count [...::frame@49:5] (counterexample: c [ssa=c_0] [type=pointer] = 1; trace: call.reset@49:5 arg0=1) [backend=z3] [reason=counterexample] - counter.cpp:55:5: error: verification failed: self_copy [...::aliasing@55:5] (counterexample: c [ssa=c_0] [type=pointer] = 1; trace: call.copy_from@55:5 arg0=1 arg1=1) [backend=z3] [reason=counterexample] + counter.cpp:49:5: error: verification failed: reset_count [frame] [backend=z3] [reason=counterexample] + 49 | c.reset(); + | ^ + counter.cpp:49:5: note: counterexample: &c = 1 + counter.cpp:49:5: note: calls reset(1) + counter.cpp:55:5: error: verification failed: self_copy [aliasing] [backend=z3] [reason=counterexample] + 55 | c.copy_from(c); + | ^ + counter.cpp:55:5: note: counterexample: &c = 1 + counter.cpp:55:5: note: calls copy_from(1, 1) ``reset`` states no ``cppverify::modifies``, so it may write all of ``c``, which ``reset_count`` may not. ``copy_from`` writes ``this->n`` and reads @@ -748,7 +785,19 @@ object after the call: .. code-block:: text Verified: Ring::Ring [backend=z3] - ring.cpp:24:7: error: verification failed: Ring::Ring [...::initialization@24:7#2] (counterexample: ...) [backend=z3] [reason=counterexample] + ring.cpp:24:7: error: verification failed: Ring::Ring [initialization] [backend=z3] [reason=counterexample] + 24 | int buf[8]; + | ^ + ring.cpp:24:7: note: counterexample: this = 11798, first = 0 + ring.cpp:19:7: note: stores 0 at address 11830 + 19 | : head(0), count(1) + | ^ + ring.cpp:19:16: note: stores 1 at address 11834 + 19 | : head(0), count(1) + | ^ + ring.cpp:21:12: note: stores 0 at address 11798 + 21 | buf[0] = first; + | ^ Verified: fresh [backend=z3] The second constructor initializes ``buf[0]`` alone, and the failure is @@ -876,7 +925,13 @@ internal calls included, so no call observes the object while it is broken: Verified: Meter::Meter [backend=z3] Verified: Meter::add [backend=z3] - meter.cpp:28:33: error: verification failed: Meter::overflow [...::type-invariant@28:33] (counterexample: ...) [backend=z3] [reason=counterexample] + meter.cpp:28:33: error: verification failed: Meter::overflow [type-invariant] [backend=z3] [reason=counterexample] + 28 | cv::type_invariant(0 <= level && level <= 100); + | ~~~~~~~~~~~^~~~~~~~~~~~~~~ + meter.cpp:28:33: note: counterexample: this = 1 + meter.cpp:17:11: note: stores 101 at address 1 + 17 | level = 101; + | ^ Verified: Meter::get [backend=z3] Verified: filled [backend=z3] @@ -897,6 +952,69 @@ internal calls included, so no call observes the object while it is broken: entry as in a precondition (`Extents of pointer fields`_). At exit it states nothing; the next call checks it from what its caller knows, so a member function that changes ``len`` states how. +- A loop outside the class keeps the invariant of every object it reaches: + the local objects alive at the loop and those its function's reference + and pointer parameters address. Only the class's members, and code that + writes a public field, change what the invariant states, and a public + member keeps it, so the verifier adds it to the loop's invariants, where + it is checked at entry and kept by every iteration like the user's own. + A loop in a member function of the class gets none of its class's, since + the member may break it between calls. + +.. code-block:: cpp + + class Tally { + public: + Tally() cv::post(count_of(this) == 0) : count(0), last(0) {} + + void add(int d) + cv::pre(0 <= d && d <= 10 && count_of(this) <= 1000) + cv::post(count_of(this) == cv::old(count_of(this)) + d) + { + last = d; + count = count + d; + } + + friend cv::spec int count_of(const Tally *t); + + private: + int count; + int last; + + public: + cv::type_invariant(0 <= last && last <= count); + }; + + cv::spec int count_of(const Tally *t) { return t->count; } + + int count_to(int n) + cv::pre(0 <= n && n <= 100) + cv::post(cv::result == n) + { + Tally t; + int i = 0; + while (i < n) + cv::invariant(0 <= i && i <= n && count_of(&t) == i) + cv::decreases(n - i) + { + t.add(1); + i = i + 1; + } + return i; + } + +.. code-block:: text + + Verified: Tally::Tally [backend=z3] + Verified: Tally::add [backend=z3] + Verified: count_to [backend=z3] + +Before 0.2, ``count_to`` was rejected: ``&t`` could not appear in a contract +(``raw address-of is unsupported until lexical lifetime and escape effects +are modeled``). A loop that called ``add`` on an object a reference +parameter names failed at that call (``[type-invariant]``): at the loop the +verifier knew of the object only what the loop's invariants said, and an +invariant over private fields could not be stated there. Class templates --------------- @@ -951,8 +1069,16 @@ it, and the class's type invariants with the class. Verified: Box::Box [backend=z3] Verified: Box::twice [backend=z3] Verified: Box::Box [backend=z3] - box.cpp:10:25: error: verification failed: Box::twice [...::postcondition@10:25] (counterexample: ...) [backend=z3] [reason=counterexample] + box.cpp:10:25: error: verification failed: Box::twice [postcondition] [backend=z3] [reason=counterexample] + 10 | cv::post(cv::result == 2 * value) + | ~~~~~~~~~~~^~~~~~~~~~~~ + box.cpp:10:25: note: counterexample: this = 1, result = -32768 + box.cpp:12:5: note: returns -32768 + 12 | return value + value; + | ^ box.cpp:33:15: note: in instantiation of member function 'Box::twice' requested here + 33 | short d = b.twice(); + | ^ Verified: doubled [backend=z3] Unresolved: doubled_short [backend=z3] [reason=callee.contract] (relies on the contract of Box::twice, which is not established) diff --git a/website/source/language/tooling.rst b/website/source/language/tooling.rst index 586565411c..88eef27164 100644 --- a/website/source/language/tooling.rst +++ b/website/source/language/tooling.rst @@ -35,6 +35,8 @@ Standalone verifier cpp-verify --solver-rlimit=500000 --max-query-nodes=50000 file.cpp cpp-verify --memory-limit=4096 file.cpp cpp-verify --diagnostics-format=json file.cpp + cpp-verify --diagnostics-format=sarif file.cpp > results.sarif + cpp-verify --diagnostics-format=github file.cpp cpp-verify --dump-ir=1,2,3,4 file.cpp cpp-verify --lower-only --dump-ir=1,2,3,4 file.cpp cpp-verify --lower-only --obligation-out=goals.cpv file.cpp @@ -206,8 +208,31 @@ one line that counts them: .. 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 + summary.cpp:14:23: error: verification failed: add_two [postcondition] [backend=z3] [reason=counterexample] + 14 | cv::post(cv::result == twice(x)) + | ~~~~~~~~~~~^~~~~~~~~~~ + summary.cpp:14:23: note: counterexample: x = 3, result = 5 + summary.cpp:16:3: note: returns 5 + 16 | return x + 2; + | ^ + Summary: 1 verified, 1 failed in 0.0 s + +A failure prints as Clang prints an error: the kind of check that fails +(``[postcondition]``), the backend and the reason, and the source with a caret +under the check; then a note with the counterexample's values by their source +names, in the order the source declares them (``result`` last), each with +the values it takes along the path in turn (``i = 0 -> 1``), not those of +branches the path does not take, and a reference as the address it refers to +(``&r = 4``); then a note at each step +of the path it takes: the branches, the calls with their arguments, a loop's +entry and an arbitrary iteration of it, the memory it writes, and the value it +returns. A path of more than ten steps shows its first and last five. A step +inside a macro is shown where the macro is used, and an operation whose own +check fails is no step of the path: a failing check in ``return e;`` ends the +path before the return. A value +the counterexample does not determine is ````. JSON keeps what the +text leaves out (see `Structured diagnostics`_). Errors and warnings show their +source line the same way; verdicts are one line each. The summary counts each kind of verdict the run reported (verified, which includes ``Certified`` and ``Proved (z3+lean)``, failed, unresolved, @@ -215,9 +240,9 @@ 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. +run. In JSON it is the last record, and in SARIF a property of the run (see +`Structured diagnostics`_); ``--list-functions`` and archive replay +(``--obligation-in``), which prints a line per module, have none. - ``--quiet`` prints only what needs attention: failures, unresolved results, errors, warnings, bounded results, and interrupted functions, then the @@ -682,14 +707,20 @@ Backends * - ``--proof-cache-max-entries=N`` - Bound the number of proof-cache records (default 100000; ``0`` disables the entry limit). Requires ``--proof-cache``. - * - ``--diagnostics-format={text,json}`` - - Select Clang-style text (default) or versioned JSON Lines for verification - results. JSON records use schema ``cppverify.diagnostic/1``. + * - ``--diagnostics-format={text,json,sarif,github}`` + - Select Clang-style text (default); JSON Lines of + ``cppverify.diagnostic/2`` records ending with a + ``cppverify.summary/1`` record; one SARIF 2.1.0 log at the end of the + run; or the text with a GitHub workflow annotation for each error and + warning (see `Structured diagnostics`_). * - ``--lower-only`` - Run Clang conversion, backend-specific preparation, passivization, canonical Obligation IR construction, spec-axiom encoding, and selected Z3/SMT-LIB translation without calling a solver. Supported for Z3, cvc5, portfolio, and BMC. + * - ``--dump-smt2=DIR`` + - Write every query sent to Z3 or cvc5 to ``DIR``, each as a script the + solver replays on its own (see `Solver queries as scripts`_). * - ``--obligation-out=FILE`` - Write deterministic, versioned backend-neutral modules with portable source attribution and SHA-256 semantic identities. Multiple modules are @@ -719,7 +750,9 @@ Backend results are intentionally distinct. Z3 ``unsat`` reports ``Verified``; a ``sat`` model reports a failed source obligation once it is checked against the true definitions (otherwise ``Unresolved`` with a reason); timeout and ``unknown`` report ``Unresolved``. cvc5 follows the same mapping, and its models -are checked the same way. +are checked the same way, then shown the same way: a failure's counterexample +and path, in text and in JSON's ``counterexample``, ``model``, and ``trace``, +come from the checked model. Portfolio mode requires agreement; an agreed ``sat`` result uses Z3's typed model and trace, while disagreement or incomplete secondary evidence is ``Unresolved``. BMC reports ``BoundedSafe: f [backend=bmc, bound=N]`` when only its unwinding @@ -730,6 +763,97 @@ reused across prefixes. JSON uses ``bound``, ``explored_bounds``, and ``reused_queries``. Lean generation reports ``Exported``. Only the pinned, admission-free kernel workflow reports ``Certified``. +Solver queries as scripts +------------------------- + +``--dump-smt2=DIR`` writes each query cpp-verify sends to a solver as an +SMT-LIB script that the solver runs on its own: ``z3 FILE`` or ``cvc5 +FILE``, with no other flags. Such a file is what a solver's bug report +needs, what compares two solver versions on the same query, and what shows +the exact formula behind a verdict. On ``clamp.cpp`` (from +:doc:`../book/part-ii/ch17-backends-modular-calls`): + +.. code-block:: text + + $ cpp-verify --dump-smt2=queries clamp.cpp + ... + $ find queries -type f | sort + queries/clamp-14e9c9/missing-return-7-3.z3-c326aaab.smt2 + queries/clamp-14e9c9/overflow-8-5.z3-19b38e3e.smt2 + queries/clamp-14e9c9/postcondition-5-23.z3-d86b7e26.smt2 + queries/clamp-14e9c9/whole.z3-3a4644f7.smt2 + +A function's queries are in a directory named by the function and the start +of the SHA-256 hash of its identity, so overloads stay apart; each file is +named by its obligation (``postcondition-5-23``, the postcondition at line 5, +column 23), or ``whole`` for every obligation at once, then the solver and +the start of the hash of the script itself. A name depends only on the query, +so the same query is the same file in every run, and two checks of one +obligation (a refinement round after the first check, say) are two files. +A check that cpp-verify adds is named after the function and the check, +every character but a letter, a digit, or ``_`` made ``_``: ``f_smoke`` +holds the vacuity checks of ``f``, and ``f_decreases`` the termination check +of a spec ``f``. + +A file starts with comments that say what it is, then the options cpp-verify +gives the solver, as commands, then the query and its check. After the check +come ``(get-model)`` when the answer was ``sat``, and the answer cpp-verify +got: + +.. code-block:: text + + ; cpp-verify 0.2.0-dev, z3 4.13.4 + ; clamp: the postcondition check at clamp.cpp:5:23 (fn_5f5a35636c616d7069::postcondition@5:23) + ; replay: z3 clamp-14e9c9/postcondition-5-23.z3-d86b7e26.smt2 + (set-option :timeout 30000) + (set-option :rlimit 6000000) + (set-option :smt.mbqi true) + (set-option :smt.qi.eager_threshold 0.0) + (declare-fun __result_1 () Int) + (declare-fun x_0 () Int) + ... + (check-sat) + (get-model) + ; cpp-verify: sat + +.. code-block:: text + + $ cd queries && z3 clamp-14e9c9/postcondition-5-23.z3-d86b7e26.smt2 + sat + ( + (define-fun __result_1 () Int + (- 1)) + ... + (define-fun x_0 () Int + (- 2)) + ) + +- **Which queries.** Every check a solver makes: the whole query and each + obligation's, every refinement round, the probes of a bounded domain, the + native-definition pass, the inductions, the confirmations, the vacuity and + contract checks, and the queries of an archive's replay. A query is + written before its check, so one that never returns still leaves its + file, and the answer is appended when the check ends: ``sat``, ``unsat``, + ``unknown`` with Z3's reason (``unknown (timeout)``), or, for cvc5, ``no + answer`` with what went wrong. ``--lower-only`` checks nothing, so it + writes nothing, and Lean writes its own project. +- **Options.** For Z3, the time the check was given (``:timeout``, which + can be less than ``--timeout`` for a check late in a query's budget), the + resource limit, and the quantifier settings, and ``(check-sat-using smt)`` + where cpp-verify solves a quantified query with the ``smt`` tactic. For + cvc5, its command-line options (``--seed=0``, ``--tlimit-per=N``, ...) as + ``(set-option ...)`` commands. +- **Replaying.** ``z3 FILE`` with the version in the first line (``z3 + --version``; cpp-verify links Z3 4.13.4) and ``cvc5 FILE`` give the answer + cpp-verify got for most queries. Z3's search on a quantified query depends + on the order in which its terms were created, and a replay creates them + as it reads the file, in another order than cpp-verify did, so such a + query can end differently, typically ``unknown`` where cpp-verify got an + answer. +- **Errors.** A directory that cannot be written is a warning at the end of + the run (``warning: --dump-smt2: cannot create ...``); verdicts do not + change. + Time limits ----------- @@ -1012,9 +1136,8 @@ kernel checking is required. Structured diagnostics ---------------------- -Failed Z3, cvc5, portfolio, and BMC results identify a source-anchored -obligation such as ``function-identity::overflow@line:column#2``. The kind says -what the obligation checks: +A failure names the kind of the check that fails, as ``[overflow]``. The +kind says what the check is: - contracts: ``assertion``, ``precondition``, ``postcondition``, ``invariant-entry``, ``invariant-preserved``, ``termination``, @@ -1034,20 +1157,84 @@ what the obligation checks: verifier rejects fail-closed. The check that a function's contract is defined (see :doc:`functions-loops`) -is reported only when it fails, as ``contract of f``. Its obligations are -named ``function-identity::contract::kind@line:column`` with the definedness -kinds above, and JSON records give ``f`` as the function. +is reported only when it fails, as ``contract of f``, with the definedness +kinds above; JSON records give ``f`` as the function. + +JSON Lines +~~~~~~~~~~ + +``--diagnostics-format=json`` prints one ``cppverify.diagnostic/2`` record +per result, and the run ends with one ``cppverify.summary/1`` record, the +counts of the text summary by name (``verified``, ``failed``, +``unresolved`` with its ``reasons``, ``bounded_safe``, ``trusted``, +``lowered``, ``exported``, ``interrupted``). No record holds a time, so a +run's records are the same at any ``--jobs``; ``--time-report`` adds +``cppverify.time/1`` records. A record has: + +- ``status`` (``verified``, ``failed``, ``unresolved``, ``warning``, + ``frontend``, ...), ``severity`` (``error``, ``warning``, ``note``), and + ``message``, the text of its line; for a failure, ``reason``; +- ``function``, and ``function_source``, the function's name where it is + declared; +- ``source``, what the result is about: for a failure the expression its + check checks. A source is ``file``, ``line``, ``column``, ``end_line``, + ``end_column``: from its first character to the column after its last, as + SARIF and editors count, on presumed (``#line``) lines; +- for a failure, ``obligation`` with the check's ``id`` and ``kind``; + ``counterexample``, the values as the note prints them; ``model``, every + value with its source name, its internal SSA name (``ssa_name``), its sort + (``bool``, ``i32``, ``u32``, ``math-i32``, ``pointer``, ``heap``, ...), and + ``"on_path": false`` for a version of a branch the path does not take; and + ``trace``, each step with its ``kind``, its ``text`` as the note prints it, + and ``active``; +- ``notes``, ``backend``, ``bound``, ``trusts``, ``vacuous``, ``partial``, + ``instantiated_at``, and the other qualifiers of the text. + +The check's ID is source-anchored, such as +``function-identity::overflow@line:column#2``: the line and column of its +caret. The local suffix only disambiguates checks of one kind at the same +anchor, so inserting or reordering an unrelated check does not renumber later +IDs unless its source anchor moves. Z3 model completion is disabled: +undetermined values print as ```` and become JSON ``null``. Signed +and unsigned bit-vectors are decoded to source-level decimal values. + +The compiler's own diagnostics are records of status ``frontend``: a parse +error, a warning with its ``option`` (``-Wparentheses``), each with the notes +that follow it as ``related``, a message and a source each. They come before +the verifier's records of their file. Only an error in ``cpp-verify``'s own +options, which stops the run before it starts, stays text on standard error. + +Before 0.2 the schema was ``cppverify.diagnostic/1``: a record had no +``function_source``, and none for a verified function at all; a source +started at the check's caret and ended at the first column of its last token; +the message held the check's ID and every value with its SSA name and sort; +compiler errors were text; and no record ended the run. + +SARIF +~~~~~ + +``--diagnostics-format=sarif`` prints one SARIF 2.1.0 log when the run ends, +made from the same records, for GitHub code scanning and SARIF viewers. Each +record is a result, after Frama-C's mapping of property statuses: a proof +passes (``kind: pass``), a failure fails (``fail``, level ``error``), and a +result no verdict settles stays open (``open``). The result's rule is the +kind of the failing check (``postcondition``, ``overflow``, ...), or +``verified``, ``unresolved``, ``warning``, ``frontend``; its location is the +record's source and its function, its message the text with the +counterexample, and the path a code flow, each step at its line with its +text. The run's properties hold the summary. + +GitHub annotations +~~~~~~~~~~~~~~~~~~ + +``--diagnostics-format=github`` prints the text, and after each error and +warning a workflow command that GitHub Actions turns into an annotation on +the source, its file relative to the working directory (the checkout, in a +workflow). For ``summary.cpp`` above, after the error and its notes: -The local suffix only disambiguates obligations of one kind at the same anchor, -so inserting or reordering an unrelated obligation does not renumber later IDs -unless its source anchor moves. JSON records carry the kind as -``obligation.kind``. Diagnostics include inclusive source ranges and source -display names while retaining internal SSA names for unambiguous tooling. +.. code-block:: text -Counterexample values carry exact sorts such as ``bool``, ``i32``, ``u32``, -``math-i32``, ``pointer``, and ``heap``. Z3 model completion is disabled: -undetermined values print as ```` and become JSON ``null``. Signed and -unsigned bit-vectors are decoded to source-level decimal values. + ::error file=summary.cpp,line=14,col=12,endLine=14,endColumn=34,title=cpp-verify%3A postcondition::verification failed: add_two [postcondition] [backend=z3] [reason=counterexample]%0Acounterexample: x = 3, result = 5 Counterexample traces may contain guarded branch, modular-call, loop, heap-write, allocation/provenance, lifetime-end, deletion, and return events. @@ -1094,9 +1281,21 @@ counts each function once: .. code-block:: text $ cpp-verify --max-failures=0 three_bugs.cpp - three_bugs.cpp:7:3: error: verification failed: three_bugs [fn_...::division-by-zero@7:3] (counterexample: x [ssa=x_0] [type=i32] = 0, y [ssa=y_0] [type=i32] = 0) [backend=z3] [reason=counterexample] - three_bugs.cpp:8:3: error: verification failed: three_bugs [fn_...::overflow@8:3] (counterexample: q [ssa=q_1] [type=i32] = -1, x [ssa=x_0] [type=i32] = -2147483648, y [ssa=y_0] [type=i32] = -9) [backend=z3] [reason=counterexample] - three_bugs.cpp:5:23: error: verification failed: three_bugs [fn_...::postcondition@5:23] (counterexample: result [ssa=__result_1] [type=i32] = 0, q [ssa=q_1] [type=i32] = 0, r [ssa=r_1] [type=i32] = 0, x [ssa=x_0] [type=i32] = 0, y [ssa=y_0] [type=i32] = 11; trace: return.return@9:3 value=0) [backend=z3] [reason=counterexample] + three_bugs.cpp:7:3: error: verification failed: three_bugs [division-by-zero] [backend=z3] [reason=counterexample] + 7 | int q = 10 / y; + | ^ + three_bugs.cpp:7:3: note: counterexample: x = 0, y = 0 + three_bugs.cpp:8:3: error: verification failed: three_bugs [overflow] [backend=z3] [reason=counterexample] + 8 | int r = x + q; + | ^ + three_bugs.cpp:8:3: note: counterexample: x = -2147483648, y = -9, q = -1 + three_bugs.cpp:5:23: error: verification failed: three_bugs [postcondition] [backend=z3] [reason=counterexample] + 5 | cv::post(cv::result > 0) + | ~~~~~~~~~~~^~~ + three_bugs.cpp:5:23: note: counterexample: x = 0, y = 11, q = 0, r = 0, result = 0 + three_bugs.cpp:9:3: note: returns 0 + 9 | return r; + | ^ Summary: 1 failed in 0.0 s Without the option only the first line is printed. The overflow's @@ -1188,11 +1387,10 @@ paths through a call. Unreachable code itself is not flagged: a defensive good reason, and every claim the function makes is still checked on the paths that remain. -``--diagnostics-format=json`` covers verification-result diagnostics. -Command-line validation and frontend parse errors may still use text. Combining -JSON diagnostics with IR dumps intentionally creates a mixed stream. Malformed -byte sequences in source or archive display text are rendered with the Unicode -replacement character, so every emitted JSON record remains valid UTF-8. +Combining JSON or SARIF with IR dumps intentionally creates a mixed stream. +Malformed byte sequences in source or archive display text are rendered with +the Unicode replacement character, so every emitted JSON record remains valid +UTF-8. Portable obligation archives ---------------------------- @@ -1249,7 +1447,7 @@ module is ``BoundedSafe`` when every goal but some unwinding goal certifies (:doc:`../book/part-ii/ch17-backends-modular-calls`). ``--lean-fallback`` applies to source verification only. -``--obligation-out`` writes ``cppverify.obligation/3`` records only after exact +``--obligation-out`` writes ``cppverify.obligation/5`` records only after exact serialize/deserialize/validate/reserialize checks. Stable wire tags make the format independent of C++ enum ordinals. The reader rejects malformed magic, unsupported versions, truncation, invalid tags, inconsistent feature @@ -1257,8 +1455,14 @@ declarations, duplicate identities, and oversized/deep expressions. Schema v2 adds the precise obligation kinds; schema v3 each spec function's termination measures, read like its definition, and, with source metadata, where each assumption of a goal comes from, which names a hypothesis of a -readable Lean goal. Records of schema v2, and of schema v1, which name only -assertions, postconditions, and unwinding checks, are still read. +readable Lean goal; schema v4, with source metadata, where each variable +version of a counterexample is defined, so a replayed counterexample lists the +values its path takes; schema v5 where each source range begins, its end +being the column after its last character. Records of schemas v4 to v2, and +of schema v1, which name only assertions, postconditions, and unwinding +checks, are still read; a counterexample replayed from one before v4 lists +the values of every version, and a range from one before v5 begins at its +anchor and ends at the first column of its last token. Records cap integer widths at 4096 bits, expression depth at 4096, and collections plus expression nodes/edges at 100,000 per record. It also rejects embedded NULs, non-canonical numerals, inactive payload fields, ill-scoped @@ -1341,6 +1545,37 @@ contract syntax, **and** contract semantics (the ``cppverify::old``/``cppverify: bool-convertibility rules), but does **not** check your logic. Ideal for editors, CI pre-flight, and LLM/agent loops. +Results in a compilation +~~~~~~~~~~~~~~~~~~~~~~~~ + +``clang++ -fverify-contracts`` reports what it verifies as its own +diagnostics, in the format ``cpp-verify`` prints: a failure is an error with +its notes, an unresolved result is an error, a warning is a warning in +``-Wcppverify`` (``-Wno-cppverify`` hides it and ``-Werror=cppverify`` makes it +an error), and a proof is a remark at its function, shown with +``-Rcppverify``. Colors, +``-fdiagnostics-format=sarif``, and editors that read the compiler's +diagnostics show them as they show Clang's: + +.. code-block:: text + + $ clang++ -std=c++17 -fverify-contracts -Rcppverify -c summary.cpp -o summary.o + summary.cpp:5:5: remark: Verified: add_one [backend=z3] [-Rcppverify] + 5 | int add_one(int x) + | ^ + summary.cpp:14:23: error: verification failed: add_two [postcondition] [backend=z3] [reason=counterexample] + 14 | cv::post(cv::result == twice(x)) + | ~~~~~~~~~~~^~~~~~~~~~~ + summary.cpp:14:23: note: counterexample: x = 3, result = 5 + summary.cpp:16:3: note: returns 5 + 16 | return x + 2; + | ^ + 1 error generated. + +Before 0.2 the compiler printed the verifier's lines as text, and then the +one error ``cppverify: verification failed``: ``-fdiagnostics-format=sarif`` +gave a document without results, and ``-w`` and ``-Werror`` changed nothing. + Quick lookup ~~~~~~~~~~~~ diff --git a/website/source/release-notes.rst b/website/source/release-notes.rst index 3048bd501f..6217abc06b 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -135,6 +135,16 @@ correct. after it could fail for values it never produces. Its state now carries over (:doc:`language/functions-loops`). +**Class invariants in loops.** A loop that called member functions of an +object, outside its class, forgot the object's class invariant: at the loop +the verifier knew only what the loop's invariants said, so the next call +failed its ``[type-invariant]`` check, and an invariant over private fields +could not be restated there. A loop now keeps the class invariant of every +object it reaches, the local objects alive at it and those its function's +reference and pointer parameters address, checked at entry and kept by +every iteration like the user's own; a loop in a member function of the +class keeps none of its class's (:doc:`language/structs`). + **Loops that initialize local storage.** Every iteration of a loop after the first saw the local storage the earlier ones wrote as uninitialized, so a loop filling a local array failed its ``initialization`` check, at the @@ -357,6 +367,27 @@ that nothing instantiates is reported. ``cv::post(cv::result > 0)`` and ``cv::ol of a template's type did not compile (``invalid operands to binary expression ('T' and 'int')``) (:doc:`language/functions-loops`). +**The address of a local in a contract.** ``size(&r)`` in a loop invariant +or a ``cv::check``, for a local object ``r``, was an error (``raw address-of +is unsupported until lexical lifetime and escape effects are modeled``). +A contract may now take the address of a local object in memory, since a +spec it is passed to cannot keep it; in ghost code, which could, it is +still an error (:doc:`language/structs`). + +**Hiding a spec template's specialization.** ``cv::hide(at<4>)`` and +``cv::reveal_with_fuel(steps<2>, 4)`` stopped the function (``unsupported C++ +type in verification: ``). They now name that +specialization, and a template's name alone is an error, as in C++ +(``reference to overloaded function could not be resolved``) +(:doc:`language/ghost-proofs`). + +**A ring buffer case study.** A class template ``Ring`` with private +fields and a class invariant, specified as a FIFO queue through spec views, +with clients that use a range-based ``for``, a ``switch``, and an object +passed by reference: six functions verify in about 2 s, and the classic +wraparound bug fails with the store that breaks the invariant +(:doc:`case-studies/ring-buffer`). + **std::min and std::max.** In a contract they ended ``Unresolved: obligation lowering failed: f (missing spec definition: fn_...)``, as did any function using a ``constexpr`` helper that could not be lifted. Of two @@ -483,6 +514,60 @@ are errors at the statement (:doc:`language/functions-loops`). Running cpp-verify ~~~~~~~~~~~~~~~~~~ +**Results as records, SARIF, and annotations.** JSON records had no source +for a verified function, a failure's range began at its caret and ended at +the first column of its last token, compiler errors stayed text, and nothing +ended the run; there was no SARIF. JSON records are now +``cppverify.diagnostic/2``: every one names its function and where it is +declared, a failure spans its whole expression to the column after its last +character and gives its counterexample and path as the text prints them, the +compiler's own errors and warnings are records with their notes, and a +``cppverify.summary/1`` record ends the run. ``--diagnostics-format=sarif`` +writes one SARIF 2.1.0 log for code scanning, each failure with its path as +a code flow, and ``--diagnostics-format=github`` annotates errors and +warnings in a GitHub workflow (:doc:`language/tooling`). + +**Failures read like compiler errors.** A failure was one line that named its +obligation by an internal identity and listed every SSA version of every +variable with its type (``verification failed: abs +[fn_5f5a3361627369::overflow@5:3] (counterexample: x [ssa=x_0] [type=i32] = +-2147483648; trace: return.return@5:3 value=-2147483648)``). It is now a Clang +error at the failing source, with the line and a caret under the check, the +kind of check in brackets, the counterexample as a note in source names (each +variable once with the values it takes along the path joined by ``->``, a +hidden local told apart by its line, a reference as ``&r``), and each step of +the path as a note at its line, in the static analyzer's words (``takes the +true branch``, ``calls f(3)``, ``returns -2147483648``); a long path shows its +first and last five steps. The values no longer include those of branches the +path does not take, which the old list gave as if they were values of the +execution, and a check that fails at a store, a ``delete``, or a ``return`` no +longer lists that operation as a step taken (in JSON as well). A ``clang++ -fverify-contracts`` compilation reports each result as a +diagnostic of its own instead of printing the verifier's text and then one +``verification failed`` error: a failure or an unresolved result is an error, +a warning belongs to ``-Wcppverify`` (so ``-Wno-cppverify`` and +``-Werror=cppverify`` work), a proof is a remark shown with ``-Rcppverify``, +and ``-fdiagnostics-format=sarif`` carries them all. In JSON Lines a record's +``message`` is the new error text (:doc:`language/tooling`). + +**cvc5's counterexamples.** A failure cvc5 found (``--backend=cvc5``, or a race +cvc5 won) gave no values and no path: the certifier checked its model and the +verifier then dropped it, so the JSON record had no ``model`` or ``trace``, +and under ``--lean-fallback`` a race cvc5 won wrote no Lean refutation (``the +solver's model gives the goal's variables no values``). It now shows the +counterexample and the steps of its path exactly as a failure Z3 finds does, +in text, JSON, SARIF, GitHub annotations, and archive replay, evaluated by the +certifier in the checked model; the race's refutation states its values +(:doc:`book/part-ii/ch17-backends-modular-calls`). + +**Queries as SMT-LIB scripts.** The only view of a query was +``--dump-ir=4``, Z3 terms without declarations or a check, which no solver +reads. ``--dump-smt2=DIR`` writes every query cpp-verify sends to Z3 or cvc5 +as a script that ``z3 FILE`` or ``cvc5 FILE`` runs on its own: the options +cpp-verify gave the solver, as commands, the query, its check, and the +answer cpp-verify got, in a file named by the function and the obligation, +for solver bug reports and for comparing solver versions +(:doc:`language/tooling`). + **Counterexamples after a loop.** A failure whose counterexample passed through a loop looked like any other, though its state there came from the loop's invariants and no execution need reach it: the first loop most users @@ -788,9 +873,11 @@ reproduce, or when two backends decide differently Data formats ~~~~~~~~~~~~ -Archives are ``cppverify.obligation/3``, which adds measures and the -sources of assumptions and reads ``/2`` and ``/1``; JSON diagnostics stay -``cppverify.diagnostic/1``. A module whose specs' measures apply other specs +Archives are ``cppverify.obligation/5``, which adds measures, the sources +of assumptions, where each variable version is defined, and where each +source range begins, and reads ``/4`` to ``/1``; JSON diagnostics are +``cppverify.diagnostic/2``, ending with a ``cppverify.summary/1`` record, and +``--diagnostics-format=sarif`` writes SARIF 2.1.0. A module whose specs' measures apply other specs keeps them, so its module hash changes; per-obligation hashes, and the proof cache keyed by them, do not. JSON records may carry the status ``proved-obligations`` (archive replay) and the reasons ``interrupted``