Skip to content

Commit

Permalink
[FileCheck] Annotate input dump (1/7)
Browse files Browse the repository at this point in the history
Extend FileCheck to dump its input annotated with FileCheck's
diagnostics: errors, good matches if -v, and additional information if
-vv.  The goal is to make it easier to visualize FileCheck's matching
behavior when debugging.

Each patch in this series implements input annotations for a
particular category of FileCheck diagnostics.  While the first few
patches alone are somewhat useful, the annotations become much more
useful as later patches implement annotations for -v and -vv
diagnostics, which show the matching behavior leading up to the error.

This first patch implements boilerplate plus input annotations for
error diagnostics reporting that no matches were found for a
directive.  These annotations mark the search ranges of the failed
directives.  Instead of using the usual `^~~`, which is used by later
patches for good matches, these annotations use `X~~` so that this
category of errors is visually distinct.

For example:

```
$ FileCheck -dump-input=help
The following description was requested by -dump-input=help to
explain the input annotations printed by -dump-input=always and
-dump-input=fail:

  - L:     labels line number L of the input file
  - T:L    labels the match result for a pattern of type T from line L of
           the check file
  - X~~    marks search range when no match is found
  - colors error

If you are not seeing color above or in input dumps, try: -color

$ FileCheck -v -dump-input=always check1 < input1 |& sed -n '/^Input file/,$p'
Input file: <stdin>
Check file: check1

-dump-input=help describes the format of the following dump.

Full input was:
<<<<<<
        1: ; abc def
        2: ; ghI jkl
next:3     X~~~~~~~~ error: no match found
>>>>>>

$ cat check1
CHECK: abc
CHECK-SAME: def
CHECK-NEXT: ghi
CHECK-SAME: jkl

$ cat input1
; abc def
; ghI jkl
```

Some additional details related to the boilerplate:

* Enabling: The annotated input dump is enabled by `-dump-input`,
  which can also be set via the `FILECHECK_OPTS` environment variable.
  Accepted values are `help`, `always`, `fail`, or `never`.  As shown
  above, `help` describes the format of the dump.  `always` is helpful
  when you want to investigate a successful FileCheck run, perhaps for
  an unexpected pass. `-dump-input-on-failure` and
  `FILECHECK_DUMP_INPUT_ON_FAILURE` remain as a deprecated alias for
  `-dump-input=fail`.

* Diagnostics: The usual diagnostics are not suppressed in this mode
  and are printed first.  For brevity in the example above, I've
  omitted them using a sed command.  Sometimes they're perfectly
  sufficient, and then they make debugging quicker than if you were
  forced to hunt through a dump of long input looking for the error.
  If you think they'll get in the way sometimes, keep in mind that
  it's pretty easy to grep for the start of the input dump, which is
  `<<<`.

* Colored Annotations: The annotated input is colored if colors are
  enabled (enabling colors can be forced using -color).  For example,
  errors are red.  However, as in the above example, colors are not
  vital to reading the annotations.

I don't know how to test color in the output, so any hints here would
be appreciated.

Reviewed By: george.karpenkov, zturner, probinson

Differential Revision: https://reviews.llvm.org/D52999

llvm-svn: 349418
  • Loading branch information
jdenny-ornl committed Dec 18, 2018
1 parent f47c734 commit 3c5d267
Show file tree
Hide file tree
Showing 8 changed files with 785 additions and 51 deletions.
9 changes: 8 additions & 1 deletion llvm/docs/CommandGuide/FileCheck.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ and from the command line.
-verify``. With this option FileCheck will verify that input does not contain
warnings not covered by any ``CHECK:`` patterns.

.. option:: --dump-input <mode>

Dump input to stderr, adding annotations representing currently enabled
diagnostics. Do this either 'always', on 'fail', or 'never'. Specify 'help'
to explain the dump format and quit.

.. option:: --dump-input-on-failure

When the check fails, dump all of the original input.
When the check fails, dump all of the original input. This option is
deprecated in favor of `--dump-input=fail`.

.. option:: --enable-var-scope

Expand Down
32 changes: 29 additions & 3 deletions llvm/include/llvm/Support/FileCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class FileCheckType {
};
}

struct FileCheckDiag;

class FileCheckPattern {
SMLoc PatternLoc;

Expand Down Expand Up @@ -145,6 +147,28 @@ class FileCheckPattern {
size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM);
};

//===----------------------------------------------------------------------===//
/// Summary of a FileCheck diagnostic.
//===----------------------------------------------------------------------===//

