CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1049 lines
43 KiB
C++
1049 lines
43 KiB
C++
//===- FileCheck.cpp - Check that File's Contents match what is expected --===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// FileCheck does a line-by line check of a file that validates whether it
|
|
// contains the expected content. This is useful for regression tests etc.
|
|
//
|
|
// This program exits with an exit status of 2 on error, exit status of 0 if
|
|
// the file matched the expected contents, and exit status of 1 if it did not
|
|
// contain the expected contents.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/FileCheck/FileCheck.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/InitLLVM.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Process.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include "llvm/Support/WithColor.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <cmath>
|
|
#include <map>
|
|
using namespace llvm;
|
|
|
|
static cl::extrahelp FileCheckOptsEnv(
|
|
"\nOptions are parsed from the environment variable FILECHECK_OPTS and\n"
|
|
"from the command line.\n");
|
|
|
|
static cl::opt<std::string>
|
|
CheckFilename(cl::Positional, cl::desc("<check-file>"), cl::Optional);
|
|
|
|
static cl::opt<std::string>
|
|
InputFilename("input-file", cl::desc("File to check (defaults to stdin)"),
|
|
cl::init("-"), cl::value_desc("filename"));
|
|
|
|
static cl::list<std::string>
|
|
CheckPrefixes("check-prefixes", cl::CommaSeparated,
|
|
cl::desc("Comma separated list of prefixes to use from check "
|
|
"file\n(defaults to 'CHECK')"));
|
|
static cl::alias CheckPrefixesAlias("check-prefix", cl::aliasopt(CheckPrefixes),
|
|
cl::CommaSeparated, cl::NotHidden,
|
|
cl::desc("Alias for -check-prefixes"));
|
|
|
|
static cl::list<std::string> CommentPrefixes(
|
|
"comment-prefixes", cl::CommaSeparated, cl::Hidden,
|
|
cl::desc("Comma-separated list of comment prefixes to use from check file\n"
|
|
"(defaults to 'COM,RUN'). Please avoid using this feature in\n"
|
|
"LLVM's LIT-based test suites, which should be easier to\n"
|
|
"maintain if they all follow a consistent comment style. This\n"
|
|
"feature is meant for non-LIT test suites using FileCheck."));
|
|
|
|
static cl::opt<bool> NoCanonicalizeWhiteSpace(
|
|
"strict-whitespace",
|
|
cl::desc("Do not treat all horizontal whitespace as equivalent"));
|
|
|
|
static cl::opt<bool> IgnoreCase(
|
|
"ignore-case",
|
|
cl::desc("Use case-insensitive matching"));
|
|
|
|
static cl::list<std::string> ImplicitCheckNot(
|
|
"implicit-check-not",
|
|
cl::desc("Add an implicit negative check with this pattern to every\n"
|
|
"positive check. This can be used to ensure that no instances of\n"
|
|
"this pattern occur which are not matched by a positive pattern"),
|
|
cl::value_desc("pattern"));
|
|
|
|
static cl::list<std::string>
|
|
GlobalDefines("D", cl::AlwaysPrefix,
|
|
cl::desc("Define a variable to be used in capture patterns."),
|
|
cl::value_desc("VAR=VALUE"));
|
|
|
|
static cl::opt<bool> AllowEmptyInput(
|
|
"allow-empty", cl::init(false),
|
|
cl::desc("Allow the input file to be empty. This is useful when making\n"
|
|
"checks that some error message does not occur, for example."));
|
|
|
|
static cl::opt<bool> AllowUnusedPrefixes(
|
|
"allow-unused-prefixes",
|
|
cl::desc("Allow prefixes to be specified but not appear in the test."));
|
|
|
|
static cl::opt<bool> MatchFullLines(
|
|
"match-full-lines", cl::init(false),
|
|
cl::desc("Require all positive matches to cover an entire input line.\n"
|
|
"Allows leading and trailing whitespace if --strict-whitespace\n"
|
|
"is not also passed."));
|
|
|
|
static cl::opt<bool> EnableVarScope(
|
|
"enable-var-scope", cl::init(false),
|
|
cl::desc("Enables scope for regex variables. Variables with names that\n"
|
|
"do not start with '$' will be reset at the beginning of\n"
|
|
"each CHECK-LABEL block."));
|
|
|
|
static cl::opt<bool> AllowDeprecatedDagOverlap(
|
|
"allow-deprecated-dag-overlap", cl::init(false),
|
|
cl::desc("Enable overlapping among matches in a group of consecutive\n"
|
|
"CHECK-DAG directives. This option is deprecated and is only\n"
|
|
"provided for convenience as old tests are migrated to the new\n"
|
|
"non-overlapping CHECK-DAG implementation.\n"));
|
|
|
|
static cl::opt<bool> Verbose(
|
|
"v",
|
|
cl::desc("Print directive pattern matches, or add them to the input dump\n"
|
|
"if enabled.\n"));
|
|
|
|
static cl::opt<bool> VerboseVerbose(
|
|
"vv",
|
|
cl::desc("Print information helpful in diagnosing internal FileCheck\n"
|
|
"issues, or add it to the input dump if enabled. Implies\n"
|
|
"-v.\n"));
|
|
|
|
// The order of DumpInputValue members affects their precedence, as documented
|
|
// for -dump-input below.
|
|
enum DumpInputValue {
|
|
DumpInputNever,
|
|
DumpInputFail,
|
|
DumpInputAlways,
|
|
DumpInputHelp
|
|
};
|
|
|
|
static cl::list<DumpInputValue> DumpInputs(
|
|
"dump-input",
|
|
cl::desc("Dump input to stderr, adding annotations representing\n"
|
|
"currently enabled diagnostics. When there are multiple\n"
|
|
"occurrences of this option, the <value> that appears earliest\n"
|
|
"in the list below has precedence. The default is 'fail'.\n"),
|
|
cl::value_desc("mode"),
|
|
cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"),
|
|
clEnumValN(DumpInputAlways, "always", "Always dump input"),
|
|
clEnumValN(DumpInputFail, "fail", "Dump input on failure"),
|
|
clEnumValN(DumpInputNever, "never", "Never dump input")));
|
|
|
|
// The order of DumpInputFilterValue members affects their precedence, as
|
|
// documented for -dump-input-filter below.
|
|
enum DumpInputFilterValue {
|
|
DumpInputFilterError,
|
|
DumpInputFilterAnnotation,
|
|
DumpInputFilterAnnotationFull,
|
|
DumpInputFilterAll
|
|
};
|
|
|
|
static cl::list<DumpInputFilterValue> DumpInputFilters(
|
|
"dump-input-filter",
|
|
cl::desc("In the dump requested by -dump-input, print only input lines of\n"
|
|
"kind <value> plus any context specified by -dump-input-context.\n"
|
|
"When there are multiple occurrences of this option, the <value>\n"
|
|
"that appears earliest in the list below has precedence. The\n"
|
|
"default is 'error' when -dump-input=fail, and it's 'all' when\n"
|
|
"-dump-input=always.\n"),
|
|
cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"),
|
|
clEnumValN(DumpInputFilterAnnotationFull, "annotation-full",
|
|
"Input lines with annotations"),
|
|
clEnumValN(DumpInputFilterAnnotation, "annotation",
|
|
"Input lines with starting points of annotations"),
|
|
clEnumValN(DumpInputFilterError, "error",
|
|
"Input lines with starting points of error "
|
|
"annotations")));
|
|
|
|
static cl::list<unsigned> DumpInputContexts(
|
|
"dump-input-context", cl::value_desc("N"),
|
|
cl::desc("In the dump requested by -dump-input, print <N> input lines\n"
|
|
"before and <N> input lines after any lines specified by\n"
|
|
"-dump-input-filter. When there are multiple occurrences of\n"
|
|
"this option, the largest specified <N> has precedence. The\n"
|
|
"default is 5.\n"));
|
|
|
|
static cl::opt<unsigned> DumpInputLabelWidth(
|
|
"dump-input-label-width", cl::value_desc("N"), cl::init(0), cl::Hidden,
|
|
cl::desc("In the dump requested by -dump-input, set <N> as the minimum\n"
|
|
"width for the initial label column. When there are multiple\n"
|
|
"occurrences of this option, the last specified has precedence.\n"
|
|
"The default is 0, meaning that the actual labels fully\n"
|
|
"determine the width. FileCheck's own test suite uses this\n"
|
|
"option to avoid a fluctuating column width when checking input\n"
|
|
"dumps. This option is not expected to be useful elsewhere.\n"));
|
|
|
|
typedef cl::list<std::string>::const_iterator prefix_iterator;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void DumpCommandLine(int argc, char **argv) {
|
|
errs() << "FileCheck command line: ";
|
|
for (int I = 0; I < argc; I++)
|
|
errs() << " " << argv[I];
|
|
errs() << "\n";
|
|
}
|
|
|
|
struct MarkerStyle {
|
|
/// The first char for marking the input line.
|
|
char Head;
|
|
/// Every character for marking the input line between \c Head and \c Tail.
|
|
/// Normally it is a tilde.
|
|
char Mid;
|
|
/// The final char for marking the input line. Normally it is a tilde.
|
|
char Tail;
|
|
/// What color to use for this annotation.
|
|
raw_ostream::Colors Color;
|
|
/// A note to follow the marker, or empty string if none.
|
|
std::string Note;
|
|
/// Does this marker indicate inclusion by -dump-input-filter=error?
|
|
bool FiltersAsError;
|
|
};
|
|
|
|
static MarkerStyle getMarker(const FileCheckDiag &Diag) {
|
|
// By default, the marker is based on whether the diagnostic is an error or is
|
|
// a MatchNoteDiag on a MatchResultDiag that is an error.
|
|
//
|
|
// It's less confusing if diagnostics that don't actually have match ranges
|
|
// don't have markers. For example, a marker for the MatchNoteDiag
|
|
// 'with "VAR" equal to "5"' would seem to indicate where "VAR" matches, but
|
|
// we don't actually have that location. Instead, we just place the note
|
|
// after the start of the associated MatchResultDiag. This decision is
|
|
// overriden below for the case of MatchNoneDiag because the search range is
|
|
// used instead.
|
|
MarkerStyle Res;
|
|
bool IsError = Diag.isError() || Diag.getMatchResultDiag().isError();
|
|
if (Diag.getMatchRange()) {
|
|
Res.Head = IsError ? '!' : '^';
|
|
Res.Mid = Res.Tail = '~';
|
|
} else {
|
|
Res.Head = Res.Mid = Res.Tail = ' ';
|
|
}
|
|
Res.Color = IsError ? raw_ostream::RED : raw_ostream::GREEN;
|
|
Res.FiltersAsError = IsError;
|
|
|
|
// Add Note. Override the default Lead and Color for some diagnostic kinds.
|
|
switch (Diag.getKind()) {
|
|
case FileCheckDiag::MatchFoundDiag:
|
|
switch (cast<MatchFoundDiag>(Diag).getStatus()) {
|
|
case MatchFoundDiag::Success:
|
|
break;
|
|
case MatchFoundDiag::Excluded:
|
|
Res.Note = "no match expected";
|
|
break;
|
|
case MatchFoundDiag::WrongLine:
|
|
Res.Note = "match on wrong line";
|
|
break;
|
|
case MatchFoundDiag::Discarded:
|
|
Res.Head = '!'; // Not an error, but not a successful match either.
|
|
Res.Color = raw_ostream::CYAN;
|
|
Res.Note = "discard: overlaps earlier match";
|
|
break;
|
|
}
|
|
break;
|
|
case FileCheckDiag::MatchNoneDiag:
|
|
Res.Head = 'X';
|
|
Res.Mid = Res.Tail = '~';
|
|
switch (cast<MatchNoneDiag>(Diag).getStatus()) {
|
|
case MatchNoneDiag::Success:
|
|
break;
|
|
case MatchNoneDiag::InvalidPattern:
|
|
Res.Note = "match failed for invalid pattern";
|
|
break;
|
|
case MatchNoneDiag::Expected:
|
|
Res.Note = "no match found";
|
|
break;
|
|
}
|
|
break;
|
|
case FileCheckDiag::MatchFuzzyDiag:
|
|
Res.Head = '?';
|
|
Res.Color = raw_ostream::MAGENTA;
|
|
Res.Note = "possible intended match";
|
|
break;
|
|
case FileCheckDiag::MatchCustomNoteDiag:
|
|
Res.Note = cast<MatchCustomNoteDiag>(Diag).getNote();
|
|
break;
|
|
}
|
|
if (Diag.isError()) {
|
|
assert(!Res.Note.empty() && "expected error diagnostic to have note");
|
|
Res.Note = "error: " + Res.Note;
|
|
}
|
|
return Res;
|
|
}
|
|
|
|
static void DumpInputAnnotationHelp(raw_ostream &OS) {
|
|
OS << "The following description was requested by -dump-input=help to\n"
|
|
<< "explain the input dump printed by FileCheck.\n"
|
|
<< "\n"
|
|
<< "Related command-line options:\n"
|
|
<< "\n"
|
|
<< " - -dump-input=<value> enables or disables the input dump\n"
|
|
<< " - -dump-input-filter=<value> filters the input lines\n"
|
|
<< " - -dump-input-context=<N> adjusts the context of filtered lines\n"
|
|
<< " - -v and -vv add more annotations\n"
|
|
<< " - -color forces colors to be enabled both in the dump and below\n"
|
|
<< " - -help documents the above options in more detail\n"
|
|
<< "\n"
|
|
<< "These options can also be set via FILECHECK_OPTS. For example, for\n"
|
|
<< "maximum debugging output on failures:\n"
|
|
<< "\n"
|
|
<< " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n"
|
|
<< "\n"
|
|
<< "Input dump annotation format:\n"
|
|
<< "\n";
|
|
|
|
// Labels for input lines.
|
|
OS << " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:";
|
|
OS << " labels line number L of the input file\n"
|
|
<< " An extra space is added after each input line to represent"
|
|
<< " the\n"
|
|
<< " newline character\n";
|
|
|
|
// Labels for annotation lines.
|
|
OS << " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L";
|
|
OS << " labels the only match result for either (1) a pattern of type T"
|
|
<< " from\n"
|
|
<< " line L of the check file if L is an integer or (2) the"
|
|
<< " I-th implicit\n"
|
|
<< " pattern if L is \"imp\" followed by an integer "
|
|
<< "I (index origin one)\n";
|
|
OS << " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N";
|
|
OS << " labels the Nth match result for such a pattern\n";
|
|
|
|
// Markers on annotation lines.
|
|
OS << " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~";
|
|
OS << " marks good match (reported if -v)\n"
|
|
<< " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~";
|
|
OS << " marks bad match, such as:\n"
|
|
<< " - CHECK-NEXT on same line as previous match (error)\n"
|
|
<< " - CHECK-NOT found (error)\n"
|
|
<< " - CHECK-DAG overlapping match (discarded, reported if "
|
|
<< "-vv)\n"
|
|
<< " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~";
|
|
OS << " marks search range when no match is found, such as:\n"
|
|
<< " - CHECK-NEXT not found (error)\n"
|
|
<< " - CHECK-NOT not found (success, reported if -vv)\n"
|
|
<< " - CHECK-DAG not found after discarded matches (error)\n"
|
|
<< " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?";
|
|
OS << " marks fuzzy match when no match is found\n";
|
|
|
|
// Elided lines.
|
|
OS << " - ";
|
|
WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "...";
|
|
OS << " indicates elided input lines and annotations, as specified by\n"
|
|
<< " -dump-input-filter and -dump-input-context\n";
|
|
|
|
// Colors.
|
|
OS << " - colors ";
|
|
WithColor(OS, raw_ostream::GREEN, true) << "success";
|
|
OS << ", ";
|
|
WithColor(OS, raw_ostream::RED, true) << "error";
|
|
OS << ", ";
|
|
WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match";
|
|
OS << ", ";
|
|
WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match";
|
|
OS << ", ";
|
|
WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input";
|
|
OS << "\n";
|
|
}
|
|
|
|
/// An annotation for a single input line.
|
|
struct InputAnnotation {
|
|
/// A globally unique index for this annotation before it was broken into
|
|
/// multiple lines.
|
|
unsigned LabelIndexGlobal;
|
|
/// The globally unique label for this annotation before it was broken into
|
|
/// multiple lines. There is one \c Label per \c LabelIndexGlobal and
|
|
/// vice-versa.
|
|
std::string Label;
|
|
/// Is this the initial (possibly only) fragment of an annotation, which has
|
|
/// been broken across multiple lines if necessary?
|
|
bool IsFirstLine;
|
|
/// What input line (one-origin indexing) this annotation marks. This might
|
|
/// be different from the starting line of the original diagnostic if
|
|
/// !IsFirstLine.
|
|
unsigned InputLine;
|
|
/// The column range (one-origin indexing, inclusive boundaries) in which to
|
|
/// mark the input line. If \c InputLastCol is \c UINT_MAX, the rest of the
|
|
/// input line should be marked.
|
|
unsigned InputFirstCol, InputLastCol;
|
|
/// The marker to use.
|
|
MarkerStyle Marker;
|
|
/// Whether this annotation represents a good match for an expected pattern.
|
|
bool FoundAndExpectedMatch;
|
|
};
|
|
|
|
/// Get an abbreviation for the check type.
|
|
static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) {
|
|
switch (Ty) {
|
|
case Check::CheckPlain:
|
|
if (Ty.getCount() > 1)
|
|
return "count";
|
|
return "check";
|
|
case Check::CheckNext:
|
|
return "next";
|
|
case Check::CheckSame:
|
|
return "same";
|
|
case Check::CheckNot:
|
|
return "not";
|
|
case Check::CheckDAG:
|
|
return "dag";
|
|
case Check::CheckLabel:
|
|
return "label";
|
|
case Check::CheckEmpty:
|
|
return "empty";
|
|
case Check::CheckComment:
|
|
return "com";
|
|
case Check::CheckEOF:
|
|
return "eof";
|
|
case Check::CheckBadNot:
|
|
return "bad-not";
|
|
case Check::CheckBadCount:
|
|
return "bad-count";
|
|
case Check::CheckMisspelled:
|
|
return "misspelled";
|
|
case Check::CheckNone:
|
|
llvm_unreachable("invalid FileCheckType");
|
|
}
|
|
llvm_unreachable("unknown FileCheckType");
|
|
}
|
|
|
|
namespace {
|
|
/// Stores all information needed to generate \c InputAnnotation labels for a
|
|
/// particular check pattern. Multiple labelers might be constructed for the
|
|
/// same pattern if the pattern has more than one \c MatchResultDiag (e.g., for
|
|
/// a \c CHECK-COUNT-<N> directive or implicit pattern).
|
|
class InputAnnotationLabeler {
|
|
private:
|
|
unsigned *LabelWidthGlobal;
|
|
unsigned *LabelIndexPerPattern;
|
|
std::string LabelPrefix;
|
|
|
|
public:
|
|
/// Make an invalid labeler to be overwritten by a valid one before calling
|
|
/// \c generateLabel.
|
|
InputAnnotationLabeler()
|
|
: LabelWidthGlobal(nullptr), LabelIndexPerPattern(nullptr) {}
|
|
/// - \p CheckFileBufferID is the buffer ID for the check file.
|
|
/// - \p ImpPatBufferIDRange is the buffer ID range for all implicit patterns.
|
|
/// - \p LabelWidthGlobal is the widest label generated so far over all
|
|
/// patterns. It will be updated by each call to \c generateLabel.
|
|
/// - \p CheckTy and \p CheckLoc identify the pattern that produced all
|
|
/// diagnostics for which this labeler will generate labels.
|
|
/// - \p LabelIndexPerPattern is either \c nullptr if only one label is
|
|
/// required for the pattern for which this labeler will generate labels, or
|
|
/// it points to the per-pattern index of the next label to be generated for
|
|
/// that pattern. In the latter case, the index will be incremented by each
|
|
/// call to \c generateLabel.
|
|
InputAnnotationLabeler(const SourceMgr &SM, unsigned CheckFileBufferID,
|
|
std::pair<unsigned, unsigned> ImpPatBufferIDRange,
|
|
unsigned &LabelWidthGlobal,
|
|
Check::FileCheckType CheckTy, SMLoc CheckLoc,
|
|
unsigned *LabelIndexPerPattern)
|
|
: LabelWidthGlobal(&LabelWidthGlobal),
|
|
LabelIndexPerPattern(LabelIndexPerPattern) {
|
|
llvm::raw_string_ostream LabelStrm(LabelPrefix);
|
|
LabelStrm << GetCheckTypeAbbreviation(CheckTy) << ":";
|
|
unsigned CheckBufferID = SM.FindBufferContainingLoc(CheckLoc);
|
|
if (CheckBufferID == CheckFileBufferID)
|
|
LabelStrm << SM.getLineAndColumn(CheckLoc, CheckBufferID).first;
|
|
else if (ImpPatBufferIDRange.first <= CheckBufferID &&
|
|
CheckBufferID < ImpPatBufferIDRange.second)
|
|
LabelStrm << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1);
|
|
else
|
|
llvm_unreachable("expected check location to be either in the check file "
|
|
"or for an implicit pattern");
|
|
}
|
|
/// Write a globally unique label into \p Label.
|
|
void generateLabel(std::string &Label) {
|
|
assert(!LabelPrefix.empty() &&
|
|
"unexpected generateLabel call on invalid labeler");
|
|
assert(Label.empty() && "expected empty string for writing label");
|
|
llvm::raw_string_ostream LabelStrm(Label);
|
|
LabelStrm << LabelPrefix;
|
|
if (LabelIndexPerPattern)
|
|
LabelStrm << "'" << (*LabelIndexPerPattern)++;
|
|
*LabelWidthGlobal =
|
|
std::max((std::string::size_type)*LabelWidthGlobal, Label.size());
|
|
}
|
|
};
|
|
|
|
/// A range specifying where annotation markers are physically \a drawn in the
|
|
/// input dump.
|
|
struct MarkerRange {
|
|
public:
|
|
/// An inclusive \c MarkerRange boundary. Both line and column use a 1-based
|
|
/// index origin.
|
|
struct Loc {
|
|
unsigned Line;
|
|
unsigned Col;
|
|
/// Make an invalid location to be overwritten before being used.
|
|
Loc() : Line(0), Col(0) {}
|
|
/// Make a valid location.
|
|
Loc(const std::pair<unsigned, unsigned> &LineAndCol)
|
|
: Line(LineAndCol.first), Col(LineAndCol.second) {}
|
|
};
|
|
|
|
private:
|
|
/// Location of the first marked character.
|
|
Loc First;
|
|
/// Location of the last marked character.
|
|
Loc Last;
|
|
MarkerRange(Loc First, Loc Last) : First(First), Last(Last) {}
|
|
|
|
public:
|
|
/// Make an invalid range to be overwritten before being used.
|
|
MarkerRange() = default;
|
|
/// \p Range specifies the \a logical input range to be depicted by annotation
|
|
/// markers \a drawn at the resulting \c MarkerRange.
|
|
///
|
|
/// If \p Range is an empty range, then the resulting \c MarkerRange is
|
|
/// expanded to a single character. This avoids a missing marker for an empty
|
|
/// range, but it means the markers for a single-character range are
|
|
/// indistinguishable from markers for an empty range.
|
|
MarkerRange(const SourceMgr &SM, SMRange Range) {
|
|
// Range has an inclusive start as MarkerRange requires.
|
|
First = SM.getLineAndColumn(Range.Start);
|
|
// Range has an exclusive end, but MarkerRange requires an inclusive end.
|
|
if (Range.Start == Range.End) {
|
|
// Convert the empty range to a one-character range.
|
|
Last = First;
|
|
} else {
|
|
// We cannot simply subtract one from the end column number because that
|
|
// might result in column 0, which does not exist and is thus incorrect
|
|
// for an inclusive boundary.
|
|
SMLoc EndLoc = SMLoc::getFromPointer(Range.End.getPointer() - 1);
|
|
Last = SM.getLineAndColumn(EndLoc);
|
|
}
|
|
}
|
|
/// Is the marker range contained on a single line?
|
|
bool isSingleLine() const { return First.Line == Last.Line; }
|
|
/// Get the location of the first marked character.
|
|
Loc getFirstLoc() const { return First; }
|
|
/// Get the location of the last marked character.
|
|
Loc getLastLoc() const { return Last; }
|
|
/// Return a range marking only the first character.
|
|
MarkerRange truncate() const { return {First, First}; }
|
|
};
|
|
} // namespace
|
|
|
|
static void
|
|
buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
|
|
const std::pair<unsigned, unsigned> &ImpPatBufferIDRange,
|
|
const FileCheckDiagList &Diags,
|
|
std::vector<InputAnnotation> &Annotations,
|
|
unsigned &LabelWidthGlobal) {
|
|
struct CompareSMLoc {
|
|
bool operator()(SMLoc LHS, SMLoc RHS) const {
|
|
return LHS.getPointer() < RHS.getPointer();
|
|
}
|
|
};
|
|
|
|
// How many unique input annotation labels does each check pattern need? Each
|
|
// check pattern can have multiple MatchResultDiag's, each followed by a
|
|
// series of zero or more MatchNoteDiag's. Each such MatchResultDiag and its
|
|
// MatchNoteDiag series can require multiple labels.
|
|
std::map<SMLoc, unsigned, CompareSMLoc> LabelCountPerPattern;
|
|
for (const FileCheckDiag &Diag : Diags)
|
|
++LabelCountPerPattern[Diag.getMatchResultDiag().getCheckLoc()];
|
|
// How many labels have we generated so far per check pattern?
|
|
std::map<SMLoc, unsigned, CompareSMLoc> LabelIndexPerPattern;
|
|
// How many total labels have we generated so far?
|
|
unsigned LabelIndexGlobal = 0;
|
|
// What's the widest label we've generated so far?
|
|
LabelWidthGlobal = 0;
|
|
// The labeler for the current MatchResultDiag and its MatchNoteDiag series.
|
|
InputAnnotationLabeler CurLabeler;
|
|
for (const FileCheckDiag &Diag : Diags) {
|
|
if (const MatchResultDiag *MRD = dyn_cast<MatchResultDiag>(&Diag)) {
|
|
CurLabeler = InputAnnotationLabeler(
|
|
SM, CheckFileBufferID, ImpPatBufferIDRange, LabelWidthGlobal,
|
|
MRD->getCheckTy(), MRD->getCheckLoc(),
|
|
LabelCountPerPattern[MRD->getCheckLoc()] > 1
|
|
? &LabelIndexPerPattern[MRD->getCheckLoc()]
|
|
: nullptr);
|
|
}
|
|
|
|
// Build label that is unique for this input annotation before it is
|
|
// potentially broken across multiple lines.
|
|
InputAnnotation A;
|
|
A.LabelIndexGlobal = LabelIndexGlobal++;
|
|
CurLabeler.generateLabel(A.Label);
|
|
|
|
// Build the input marker.
|
|
A.Marker = getMarker(Diag);
|
|
|
|
// Does this diagnostic mark text that has been successfully matched?
|
|
A.FoundAndExpectedMatch = false;
|
|
if (const MatchFoundDiag *Found = dyn_cast<MatchFoundDiag>(&Diag)) {
|
|
if (Found->getStatus() == MatchFoundDiag::Success)
|
|
A.FoundAndExpectedMatch = true;
|
|
}
|
|
|
|
// If Diag has a match range, position the marker there. If it is a
|
|
// MatchNoneDiag, position the marker at its search range. Otherwise,
|
|
// position the marker at the start of the most recent MatchResultDiag, with
|
|
// which it is associated.
|
|
MarkerRange InputRange;
|
|
if (Diag.getMatchRange()) {
|
|
InputRange = MarkerRange(SM, *Diag.getMatchRange());
|
|
} else if (const MatchNoneDiag *MND = dyn_cast<MatchNoneDiag>(&Diag)) {
|
|
InputRange = MarkerRange(SM, MND->getSearchRange());
|
|
} else {
|
|
assert(isa<MatchNoteDiag>(Diag) &&
|
|
"expected only MatchNoteDiag to have no input range");
|
|
const MatchResultDiag &MRD = Diag.getMatchResultDiag();
|
|
InputRange = MRD.getMatchRange() ? MarkerRange(SM, *MRD.getMatchRange())
|
|
: MarkerRange(SM, MRD.getSearchRange());
|
|
InputRange = InputRange.truncate();
|
|
assert(A.Marker.Head == ' ' && "expected no marker for no match range");
|
|
}
|
|
|
|
// Compute the marker location, and break annotation into multiple
|
|
// annotations if it spans multiple lines.
|
|
A.IsFirstLine = true;
|
|
A.InputLine = InputRange.getFirstLoc().Line;
|
|
A.InputFirstCol = InputRange.getFirstLoc().Col;
|
|
if (InputRange.isSingleLine()) {
|
|
A.InputLastCol = InputRange.getLastLoc().Col;
|
|
Annotations.push_back(A);
|
|
} else {
|
|
A.InputLastCol = UINT_MAX;
|
|
char MarkerTail = A.Marker.Tail;
|
|
A.Marker.Tail = A.Marker.Mid;
|
|
Annotations.push_back(A);
|
|
for (unsigned L = InputRange.getFirstLoc().Line + 1,
|
|
E = InputRange.getLastLoc().Line;
|
|
L <= E; ++L) {
|
|
InputAnnotation B;
|
|
B.LabelIndexGlobal = A.LabelIndexGlobal;
|
|
B.Label = A.Label;
|
|
B.IsFirstLine = false;
|
|
B.InputLine = L;
|
|
B.Marker = A.Marker;
|
|
B.Marker.Head = B.Marker.Mid = A.Marker.Mid;
|
|
B.Marker.Tail = L != E ? A.Marker.Mid : MarkerTail;
|
|
B.Marker.Note = "";
|
|
B.InputFirstCol = 1;
|
|
B.InputLastCol = L != E ? UINT_MAX : InputRange.getLastLoc().Col;
|
|
B.FoundAndExpectedMatch = A.FoundAndExpectedMatch;
|
|
Annotations.push_back(B);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsigned FindInputLineInFilter(
|
|
DumpInputFilterValue DumpInputFilter, unsigned CurInputLine,
|
|
const std::vector<InputAnnotation>::iterator &AnnotationBeg,
|
|
const std::vector<InputAnnotation>::iterator &AnnotationEnd) {
|
|
if (DumpInputFilter == DumpInputFilterAll)
|
|
return CurInputLine;
|
|
for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd;
|
|
++AnnotationItr) {
|
|
switch (DumpInputFilter) {
|
|
case DumpInputFilterAll:
|
|
llvm_unreachable("unexpected DumpInputFilterAll");
|
|
break;
|
|
case DumpInputFilterAnnotationFull:
|
|
return AnnotationItr->InputLine;
|
|
case DumpInputFilterAnnotation:
|
|
if (AnnotationItr->IsFirstLine)
|
|
return AnnotationItr->InputLine;
|
|
break;
|
|
case DumpInputFilterError:
|
|
if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError)
|
|
return AnnotationItr->InputLine;
|
|
break;
|
|
}
|
|
}
|
|
return UINT_MAX;
|
|
}
|
|
|
|
/// To OS, print a vertical ellipsis (right-justified at LabelWidthGlobal) if it
|
|
/// would occupy less lines than ElidedLines, but print ElidedLines otherwise.
|
|
/// Either way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing.
|
|
static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines,
|
|
unsigned LabelWidthGlobal) {
|
|
if (ElidedLines.empty())
|
|
return;
|
|
unsigned EllipsisLines = 3;
|
|
if (EllipsisLines < StringRef(ElidedLines).count('\n')) {
|
|
for (unsigned i = 0; i < EllipsisLines; ++i) {
|
|
WithColor(OS, raw_ostream::BLACK, /*Bold=*/true)
|
|
<< right_justify(".", LabelWidthGlobal);
|
|
OS << '\n';
|
|
}
|
|
} else
|
|
OS << ElidedLines;
|
|
ElidedLines.clear();
|
|
}
|
|
|
|
static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
|
|
DumpInputFilterValue DumpInputFilter,
|
|
unsigned DumpInputContext,
|
|
StringRef InputFileText,
|
|
std::vector<InputAnnotation> &Annotations,
|
|
unsigned LabelWidthGlobal) {
|
|
OS << "Input was:\n<<<<<<\n";
|
|
|
|
// Sort annotations.
|
|
llvm::sort(Annotations,
|
|
[](const InputAnnotation &A, const InputAnnotation &B) {
|
|
// 1. Sort annotations in the order of the input lines.
|
|
//
|
|
// This makes it easier to find relevant annotations while
|
|
// iterating input lines in the implementation below. FileCheck
|
|
// does not always produce diagnostics in the order of input
|
|
// lines due to, for example, CHECK-DAG and CHECK-NOT.
|
|
if (A.InputLine != B.InputLine)
|
|
return A.InputLine < B.InputLine;
|
|
// 2. Sort annotations in the temporal order FileCheck produced
|
|
// their associated diagnostics.
|
|
//
|
|
// This sort offers several benefits:
|
|
//
|
|
// A. On a single input line, the order of annotations reflects
|
|
// the FileCheck logic for processing directives/patterns.
|
|
// This can be helpful in understanding cases in which the
|
|
// order of the associated directives/patterns in the check
|
|
// file or on the command line either (i) does not match the
|
|
// temporal order in which FileCheck looks for matches for the
|
|
// directives/patterns (due to, for example, CHECK-LABEL,
|
|
// CHECK-NOT, or `--implicit-check-not`) or (ii) does match
|
|
// that order but does not match the order of those
|
|
// diagnostics along an input line (due to, for example,
|
|
// CHECK-DAG).
|
|
//
|
|
// On the other hand, because our presentation format presents
|
|
// input lines in order, there's no clear way to offer the
|
|
// same benefit across input lines. For consistency, it might
|
|
// then seem worthwhile to have annotations on a single line
|
|
// also sorted in input order (that is, by input column).
|
|
// However, in practice, this appears to be more confusing
|
|
// than helpful. Perhaps it's intuitive to expect annotations
|
|
// to be listed in the temporal order in which they were
|
|
// produced except in cases the presentation format obviously
|
|
// and inherently cannot support it (that is, across input
|
|
// lines).
|
|
//
|
|
// B. When diagnostics' annotations are split among multiple
|
|
// input lines, the user must track them from one input line
|
|
// to the next. One property of the sort chosen here is that
|
|
// it facilitates the user in this regard by ensuring the
|
|
// following: when comparing any two input lines, a
|
|
// diagnostic's annotations are sorted in the same position
|
|
// relative to all other diagnostics' annotations.
|
|
return A.LabelIndexGlobal < B.LabelIndexGlobal;
|
|
});
|
|
|
|
// Compute the width of the label column.
|
|
const unsigned char *InputFilePtr = InputFileText.bytes_begin(),
|
|
*InputFileEnd = InputFileText.bytes_end();
|
|
unsigned LineCount = InputFileText.count('\n');
|
|
if (InputFileEnd[-1] != '\n')
|
|
++LineCount;
|
|
unsigned LineNoWidth = NumDigitsBase10(LineCount);
|
|
// +3 below adds spaces (1) to the left of the (right-aligned) line numbers
|
|
// on input lines and (2) to the right of the (left-aligned) labels on
|
|
// annotation lines so that input lines and annotation lines are more
|
|
// visually distinct. For example, the spaces on the annotation lines ensure
|
|
// that input line numbers and check directive line numbers never align
|
|
// horizontally. Those line numbers might not even be for the same file.
|
|
// One space would be enough to achieve that, but more makes it even easier
|
|
// to see.
|
|
LabelWidthGlobal = std::max(LabelWidthGlobal, LineNoWidth) + 3;
|
|
LabelWidthGlobal = std::max(LabelWidthGlobal, DumpInputLabelWidth.getValue());
|
|
|
|
// Print annotated input lines.
|
|
unsigned PrevLineInFilter = 0; // 0 means none so far
|
|
unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none
|
|
std::string ElidedLines;
|
|
raw_string_ostream ElidedLinesOS(ElidedLines);
|
|
ColorMode TheColorMode =
|
|
WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable;
|
|
if (TheColorMode == ColorMode::Enable)
|
|
ElidedLinesOS.enable_colors(true);
|
|
auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end();
|
|
for (unsigned Line = 1;
|
|
InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd;
|
|
++Line) {
|
|
const unsigned char *InputFileLine = InputFilePtr;
|
|
|
|
// Compute the previous and next line included by the filter.
|
|
if (NextLineInFilter < Line)
|
|
NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line,
|
|
AnnotationItr, AnnotationEnd);
|
|
assert(NextLineInFilter && "expected NextLineInFilter to be computed");
|
|
if (NextLineInFilter == Line)
|
|
PrevLineInFilter = Line;
|
|
|
|
// Elide this input line and its annotations if it's not within the
|
|
// context specified by -dump-input-context of an input line included by
|
|
// -dump-input-filter. However, in case the resulting ellipsis would occupy
|
|
// more lines than the input lines and annotations it elides, buffer the
|
|
// elided lines and annotations so we can print them instead.
|
|
raw_ostream *LineOS;
|
|
if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) &&
|
|
(NextLineInFilter == UINT_MAX ||
|
|
Line + DumpInputContext < NextLineInFilter))
|
|
LineOS = &ElidedLinesOS;
|
|
else {
|
|
LineOS = &OS;
|
|
DumpEllipsisOrElidedLines(OS, ElidedLines, LabelWidthGlobal);
|
|
}
|
|
|
|
// Print right-aligned line number.
|
|
WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false,
|
|
TheColorMode)
|
|
<< format_decimal(Line, LabelWidthGlobal) << ": ";
|
|
|
|
// For the case where -v and colors are enabled, find the annotations for
|
|
// good matches for expected patterns in order to highlight everything
|
|
// else in the line. There are no such annotations if -v is disabled.
|
|
std::vector<InputAnnotation> FoundAndExpectedMatches;
|
|
if (Req.Verbose && TheColorMode == ColorMode::Enable) {
|
|
for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line;
|
|
++I) {
|
|
if (I->FoundAndExpectedMatch)
|
|
FoundAndExpectedMatches.push_back(*I);
|
|
}
|
|
}
|
|
|
|
// Print numbered line with highlighting where there are no matches for
|
|
// expected patterns.
|
|
bool Newline = false;
|
|
{
|
|
WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false,
|
|
/*BG=*/false, TheColorMode);
|
|
bool InMatch = false;
|
|
if (Req.Verbose)
|
|
COS.changeColor(raw_ostream::CYAN, true, true);
|
|
for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) {
|
|
bool WasInMatch = InMatch;
|
|
InMatch = false;
|
|
for (const InputAnnotation &M : FoundAndExpectedMatches) {
|
|
if (M.InputFirstCol <= Col && Col <= M.InputLastCol) {
|
|
InMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!WasInMatch && InMatch)
|
|
COS.resetColor();
|
|
else if (WasInMatch && !InMatch)
|
|
COS.changeColor(raw_ostream::CYAN, true, true);
|
|
if (*InputFilePtr == '\n') {
|
|
Newline = true;
|
|
COS << ' ';
|
|
} else
|
|
COS << *InputFilePtr;
|
|
++InputFilePtr;
|
|
}
|
|
}
|
|
*LineOS << '\n';
|
|
unsigned InputLineWidth = InputFilePtr - InputFileLine;
|
|
|
|
// Print any annotations.
|
|
while (AnnotationItr != AnnotationEnd &&
|
|
AnnotationItr->InputLine == Line) {
|
|
WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true,
|
|
/*BG=*/false, TheColorMode);
|
|
// The two spaces below are where the ": " appears on input lines.
|
|
COS << left_justify(AnnotationItr->Label, LabelWidthGlobal) << " ";
|
|
unsigned Col;
|
|
for (Col = 1; Col < AnnotationItr->InputFirstCol; ++Col)
|
|
COS << ' ';
|
|
COS << AnnotationItr->Marker.Head;
|
|
// If InputLastCol==UINT_MAX, stop at InputLineWidth.
|
|
for (++Col; Col < AnnotationItr->InputLastCol && Col <= InputLineWidth;
|
|
++Col)
|
|
COS << AnnotationItr->Marker.Mid;
|
|
if (Col <= AnnotationItr->InputLastCol && Col <= InputLineWidth) {
|
|
COS << AnnotationItr->Marker.Tail;
|
|
++Col;
|
|
}
|
|
const std::string &Note = AnnotationItr->Marker.Note;
|
|
if (!Note.empty()) {
|
|
// Put the note at the end of the input line. If we were to instead
|
|
// put the note right after the marker, subsequent annotations for the
|
|
// same input line might appear to mark this note instead of the input
|
|
// line.
|
|
for (; Col <= InputLineWidth; ++Col)
|
|
COS << ' ';
|
|
COS << ' ' << Note;
|
|
}
|
|
COS << '\n';
|
|
++AnnotationItr;
|
|
}
|
|
}
|
|
DumpEllipsisOrElidedLines(OS, ElidedLines, LabelWidthGlobal);
|
|
|
|
OS << ">>>>>>\n";
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
// Enable use of ANSI color codes because FileCheck is using them to
|
|
// highlight text.
|
|
llvm::sys::Process::UseANSIEscapeCodes(true);
|
|
|
|
InitLLVM X(argc, argv);
|
|
cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr,
|
|
/*VFS*/ nullptr, "FILECHECK_OPTS");
|
|
|
|
// Select -dump-input* values. The -help documentation specifies the default
|
|
// value and which value to choose if an option is specified multiple times.
|
|
// In the latter case, the general rule of thumb is to choose the value that
|
|
// provides the most information.
|
|
DumpInputValue DumpInput =
|
|
DumpInputs.empty() ? DumpInputFail : *llvm::max_element(DumpInputs);
|
|
DumpInputFilterValue DumpInputFilter;
|
|
if (DumpInputFilters.empty())
|
|
DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll
|
|
: DumpInputFilterError;
|
|
else
|
|
DumpInputFilter = *llvm::max_element(DumpInputFilters);
|
|
unsigned DumpInputContext =
|
|
DumpInputContexts.empty() ? 5 : *llvm::max_element(DumpInputContexts);
|
|
|
|
if (DumpInput == DumpInputHelp) {
|
|
DumpInputAnnotationHelp(outs());
|
|
return 0;
|
|
}
|
|
if (CheckFilename.empty()) {
|
|
errs() << "<check-file> not specified\n";
|
|
return 2;
|
|
}
|
|
|
|
FileCheckRequest Req;
|
|
append_range(Req.CheckPrefixes, CheckPrefixes);
|
|
|
|
append_range(Req.CommentPrefixes, CommentPrefixes);
|
|
|
|
append_range(Req.ImplicitCheckNot, ImplicitCheckNot);
|
|
|
|
bool GlobalDefineError = false;
|
|
for (StringRef G : GlobalDefines) {
|
|
size_t EqIdx = G.find('=');
|
|
if (EqIdx == std::string::npos) {
|
|
errs() << "Missing equal sign in command-line definition '-D" << G
|
|
<< "'\n";
|
|
GlobalDefineError = true;
|
|
continue;
|
|
}
|
|
if (EqIdx == 0) {
|
|
errs() << "Missing variable name in command-line definition '-D" << G
|
|
<< "'\n";
|
|
GlobalDefineError = true;
|
|
continue;
|
|
}
|
|
Req.GlobalDefines.push_back(G);
|
|
}
|
|
if (GlobalDefineError)
|
|
return 2;
|
|
|
|
Req.AllowEmptyInput = AllowEmptyInput;
|
|
Req.AllowUnusedPrefixes = AllowUnusedPrefixes;
|
|
Req.EnableVarScope = EnableVarScope;
|
|
Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap;
|
|
Req.Verbose = Verbose;
|
|
Req.VerboseVerbose = VerboseVerbose;
|
|
Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace;
|
|
Req.MatchFullLines = MatchFullLines;
|
|
Req.IgnoreCase = IgnoreCase;
|
|
|
|
if (VerboseVerbose)
|
|
Req.Verbose = true;
|
|
|
|
FileCheck FC(Req);
|
|
if (!FC.ValidateCheckPrefixes())
|
|
return 2;
|
|
|
|
SourceMgr SM;
|
|
|
|
// Read the expected strings from the check file.
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> CheckFileOrErr =
|
|
MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true);
|
|
if (std::error_code EC = CheckFileOrErr.getError()) {
|
|
errs() << "Could not open check file '" << CheckFilename
|
|
<< "': " << EC.message() << '\n';
|
|
return 2;
|
|
}
|
|
MemoryBuffer &CheckFile = *CheckFileOrErr.get();
|
|
|
|
SmallString<4096> CheckFileBuffer;
|
|
StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer);
|
|
|
|
unsigned CheckFileBufferID =
|
|
SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer(
|
|
CheckFileText, CheckFile.getBufferIdentifier()),
|
|
SMLoc());
|
|
|
|
std::pair<unsigned, unsigned> ImpPatBufferIDRange;
|
|
if (FC.readCheckFile(SM, CheckFileText, &ImpPatBufferIDRange))
|
|
return 2;
|
|
|
|
// Open the file to check and add it to SourceMgr.
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> InputFileOrErr =
|
|
MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true);
|
|
if (InputFilename == "-")
|
|
InputFilename = "<stdin>"; // Overwrite for improved diagnostic messages
|
|
if (std::error_code EC = InputFileOrErr.getError()) {
|
|
errs() << "Could not open input file '" << InputFilename
|
|
<< "': " << EC.message() << '\n';
|
|
return 2;
|
|
}
|
|
MemoryBuffer &InputFile = *InputFileOrErr.get();
|
|
|
|
if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) {
|
|
errs() << "FileCheck error: '" << InputFilename << "' is empty.\n";
|
|
DumpCommandLine(argc, argv);
|
|
return 2;
|
|
}
|
|
|
|
SmallString<4096> InputFileBuffer;
|
|
StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer);
|
|
|
|
SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer(
|
|
InputFileText, InputFile.getBufferIdentifier()),
|
|
SMLoc());
|
|
|
|
FileCheckDiagList Diags;
|
|
int ExitCode = FC.checkInput(SM, InputFileText,
|
|
DumpInput == DumpInputNever ? nullptr : &Diags)
|
|
? EXIT_SUCCESS
|
|
: 1;
|
|
if (DumpInput == DumpInputAlways ||
|
|
(ExitCode == 1 && DumpInput == DumpInputFail)) {
|
|
errs() << "\n"
|
|
<< "Input file: " << InputFilename << "\n"
|
|
<< "Check file: " << CheckFilename << "\n"
|
|
<< "\n"
|
|
<< "-dump-input=help explains the following input dump.\n"
|
|
<< "\n";
|
|
std::vector<InputAnnotation> Annotations;
|
|
unsigned LabelWidthGlobal;
|
|
buildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags,
|
|
Annotations, LabelWidthGlobal);
|
|
DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext,
|
|
InputFileText, Annotations, LabelWidthGlobal);
|
|
}
|
|
|
|
return ExitCode;
|
|
}
|