[FileCheck] Add a literal check directive modifier

Introduce CHECK modifiers that change the behavior of the CHECK
directive. Also add a LITERAL modifier for cases where matching could
end requiring escaping strings interpreted as regex where only
literal/fixed string matching is desired (making the CHECK's more
difficult to write/fragile and difficult to interpret).
This commit is contained in:
Jacques Pienaar 2020-12-18 17:26:15 -08:00
parent 2b62e62328
commit 44f399ccc1
4 changed files with 193 additions and 38 deletions

View File

@ -660,6 +660,30 @@ simply uniquely match a single line in the file being verified.
``CHECK-LABEL:`` directives cannot contain variable definitions or uses.
Directive modifiers
~~~~~~~~~~~~~~~~~~~
A directive modifier can be append to a directive by following the directive
with ``{<modifier>}`` where the only supported value for ``<modifier>`` is
``LITERAL``.
The ``LITERAL`` directive modifier can be used to perform a literal match. The
modifier results in the directive not recognizing any syntax to perform regex
matching, variable capture or any substitutions. This is useful when the text
to match would require excessive escaping otherwise. For example, the
following will perform literal matches rather than considering these as
regular expressions:
.. code-block:: text
Input: [[[10, 20]], [[30, 40]]]
Output %r10: [[10, 20]]
Output %r10: [[30, 40]]
; CHECK{LITERAL}: [[[10, 20]], [[30, 40]]]
; CHECK-DAG{LITERAL}: [[30, 40]]
; CHECK-DAG{LITERAL}: [[10, 20]]
FileCheck Regex Matching Syntax
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -17,6 +17,7 @@
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/SourceMgr.h"
#include <bitset>
#include <string>
#include <vector>
@ -64,12 +65,23 @@ enum FileCheckKind {
CheckBadCount
};
enum FileCheckKindModifier {
/// Modifies directive to perform literal match.
ModifierLiteral = 0,
// The number of modifier.
Size
};
class FileCheckType {
FileCheckKind Kind;
int Count; ///< optional Count for some checks
/// Modifers for the check directive.
std::bitset<FileCheckKindModifier::Size> Modifiers;
public:
FileCheckType(FileCheckKind Kind = CheckNone) : Kind(Kind), Count(1) {}
FileCheckType(FileCheckKind Kind = CheckNone)
: Kind(Kind), Count(1), Modifiers() {}
FileCheckType(const FileCheckType &) = default;
FileCheckType &operator=(const FileCheckType &) = default;
@ -78,8 +90,19 @@ public:
int getCount() const { return Count; }
FileCheckType &setCount(int C);
bool isLiteralMatch() const {
return Modifiers[FileCheckKindModifier::ModifierLiteral];
}
FileCheckType &setLiteralMatch(bool Literal = true) {
Modifiers.set(FileCheckKindModifier::ModifierLiteral, Literal);
return *this;
}
// \returns a description of \p Prefix.
std::string getDescription(StringRef Prefix) const;
// \returns a description of \p Modifiers.
std::string getModifiersDescription() const;
};
} // namespace Check

View File