struct FileCheckDiag {
/// What is the FileCheck directive for this diagnostic?
Check::FileCheckType CheckTy;
/// Where is the FileCheck directive for this diagnostic?
unsigned CheckLine, CheckCol;
/// What kind of match result does this diagnostic describe?
enum MatchType {
// TODO: More members will appear with later patches in this series.
/// Indicates no match for an expected pattern.
MatchNoneButExpected,
MatchTypeCount,
} MatchTy;
/// The search range.
unsigned InputStartLine, InputStartCol, InputEndLine, InputEndCol;
FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy,
SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange);
};

//===----------------------------------------------------------------------===//
// Check Strings.
//===----------------------------------------------------------------------===//
Expand All @@ -169,7 +193,7 @@ struct FileCheckString {

size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode,
size_t &MatchLen, StringMap<StringRef> &VariableTable,
FileCheckRequest &Req) const;
FileCheckRequest &Req, std::vector<FileCheckDiag> *Diags) const;

bool CheckNext(const SourceMgr &SM, StringRef Buffer) const;
bool CheckSame(const SourceMgr &SM, StringRef Buffer) const;
Expand All @@ -180,7 +204,8 @@ struct FileCheckString {
size_t CheckDag(const SourceMgr &SM, StringRef Buffer,
std::vector<const FileCheckPattern *> &NotStrings,
StringMap<StringRef> &VariableTable,
const FileCheckRequest &Req) const;
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) const;
};

/// FileCheck class takes the request and exposes various methods that
Expand Down Expand Up @@ -217,7 +242,8 @@ class FileCheck {
///
/// Returns false if the input fails to satisfy the checks.
bool CheckInput(SourceMgr &SM, StringRef Buffer,
ArrayRef<FileCheckString> CheckStrings);
ArrayRef<FileCheckString> CheckStrings,
std::vector<FileCheckDiag> *Diags = nullptr);
};
} // namespace llvm
#endif
84 changes: 61 additions & 23 deletions llvm/lib/Support/FileCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,21 @@ void FileCheckPattern::PrintVariableUses(const SourceMgr &SM, StringRef Buffer,
}
}

static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
const SourceMgr &SM, SMLoc Loc,
Check::FileCheckType CheckTy,
StringRef Buffer, size_t Pos, size_t Len,
std::vector<FileCheckDiag> *Diags) {
SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos);
SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len);
SMRange Range(Start, End);
// TODO: The second condition will disappear when we extend this to handle
// more match types.
if (Diags && MatchTy != FileCheckDiag::MatchTypeCount)
Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range);
return Range;
}

void FileCheckPattern::PrintFuzzyMatch(
const SourceMgr &SM, StringRef Buffer,
const StringMap<StringRef> &VariableTable) const {
Expand Down Expand Up @@ -531,6 +546,22 @@ llvm::FileCheck::CanonicalizeFile(MemoryBuffer &MB,
return StringRef(OutputBuffer.data(), OutputBuffer.size() - 1);
}

FileCheckDiag::FileCheckDiag(const SourceMgr &SM,
const Check::FileCheckType &CheckTy,
SMLoc CheckLoc, MatchType MatchTy,
SMRange InputRange)
: CheckTy(CheckTy), MatchTy(MatchTy) {
auto Start = SM.getLineAndColumn(InputRange.Start);
auto End = SM.getLineAndColumn(InputRange.End);
InputStartLine = Start.first;
InputStartCol = Start.second;
InputEndLine = End.first;
InputEndCol = End.second;
Start = SM.getLineAndColumn(CheckLoc);
CheckLine = Start.first;
CheckCol = Start.second;
}

static bool IsPartOfWord(char c) {
return (isalnum(c) || c == '-' || c == '_');
}
Expand Down Expand Up @@ -897,7 +928,8 @@ static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc,
const FileCheckPattern &Pat, int MatchedCount,
StringRef Buffer, StringMap<StringRef> &VariableTable,
bool VerboseVerbose) {
bool VerboseVerbose,
std::vector<FileCheckDiag> *Diags) {
if (!ExpectedMatch && !VerboseVerbose)
return;

Expand All @@ -915,9 +947,11 @@ static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
// Print the "scanning from here" line. If the current position is at the
// end of a line, advance to the start of the next line.
Buffer = Buffer.substr(Buffer.find_first_not_of(" \t\n\r"));

SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note,
"scanning from here");
SMRange SearchRange = ProcessMatchResult(
ExpectedMatch ? FileCheckDiag::MatchNoneButExpected
: FileCheckDiag::MatchTypeCount,
SM, Loc, Pat.getCheckTy(), Buffer, 0, Buffer.size(), Diags);
SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here");

// Allow the pattern to print additional information if desired.
Pat.PrintVariableUses(SM, Buffer, VariableTable);
Expand All @@ -928,9 +962,10 @@ static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
const FileCheckString &CheckStr, int MatchedCount,
StringRef Buffer, StringMap<StringRef> &VariableTable,
bool VerboseVerbose) {
bool VerboseVerbose,
std::vector<FileCheckDiag> *Diags) {
PrintNoMatch(ExpectedMatch, SM, CheckStr.Prefix, CheckStr.Loc, CheckStr.Pat,
MatchedCount, Buffer, VariableTable, VerboseVerbose);
MatchedCount, Buffer, VariableTable, VerboseVerbose, Diags);
}

