From 9f86b8ec41f04fa6f087264008317515a612a922 Mon Sep 17 00:00:00 2001 From: "Joel E. Denny" Date: Tue, 28 Jul 2020 18:09:47 -0400 Subject: [PATCH] [FileCheck] Report captured variables Report captured variables in input dumps and traces. For example: ``` $ cat check CHECK: hello [[WHAT:[a-z]+]] CHECK: goodbye [[WHAT]] $ FileCheck -dump-input=always -vv check < input |& tail -8 <<<<<< 1: hello world check:1'0 ^~~~~~~~~~~ check:1'1 ^~~~~ captured var "WHAT" 2: goodbye world check:2'0 ^~~~~~~~~~~~~ check:2'1 with "WHAT" equal to "world" >>>>>> $ FileCheck -dump-input=never -vv check < input check2:1:8: remark: CHECK: expected string found in input CHECK: hello [[WHAT:[a-z]+]] ^ :1:1: note: found here hello world ^~~~~~~~~~~ :1:7: note: captured var "WHAT" hello world ^~~~~ check2:2:8: remark: CHECK: expected string found in input CHECK: goodbye [[WHAT]] ^ :2:1: note: found here goodbye world ^~~~~~~~~~~~~ :2:1: note: with "WHAT" equal to "world" goodbye world ^ ``` Reviewed By: thopre Differential Revision: https://reviews.llvm.org/D83651 --- llvm/lib/Support/FileCheck.cpp | 58 +++++++++++++++++++++++++- llvm/lib/Support/FileCheckImpl.h | 28 +++++++++++-- llvm/test/FileCheck/dump-input-annotations.txt | 58 ++++++++++++++++++++++++-- llvm/test/FileCheck/verbose.txt | 38 ++++++++++++++--- llvm/unittests/Support/FileCheckTest.cpp | 46 +++++++++++++++++++- 5 files changed, 213 insertions(+), 15 deletions(-) diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp index fd426a1..29a5a13 100644 --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -1218,7 +1218,7 @@ Expected Pattern::match(StringRef Buffer, size_t &MatchLen, Format.valueFromStringRepr(MatchedValue, SM); if (!Value) return Value.takeError(); - DefinedNumericVariable->setValue(*Value); + DefinedNumericVariable->setValue(*Value, MatchedValue); } // Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after @@ -1295,6 +1295,57 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, } } +void Pattern::printVariableDefs(const SourceMgr &SM, + FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const { + if (VariableDefs.empty() && NumericVariableDefs.empty()) + return; + // Build list of variable captures. + struct VarCapture { + StringRef Name; + SMRange Range; + }; + SmallVector VarCaptures; + for (const auto &VariableDef : VariableDefs) { + VarCapture VC; + VC.Name = VariableDef.first; + StringRef Value = Context->GlobalVariableTable[VC.Name]; + SMLoc Start = SMLoc::getFromPointer(Value.data()); + SMLoc End = SMLoc::getFromPointer(Value.data() + Value.size()); + VC.Range = SMRange(Start, End); + VarCaptures.push_back(VC); + } + for (const auto &VariableDef : NumericVariableDefs) { + VarCapture VC; + VC.Name = VariableDef.getKey(); + StringRef StrValue = VariableDef.getValue() + .DefinedNumericVariable->getStringValue() + .getValue(); + SMLoc Start = SMLoc::getFromPointer(StrValue.data()); + SMLoc End = SMLoc::getFromPointer(StrValue.data() + StrValue.size()); + VC.Range = SMRange(Start, End); + VarCaptures.push_back(VC); + } + // Sort variable captures by the order in which they matched the input. + // Ranges shouldn't be overlapping, so we can just compare the start. + std::sort(VarCaptures.begin(), VarCaptures.end(), + [](const VarCapture &A, const VarCapture &B) { + assert(A.Range.Start != B.Range.Start && + "unexpected overlapping variable captures"); + return A.Range.Start.getPointer() < B.Range.Start.getPointer(); + }); + // Create notes for the sorted captures. + for (const VarCapture &VC : VarCaptures) { + SmallString<256> Msg; + raw_svector_ostream OS(Msg); + OS << "captured var \"" << VC.Name << "\""; + if (Diags) + Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, VC.Range, OS.str()); + else + SM.PrintMessage(VC.Range.Start, SourceMgr::DK_Note, OS.str(), VC.Range); + } +} + static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy, const SourceMgr &SM, SMLoc Loc, Check::FileCheckType CheckTy, @@ -1876,8 +1927,10 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, : FileCheckDiag::MatchFoundButExcluded; SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags); - if (Diags) + if (Diags) { Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags); + Pat.printVariableDefs(SM, MatchTy, Diags); + } if (!PrintDiag) return; @@ -1893,6 +1946,7 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr); + Pat.printVariableDefs(SM, MatchTy, nullptr); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h index d3f4384..edb0c25 100644 --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -261,6 +261,10 @@ private: /// Value of numeric variable, if defined, or None otherwise. Optional Value; + /// The input buffer's string from which Value was parsed, or None. See + /// comments on getStringValue for a discussion of the None case. + Optional StrValue; + /// Line number where this variable is defined, or None if defined before /// input is parsed. Used to determine whether a variable is defined on the /// same line as a given use. @@ -284,12 +288,28 @@ public: /// \returns this variable's value. Optional getValue() const { return Value; } - /// Sets value of this numeric variable to \p NewValue. - void setValue(ExpressionValue NewValue) { Value = NewValue; } + /// \returns the input buffer's string from which this variable's value was + /// parsed, or None if the value is not yet defined or was not parsed from the + /// input buffer. For example, the value of @LINE is not parsed from the + /// input buffer, and some numeric variables are parsed from the command + /// line instead. + Optional getStringValue() const { return StrValue; } + + /// Sets value of this numeric variable to \p NewValue, and sets the input + /// buffer string from which it was parsed to \p NewStrValue. See comments on + /// getStringValue for a discussion of when the latter can be None. + void setValue(ExpressionValue NewValue, + Optional NewStrValue = None) { + Value = NewValue; + StrValue = NewStrValue; + } /// Clears value of this numeric variable, regardless of whether it is /// currently defined or not. - void clearValue() { Value = None; } + void clearValue() { + Value = None; + StrValue = None; + } /// \returns the line number where this variable is defined, if any, or None /// if defined before input is parsed. @@ -691,6 +711,8 @@ public: bool hasVariable() const { return !(Substitutions.empty() && VariableDefs.empty()); } + void printVariableDefs(const SourceMgr &SM, FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const; Check::FileCheckType getCheckTy() const { return CheckTy; } diff --git a/llvm/test/FileCheck/dump-input-annotations.txt b/llvm/test/FileCheck/dump-input-annotations.txt index fe39f39..80d9b1d 100644 --- a/llvm/test/FileCheck/dump-input-annotations.txt +++ b/llvm/test/FileCheck/dump-input-annotations.txt @@ -668,12 +668,64 @@ ; SUBST-NEG-NEXT:>>>>>> ;-------------------------------------------------- -; Substitutions: CHECK-NEXT, CHECK-SAME, CHECK-DAG fixups. +; Captured variables +;-------------------------------------------------- + +; RUN: echo 'strvar: foo' > %t.in +; RUN: echo 'numvar no expr: 51' >> %t.in +; RUN: echo 'numvar expr: -49' >> %t.in +; RUN: echo 'many: foo 100 8 bar' >> %t.in +; RUN: echo 'var in neg match: foo' >> %t.in +; RUN: echo 'END' >> %t.in + +; RUN: echo 'CHECK: strvar: [[STRVAR:[a-z]+]]' > %t.chk +; RUN: echo 'CHECK: numvar no expr: [[#NUMVAR_NO_EXPR:]]' >> %t.chk +; RUN: echo 'CHECK: numvar expr: [[#%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR]]' >> %t.chk + +; Capture many variables of different kinds in a different order than their +; names sort alphabetically to ensure they're sorted in capture order. +; RUN: echo 'CHECK: many: [[VAR1:foo]] [[#%d,VAR3:]] [[#VAR2:]] [[VAR4:bar]]' \ +; RUN: >> %t.chk + +; RUN: echo 'CHECK-NOT: var in neg match: [[VAR:foo]]' >> %t.chk +; RUN: echo 'CHECK: END' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: | FileCheck -strict-whitespace -match-full-lines %s -check-prefix=CAPTURE-NEG + +; CAPTURE-NEG:<<<<<< +; CAPTURE-NEG-NEXT: 1: strvar: foo +; CAPTURE-NEG-NEXT:check:1'0 ^~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:1'1 ^~~ captured var "STRVAR" +; CAPTURE-NEG-NEXT: 2: numvar no expr: 51 +; CAPTURE-NEG-NEXT:check:2'0 ^~~~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:2'1 ^~ captured var "NUMVAR_NO_EXPR" +; CAPTURE-NEG-NEXT: 3: numvar expr: -49 +; CAPTURE-NEG-NEXT:check:3'0 ^~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:3'1 with "%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR" equal to "-49" +; CAPTURE-NEG-NEXT:check:3'2 ^~~ captured var "NUMVAR_EXPR" +; CAPTURE-NEG-NEXT: 4: many: foo 100 8 bar +; CAPTURE-NEG-NEXT:check:4'0 ^~~~~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:4'1 ^~~ captured var "VAR1" +; CAPTURE-NEG-NEXT:check:4'2 ^~~ captured var "VAR3" +; CAPTURE-NEG-NEXT:check:4'3 ^ captured var "VAR2" +; CAPTURE-NEG-NEXT:check:4'4 ^~~ captured var "VAR4" +; CAPTURE-NEG-NEXT: 5: var in neg match: foo +; CAPTURE-NEG-NEXT:not:5'0 !~~~~~~~~~~~~~~~~~~~~ error: no match expected +; CAPTURE-NEG-NEXT:not:5'1 !~~ captured var "VAR" +; CAPTURE-NEG-NEXT: 6: END +; CAPTURE-NEG-NEXT:check:6 ^~~ +; CAPTURE-NEG-NEXT:>>>>>> + +;-------------------------------------------------- +; CHECK-NEXT, CHECK-SAME, CHECK-DAG note fixups. ; ; When CHECK-NEXT or CHECK-SAME fails for the wrong line, or when a CHECK-DAG ; match is discarded, the associated diagnostic type must be converted from -; successful to failed or discarded. However, any substitution diagnostics must -; be traversed to find that diagnostic. +; successful to failed or discarded. However, any note annotation must be +; traversed to find that diagnostic. We check this behavior here only for +; substitutions, but it's the same mechanism for all note annotations. ;-------------------------------------------------- ;- - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/llvm/test/FileCheck/verbose.txt b/llvm/test/FileCheck/verbose.txt index 2098ddc..6baf07e 100644 --- a/llvm/test/FileCheck/verbose.txt +++ b/llvm/test/FileCheck/verbose.txt @@ -55,6 +55,31 @@ VV-NEXT: verbose.txt:[[@LINE-22]]:1: note: scanning from here VV-NEXT: {{^}}bar{{$}} VV-NEXT: {{^}}^{{$}} +STRVAR=foobar +STRVAR:foobar +CHECK: STRVAR=[[STRVAR:[a-z]+]] +CHECK-NEXT: STRVAR:[[STRVAR]] + + V: verbose.txt:[[#@LINE-3]]:8: remark: {{C}}HECK: expected string found in input +V-NEXT: {{C}}HECK: {{STRVAR=\[\[STRVAR:\[a-z\]\+\]\]}} +V-NEXT: {{^}} ^{{$}} +V-NEXT: verbose.txt:[[#@LINE-8]]:1: note: found here +V-NEXT: {{^}}STRVAR=foobar{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-11]]:8: note: captured var "STRVAR" +V-NEXT: {{^}}STRVAR=foobar{{$}} +V-NEXT: {{^}} ^~~~~~{{$}} + +V-NEXT: verbose.txt:[[#@LINE-12]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: {{C}}HECK-NEXT: {{STRVAR:\[\[STRVAR\]\]}} +V-NEXT: {{^}} ^{{$}} +V-NEXT: verbose.txt:[[#@LINE-17]]:1: note: found here +V-NEXT: {{^}}STRVAR:foobar{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-20]]:1: note: with "STRVAR" equal to "foobar" +V-NEXT: {{^}}STRVAR:foobar{{$}} +V-NEXT: {{^}}^{{$}} + NUMVAR=42 NUMVAR - 1:41 CHECK: NUMVAR=[[#NUMVAR:]] @@ -67,21 +92,24 @@ V-NEXT: {{^}} ^{{$}} V-NEXT: verbose.txt:[[#@LINE-9]]:1: note: found here V-NEXT: {{^}}NUMVAR=42{{$}} V-NEXT: {{^}}^~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-12]]:8: note: captured var "NUMVAR" +V-NEXT: NUMVAR=42 +V-NEXT: ^~ -V-NEXT: verbose.txt:[[#@LINE-9]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: verbose.txt:[[#@LINE-12]]:13: remark: {{C}}HECK-NEXT: expected string found in input V-NEXT: {{C}}HECK-NEXT: {{NUMVAR - 1:[[][[]#NUMVAR - 1[]][]]$}} V-NEXT: {{^}} ^{{$}} -V-NEXT: verbose.txt:[[#@LINE-15]]:1: note: found here +V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: found here V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: with "NUMVAR - 1" equal to "41" +V-NEXT: verbose.txt:[[#@LINE-21]]:1: note: with "NUMVAR - 1" equal to "41" V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^{{$}} -VV-NEXT: verbose.txt:[[#@LINE-20]]:12: remark: {{C}}HECK-NOT: excluded string not found in input +VV-NEXT: verbose.txt:[[#@LINE-23]]:12: remark: {{C}}HECK-NOT: excluded string not found in input VV-NEXT: {{C}}HECK-NOT: {{[[][[]#NUMVAR [+] 1[]][]]$}} VV-NEXT: {{^}} ^{{$}} -VV-NEXT: verbose.txt:[[#@LINE-25]]:1: note: scanning from here +VV-NEXT: verbose.txt:[[#@LINE-28]]:1: note: scanning from here VV-NEXT: {{^}}NUMVAR - 1:41{{$}} VV-NEXT: {{^}}^{{$}} diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp index 8cf8234..9f28684 100644 --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -655,20 +655,36 @@ TEST_F(FileCheckTest, NumericVariable) { Expected EvalResult = FooVarUse.eval(); expectUndefErrors({"FOO"}, EvalResult.takeError()); + // Defined variable without string: only getValue and eval return value set. FooVar.setValue(ExpressionValue(42u)); - - // Defined variable: getValue and eval return value set. Optional Value = FooVar.getValue(); ASSERT_TRUE(Value); EXPECT_EQ(42, cantFail(Value->getSignedValue())); + EXPECT_FALSE(FooVar.getStringValue()); EvalResult = FooVarUse.eval(); ASSERT_THAT_EXPECTED(EvalResult, Succeeded()); EXPECT_EQ(42, cantFail(EvalResult->getSignedValue())); + // Defined variable with string: getValue, eval, and getStringValue return + // value set. + StringRef StringValue = "925"; + FooVar.setValue(ExpressionValue(925u), StringValue); + Value = FooVar.getValue(); + ASSERT_TRUE(Value); + EXPECT_EQ(925, cantFail(Value->getSignedValue())); + // getStringValue should return the same memory not just the same characters. + EXPECT_EQ(StringValue.begin(), FooVar.getStringValue().getValue().begin()); + EXPECT_EQ(StringValue.end(), FooVar.getStringValue().getValue().end()); + EvalResult = FooVarUse.eval(); + ASSERT_THAT_EXPECTED(EvalResult, Succeeded()); + EXPECT_EQ(925, cantFail(EvalResult->getSignedValue())); + EXPECT_EQ(925, cantFail(EvalResult->getSignedValue())); + // Clearing variable: getValue and eval fail. Error returned by eval holds // the name of the cleared variable. FooVar.clearValue(); EXPECT_FALSE(FooVar.getValue()); + EXPECT_FALSE(FooVar.getStringValue()); EvalResult = FooVarUse.eval(); expectUndefErrors({"FOO"}, EvalResult.takeError()); } @@ -921,6 +937,11 @@ public: size_t MatchLen; return P.match(BufferRef, MatchLen, SM); } + + void printVariableDefs(FileCheckDiag::MatchType MatchTy, + std::vector &Diags) { + P.printVariableDefs(SM, MatchTy, &Diags); + } }; TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { @@ -1617,4 +1638,25 @@ TEST_F(FileCheckTest, FileCheckContext) { ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(cantFail(ExpressionVal->getSignedValue()), 36); } + +TEST_F(FileCheckTest, CapturedVarDiags) { + PatternTester Tester; + ASSERT_FALSE(Tester.parsePattern("[[STRVAR:[a-z]+]] [[#NUMVAR:@LINE]]")); + EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded()); + std::vector Diags; + Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags); + EXPECT_EQ(Diags.size(), 2ul); + for (FileCheckDiag Diag : Diags) { + EXPECT_EQ(Diag.CheckTy, Check::CheckPlain); + EXPECT_EQ(Diag.MatchTy, FileCheckDiag::MatchFoundAndExpected); + EXPECT_EQ(Diag.InputStartLine, 1u); + EXPECT_EQ(Diag.InputEndLine, 1u); + } + EXPECT_EQ(Diags[0].InputStartCol, 1u); + EXPECT_EQ(Diags[0].InputEndCol, 7u); + EXPECT_EQ(Diags[1].InputStartCol, 8u); + EXPECT_EQ(Diags[1].InputEndCol, 9u); + EXPECT_EQ(Diags[0].Note, "captured var \"STRVAR\""); + EXPECT_EQ(Diags[1].Note, "captured var \"NUMVAR\""); +} } // namespace -- 2.7.4