@ -917,6 +917,12 @@ bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix,
return false;
}
// If literal check, set fixed string.
if (CheckTy.isLiteralMatch()) {
FixedStr = PatternStr;
return false;
}
// Check to see if this is a fixed string, or if it has regex pieces.
if (!MatchFullLinesHere &&
(PatternStr.size() < 2 || (PatternStr.find("{{") == StringRef::npos &&
@ -1588,26 +1594,43 @@ Check::FileCheckType &Check::FileCheckType::setCount(int C) {
return *this;
}
std::string Check::FileCheckType::getModifiersDescription() const {
if (Modifiers.none())
return "";
std::string Ret;
raw_string_ostream OS(Ret);
OS << '{';
if (isLiteralMatch())
OS << "LITERAL";
OS << '}';
return OS.str();
}
std::string Check::FileCheckType::getDescription(StringRef Prefix) const {
// Append directive modifiers.
auto WithModifiers = [this, Prefix](StringRef Str) -> std::string {
return (Prefix + Str + getModifiersDescription()).str();
};
switch (Kind) {
case Check::CheckNone:
return "invalid";
case Check::CheckPlain:
if (Count > 1)
return Prefix.str() + "-COUNT";
return std::string(Prefix);
return WithModifiers("-COUNT");
return WithModifiers("");
case Check::CheckNext:
return Prefix.str() + "-NEXT";
return WithModifiers("-NEXT");
case Check::CheckSame:
return Prefix.str() + "-SAME";
return WithModifiers("-SAME");
case Check::CheckNot:
return Prefix.str() + "-NOT";
return WithModifiers("-NOT");
case Check::CheckDAG:
return Prefix.str() + "-DAG";
return WithModifiers("-DAG");
case Check::CheckLabel:
return Prefix.str() + "-LABEL";
return WithModifiers("-LABEL");
case Check::CheckEmpty:
return Prefix.str() + "-EMPTY";
return WithModifiers("-EMPTY");
case Check::CheckComment:
return std::string(Prefix);
case Check::CheckEOF:
@ -1625,23 +1648,45 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
if (Buffer.size() <= Prefix.size())
return {Check::CheckNone, StringRef()};
char NextChar = Buffer[Prefix.size()];
StringRef Rest = Buffer.drop_front(Prefix.size() + 1);
StringRef Rest = Buffer.drop_front(Prefix.size());
// Check for comment.
if (llvm::is_contained(Req.CommentPrefixes, Prefix)) {
if (NextChar == ':')
if (Rest.consume_front(":"))
return {Check::CheckComment, Rest};
// Ignore a comment prefix if it has a suffix like "-NOT".
return {Check::CheckNone, StringRef()};
}
// Verify that the : is present after the prefix.
if (NextChar == ':')
return {Check::CheckPlain, Rest};
auto ConsumeModifiers = [&](Check::FileCheckType Ret)
-> std::pair<Check::FileCheckType, StringRef> {
if (Rest.consume_front(":"))
return {Ret, Rest};
if (!Rest.consume_front("{"))
return {Check::CheckNone, StringRef()};
if (NextChar != '-')
// Parse the modifiers, speparated by commas.
do {
// Allow whitespace in modifiers list.
Rest = Rest.ltrim();
if (Rest.consume_front("LITERAL"))
Ret.setLiteralMatch();
else
return {Check::CheckNone, Rest};
// Allow whitespace in modifiers list.
Rest = Rest.ltrim();
} while (Rest.consume_front(","));
if (!Rest.consume_front("}:"))
return {Check::CheckNone, Rest};
return {Ret, Rest};
};
// Verify that the prefix is followed by directive modifiers or a colon.
if (Rest.consume_front(":"))
return {Check::CheckPlain, Rest};
if (Rest.front() == '{')
return ConsumeModifiers(Check::CheckPlain);
if (!Rest.consume_front("-"))
return {Check::CheckNone, StringRef()};
if (Rest.consume_front("COUNT-")) {
@ -1651,29 +1696,12 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
return {Check::CheckBadCount, Rest};
if (Count <= 0 || Count > INT32_MAX)
return {Check::CheckBadCount, Rest};
if (!Rest.consume_front(":"))
if (Rest.front() != ':' && Rest.front() != '{')
return {Check::CheckBadCount, Rest};
return {Check::FileCheckType(Check::CheckPlain).setCount(Count), Rest};
return ConsumeModifiers(
Check::FileCheckType(Check::CheckPlain).setCount(Count));
}
if (Rest.consume_front("NEXT:"))
return {Check::CheckNext, Rest};
if (Rest.consume_front("SAME:"))
return {Check::CheckSame, Rest};
if (Rest.consume_front("NOT:"))
return {Check::CheckNot, Rest};
if (Rest.consume_front("DAG:"))
return {Check::CheckDAG, Rest};
if (Rest.consume_front("LABEL:"))
return {Check::CheckLabel, Rest};
if (Rest.consume_front("EMPTY:"))
return {Check::CheckEmpty, Rest};
// You can't combine -NOT with another suffix.
if (Rest.startswith("DAG-NOT:") || Rest.startswith("NOT-DAG:") ||
Rest.startswith("NEXT-NOT:") || Rest.startswith("NOT-NEXT:") ||
@ -1681,6 +1709,24 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
Rest.startswith("EMPTY-NOT:") || Rest.startswith("NOT-EMPTY:"))
return {Check::CheckBadNot, Rest};
if (Rest.consume_front("NEXT"))
return ConsumeModifiers(Check::CheckNext);
if (Rest.consume_front("SAME"))
return ConsumeModifiers(Check::CheckSame);
if (Rest.consume_front("NOT"))
return ConsumeModifiers(Check::CheckNot);
if (Rest.consume_front("DAG"))
return ConsumeModifiers(Check::CheckDAG);
if (Rest.consume_front("LABEL"))
return ConsumeModifiers(Check::CheckLabel);
if (Rest.consume_front("EMPTY"))
return ConsumeModifiers(Check::CheckEmpty);
return {Check::CheckNone, Rest};
}

View File

@ -0,0 +1,62 @@
; RUN: FileCheck -check-prefix=A -input-file %s %s
;; This tests the LITERAL directive modifier.
The result is "5371, 5372, 5373, 5374"
The result is "[[[5371]], [[5372]], [[5373]], [[5374]]]"
[[[5375]], [[5376]],
[[[5377]], [[5378]],
{{there you go.*}}
[[10]]
[[20]]
[[50]]
;; These should all not match.
; A{}: 5371, 5372,
; A{LITERAL} 5371, 5372,
; A{LITERAL 5371, 5372,
; A{LITERAL,} 5371, 5372,
; A{, LITERAL} 5371, 5372,
; A: 5371, 5372,
; A-SAME: 5373, 5374
; A{LITERAL}: [[[5371]], [[5372]],
; A-SAME{LITERAL}: [[5373]], [[5374]]]
;; Modifier list allows whitespace.
; A{ LITERAL }: [[[5375]], [[5376]],
;; Modifiers are combined into a set and repetition is allowed.
; A{LITERAL , LITERAL}: [[[5377]], [[5378]],
; A-NEXT{LITERAL}: {{there you go.*}}
; A-NOT{LITERAL}: [[50]]
; A-DAG{LITERAL}: [[20]]
; A-DAG{LITERAL}: [[10]]
; A{LITERAL}: [[50]]
; RUN: %ProtectFileCheckOutput \
; RUN: not FileCheck %s --input-file %s --check-prefix=INVALID 2>&1 | \
; RUN: FileCheck %s --check-prefix=CHECK-INVALID
;; Ensure invalid modifier skipped.
; INVALID{BADMODIFIER}: 6371, 6372,
; CHECK-INVALID: no check strings found with prefix 'INVALID
; RUN: %ProtectFileCheckOutput \
; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERRNOT 2>&1 | \
; RUN: FileCheck %s --check-prefix=ERRNOT
;; This ensures a failure is correctly reported when a NOT directive with a
;; LITERAL modifier matches.
[[a]]
[[b]]
[[c]]
; CHECK-ERRNOT{LITERAL}: [[a]]
; CHECK-ERRNOT-NOT{LITERAL}: [[b]]
; CHECK-ERRNOT{LITERAL}: [[c]]
; ERRNOT: no match expected