/// Count the number of newlines in the specified range.
Expand Down Expand Up @@ -958,9 +993,10 @@ static unsigned CountNumNewlinesBetween(StringRef Range,

/// Match check string and its "not strings" and/or "dag strings".
size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
bool IsLabelScanMode, size_t &MatchLen,
StringMap<StringRef> &VariableTable,
FileCheckRequest &Req) const {
bool IsLabelScanMode, size_t &MatchLen,
StringMap<StringRef> &VariableTable,
FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) const {
size_t LastPos = 0;
std::vector<const FileCheckPattern *> NotStrings;

Expand All @@ -970,7 +1006,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
// over the block again (including the last CHECK-LABEL) in normal mode.
if (!IsLabelScanMode) {
// Match "dag strings" (with mixed "not strings" if any).
LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req);
LastPos = CheckDag(SM, Buffer, NotStrings, VariableTable, Req, Diags);
if (LastPos == StringRef::npos)
return StringRef::npos;
}
Expand All @@ -992,7 +1028,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
// report
if (MatchPos == StringRef::npos) {
PrintNoMatch(true, SM, *this, i, MatchBuffer, VariableTable,
Req.VerboseVerbose);
Req.VerboseVerbose, Diags);
return StringRef::npos;
}
PrintMatch(true, SM, *this, i, MatchBuffer, VariableTable, MatchPos,
Expand Down Expand Up @@ -1116,7 +1152,7 @@ bool FileCheckString::CheckNot(const SourceMgr &SM, StringRef Buffer,

if (Pos == StringRef::npos) {
PrintNoMatch(false, SM, Prefix, Pat->getLoc(), *Pat, 1, Buffer,
VariableTable, Req.VerboseVerbose);
VariableTable, Req.VerboseVerbose, nullptr);
continue;
}

Expand All @@ -1130,10 +1166,12 @@ bool FileCheckString::CheckNot(const SourceMgr &SM, StringRef Buffer,
}

/// Match "dag strings" and their mixed "not strings".
size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
std::vector<const FileCheckPattern *> &NotStrings,
StringMap<StringRef> &VariableTable,
const FileCheckRequest &Req) const {
size_t
FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
std::vector<const FileCheckPattern *> &NotStrings,
StringMap<StringRef> &VariableTable,
const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) const {
if (DagNotStrings.empty())
return 0;

Expand Down Expand Up @@ -1177,7 +1215,7 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
// that group of CHECK-DAGs fails immediately.
if (MatchPosBuf == StringRef::npos) {
PrintNoMatch(true, SM, Prefix, Pat.getLoc(), Pat, 1, MatchBuffer,
VariableTable, Req.VerboseVerbose);
VariableTable, Req.VerboseVerbose, Diags);
return StringRef::npos;
}
// Re-calc it as the offset relative to the start of the original string.
Expand Down Expand Up @@ -1318,7 +1356,8 @@ static void ClearLocalVars(StringMap<StringRef> &VariableTable) {
///
/// Returns false if the input fails to satisfy the checks.
bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer,
ArrayRef<FileCheckString> CheckStrings) {
ArrayRef<FileCheckString> CheckStrings,
std::vector<FileCheckDiag> *Diags) {
bool ChecksFailed = false;

/// VariableTable - This holds all the current filecheck variables.
Expand All @@ -1341,9 +1380,8 @@ bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer,

// Scan to next CHECK-LABEL match, ignoring CHECK-NOT and CHECK-DAG
size_t MatchLabelLen = 0;
size_t MatchLabelPos =
CheckLabelStr.Check(SM, Buffer, true, MatchLabelLen, VariableTable,
Req);
size_t MatchLabelPos = CheckLabelStr.Check(
SM, Buffer, true, MatchLabelLen, VariableTable, Req, Diags);
if (MatchLabelPos == StringRef::npos)
// Immediately bail of CHECK-LABEL fails, nothing else we can do.
return false;
Expand All @@ -1362,8 +1400,8 @@ bool llvm::FileCheck::CheckInput(SourceMgr &SM, StringRef Buffer,
// Check each string within the scanned region, including a second check
// of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
size_t MatchLen = 0;
size_t MatchPos =
CheckStr.Check(SM, CheckRegion, false, MatchLen, VariableTable, Req);
size_t MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen,
VariableTable, Req, Diags);

if (MatchPos == StringRef::npos) {
ChecksFailed = true;
Expand Down
Loading

0 comments on commit 3c5d267

Please sign in to comment.