diff --git a/README.md b/README.md index 7e6c81fcbb..098bf5e3b3 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ 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 ``` @@ -165,9 +166,16 @@ Z3 runs support deterministic isolated solving (`--jobs`) and positive-proof reu (`--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/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/Verify/Backend/CVC5Backend.cpp b/clang/lib/Verify/Backend/CVC5Backend.cpp index bc98d9970b..9bc516fb37 100644 --- a/clang/lib/Verify/Backend/CVC5Backend.cpp +++ b/clang/lib/Verify/Backend/CVC5Backend.cpp @@ -2897,6 +2897,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..280b570e84 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)); 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/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..4fe90ef463 100644 --- a/clang/lib/Verify/Backend/Z3Encode.cpp +++ b/clang/lib/Verify/Backend/Z3Encode.cpp @@ -3405,6 +3405,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 +3443,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 +3467,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 +3545,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 +3790,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 +3859,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/CMakeLists.txt b/clang/lib/Verify/CMakeLists.txt index 59affb4165..1856824bae 100644 --- a/clang/lib/Verify/CMakeLists.txt +++ b/clang/lib/Verify/CMakeLists.txt @@ -49,6 +49,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..69911b546e 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)); @@ -3238,6 +3246,7 @@ ASTConverter::convertFunction(const FunctionDecl *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 +4268,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 +5405,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; diff --git a/clang/lib/Verify/Frontend/ASTConverter.h b/clang/lib/Verify/Frontend/ASTConverter.h index dfc350c871..fd425c3bc5 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. 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/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/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("= 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_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..06ad3a2a0c --- /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: 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/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/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/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/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/tools/cpp-verify/Main.cpp b/clang/tools/cpp-verify/Main.cpp index 5cb8cdb83f..0196436937 100644 --- a/clang/tools/cpp-verify/Main.cpp +++ b/clang/tools/cpp-verify/Main.cpp @@ -4,8 +4,10 @@ #include "../../lib/Verify/Backend/ObligationSimplify.h" #include "../../lib/Verify/Backend/ProofCache.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 +15,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" @@ -267,13 +270,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 +416,51 @@ 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 (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 +537,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 +580,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 +595,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 +610,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 +759,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 +796,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 +813,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 +855,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 +875,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 +899,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 +919,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 +1131,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 +1182,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 +1250,24 @@ 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()) + 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 +1315,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); @@ -1143,7 +1375,7 @@ int main(int argc, const char **argv) { "source files\n"; return 1; } - return replayObligationArchive(); + return finishOutput(replayObligationArchive()); } std::unique_ptr ObligationFile; @@ -1204,27 +1436,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/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4cb50ca1a3..2f04acbb3c 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 │ └──────────────────────────┘ @@ -1124,7 +1124,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 +1148,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. @@ -2461,12 +2468,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 @@ -3091,7 +3150,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 diff --git a/docs/DESIGN.md b/docs/DESIGN.md index ea57b0106a..73344942cf 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -2281,10 +2281,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 +2330,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 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..540821a878 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 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..1a58e9099b 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,18 @@ 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 Z3 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 +26,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 +66,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 @@ -391,7 +415,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..bb6c594156 100644 --- a/website/source/book/part-ii/ch17-backends-modular-calls.rst +++ b/website/source/book/part-ii/ch17-backends-modular-calls.rst @@ -410,7 +410,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 +1093,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/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..8e81cadbd1 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. 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..30ac9f65f6 100644 --- a/website/source/language/limitations.rst +++ b/website/source/language/limitations.rst @@ -704,7 +704,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 +777,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..783564d35f 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 @@ -509,7 +519,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 +630,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 +781,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 +921,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] @@ -951,8 +1002,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..0a439ed88b 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,9 +707,12 @@ 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 @@ -1012,9 +1040,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 +1061,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 +1185,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 +1291,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 +1351,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 +1359,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 +1449,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..1875d27be2 100644 --- a/website/source/release-notes.rst +++ b/website/source/release-notes.rst @@ -483,6 +483,41 @@ 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`). + **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 +823,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``