Skip to content

Commit 98f7d75

Browse files
authored
[FileCheck] Improve printing variables with escapes (#145865)
Firstly fix FileCheck printing string variables double-escaped (first regex, then C-style). This is confusing because it is not clear if the printed value is the literal value or exactly how it is escaped, without looking at FileCheck's source code. Secondly, only escape when doing so makes it easier to read the value (when the string contains tabs, newlines or non-printable characters). When the variable value is escaped, make a note of it in the output too, in order to avoid confusion. The common case that is motivating this change is variables that contain windows style paths with backslashes. These were printed as `"C:\\\\Program Files\\\\MyApp\\\\file.txt"`. Now prefer to print them as `"C:\Program Files\MyApp\file.txt"`. Printing the value literally also makes it easier to search for variables in the output, since the user can just copy-paste it.
1 parent 526701f commit 98f7d75

File tree

4 files changed

+75
-17
lines changed

4 files changed

+75
-17
lines changed

llvm/lib/FileCheck/FileCheck.cpp

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ BinaryOperation::getImplicitFormat(const SourceMgr &SM) const {
264264
: *RightFormat;
265265
}
266266

267-
Expected<std::string> NumericSubstitution::getResult() const {
267+
Expected<std::string> NumericSubstitution::getResultRegex() const {
268268
assert(ExpressionPointer->getAST() != nullptr &&
269269
"Substituting empty expression");
270270
Expected<APInt> EvaluatedValue = ExpressionPointer->getAST()->eval();
@@ -274,14 +274,55 @@ Expected<std::string> NumericSubstitution::getResult() const {
274274
return Format.getMatchingString(*EvaluatedValue);
275275
}
276276

277-
Expected<std::string> StringSubstitution::getResult() const {
277+
Expected<std::string> NumericSubstitution::getResultForDiagnostics() const {
278+
// The "regex" returned by getResultRegex() is just a numeric value
279+
// like '42', '0x2A', '-17', 'DEADBEEF' etc. This is already suitable for use
280+
// in diagnostics.
281+
Expected<std::string> Literal = getResultRegex();
282+
if (!Literal)
283+
return Literal;
284+
285+
return "\"" + std::move(*Literal) + "\"";
286+
}
287+
288+
Expected<std::string> StringSubstitution::getResultRegex() const {
278289
// Look up the value and escape it so that we can put it into the regex.
279290
Expected<StringRef> VarVal = Context->getPatternVarValue(FromStr);
280291
if (!VarVal)
281292
return VarVal.takeError();
282293
return Regex::escape(*VarVal);
283294
}
284295

296+
Expected<std::string> StringSubstitution::getResultForDiagnostics() const {
297+
Expected<StringRef> VarVal = Context->getPatternVarValue(FromStr);
298+
if (!VarVal)
299+
return VarVal.takeError();
300+
301+
std::string Result;
302+
Result.reserve(VarVal->size() + 2);
303+
raw_string_ostream OS(Result);
304+
305+
OS << '"';
306+
// Escape the string if it contains any characters that
307+
// make it hard to read, such as non-printable characters (including all
308+
// whitespace except space) and double quotes. These are the characters that
309+
// are escaped by write_escaped(), except we do not include backslashes,
310+
// because they are common in Windows paths and escaping them would make the
311+
// output harder to read. However, when we do escape, backslashes are escaped
312+
// as well, otherwise the output would be ambiguous.
313+
const bool NeedsEscaping =
314+
llvm::any_of(*VarVal, [](char C) { return !isPrint(C) || C == '"'; });
315+
if (NeedsEscaping)
316+
OS.write_escaped(*VarVal);
317+
else
318+
OS << *VarVal;
319+
OS << '"';
320+
if (NeedsEscaping)
321+
OS << " (escaped value)";
322+
323+
return Result;
324+
}
325+
285326
bool Pattern::isValidVarNameStart(char C) { return C == '_' || isAlpha(C); }
286327

287328
Expected<Pattern::VariableProperties>
@@ -1106,7 +1147,7 @@ Pattern::MatchResult Pattern::match(StringRef Buffer,
11061147
Error Errs = Error::success();
11071148
for (const auto &Substitution : Substitutions) {
11081149
// Substitute and check for failure (e.g. use of undefined variable).
1109-
Expected<std::string> Value = Substitution->getResult();
1150+
Expected<std::string> Value = Substitution->getResultRegex();
11101151
if (!Value) {
11111152
// Convert to an ErrorDiagnostic to get location information. This is
11121153
// done here rather than printMatch/printNoMatch since now we know which
@@ -1210,16 +1251,17 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
12101251
SmallString<256> Msg;
12111252
raw_svector_ostream OS(Msg);
12121253

1213-
Expected<std::string> MatchedValue = Substitution->getResult();
1254+
Expected<std::string> MatchedValue =
1255+
Substitution->getResultForDiagnostics();
12141256
// Substitution failures are handled in printNoMatch().
12151257
if (!MatchedValue) {
12161258
consumeError(MatchedValue.takeError());
12171259
continue;
12181260
}
12191261

12201262
OS << "with \"";
1221-
OS.write_escaped(Substitution->getFromString()) << "\" equal to \"";
1222-
OS.write_escaped(*MatchedValue) << "\"";
1263+
OS.write_escaped(Substitution->getFromString()) << "\" equal to ";
1264+
OS << *MatchedValue;
12231265

12241266
// We report only the start of the match/search range to suggest we are
12251267
// reporting the substitutions as set at the start of the match/search.

llvm/lib/FileCheck/FileCheckImpl.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,15 @@ class Substitution {
366366
/// \returns the index where the substitution is to be performed in RegExStr.
367367
size_t getIndex() const { return InsertIdx; }
368368

369+
/// \returns a regular expression string that matches the result of the
370+
/// substitution represented by this class instance or an error if
371+
/// substitution failed.
372+
virtual Expected<std::string> getResultRegex() const = 0;
373+
369374
/// \returns a string containing the result of the substitution represented
370-
/// by this class instance or an error if substitution failed.
371-
virtual Expected<std::string> getResult() const = 0;
375+
/// by this class instance in a form suitable for diagnostics, or an error if
376+
/// substitution failed.
377+
virtual Expected<std::string> getResultForDiagnostics() const = 0;
372378
};
373379

374380
class StringSubstitution : public Substitution {
@@ -379,7 +385,12 @@ class StringSubstitution : public Substitution {
379385

380386
/// \returns the text that the string variable in this substitution matched
381387
/// when defined, or an error if the variable is undefined.
382-
Expected<std::string> getResult() const override;
388+
Expected<std::string> getResultRegex() const override;
389+
390+
/// \returns the text that the string variable in this substitution matched
391+
/// when defined, in a form suitable for diagnostics, or an error if the
392+
/// variable is undefined.
393+
Expected<std::string> getResultForDiagnostics() const override;
383394
};
384395

385396
class NumericSubstitution : public Substitution {
@@ -397,7 +408,12 @@ class NumericSubstitution : public Substitution {
397408

398409
/// \returns a string containing the result of evaluating the expression in
399410
/// this substitution, or an error if evaluation failed.
400-
Expected<std::string> getResult() const override;
411+
Expected<std::string> getResultRegex() const override;
412+
413+
/// \returns a string containing the result of evaluating the expression in
414+
/// this substitution, in a form suitable for diagnostics, or an error if
415+
/// evaluation failed.
416+
Expected<std::string> getResultForDiagnostics() const override;
401417
};
402418

403419
//===----------------------------------------------------------------------===//

llvm/test/FileCheck/var-escape.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ VARS-NEXT: [[WINPATH]] [[NOT_ESCAPED]] [[ESCAPED]] [[#$NUMERIC + 0]]
1414
; RUN: -dump-input=never --strict-whitespace --check-prefix=VARS --input-file=%t.1 %s 2>&1 \
1515
; RUN: | FileCheck %s
1616

17-
CHECK: with "WINPATH" equal to "A:\\\\windows\\\\style\\\\path"
18-
CHECK: with "NOT_ESCAPED" equal to "shouldn't be escaped \\[a-Z\\]\\\\\\+\\$"
19-
CHECK: with "ESCAPED" equal to "\\\\ \014\013 needs\to \"be\" escaped\\\000"
17+
CHECK: with "WINPATH" equal to "A:\windows\style\path"
18+
CHECK: with "NOT_ESCAPED" equal to "shouldn't be escaped [a-Z]\+$"
19+
CHECK: with "ESCAPED" equal to "\\ \014\013 needs\to \"be\" escaped\000" (escaped value)
2020
CHECK: with "$NUMERIC + 0" equal to "DEADBEEF"
2121

2222
; Test escaping of the name of a numeric substitution, which might contain

llvm/unittests/FileCheck/FileCheckTest.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ TEST_F(FileCheckTest, Substitution) {
14391439
// Substitution of an undefined string variable fails and error holds that
14401440
// variable's name.
14411441
StringSubstitution StringSubstitution(&Context, "VAR404", 42);
1442-
Expected<std::string> SubstValue = StringSubstitution.getResult();
1442+
Expected<std::string> SubstValue = StringSubstitution.getResultRegex();
14431443
expectUndefErrors({"VAR404"}, SubstValue.takeError());
14441444

14451445
// Numeric substitution blocks constituted of defined numeric variables are
@@ -1452,20 +1452,20 @@ TEST_F(FileCheckTest, Substitution) {
14521452
std::move(NVarUse), ExpressionFormat(ExpressionFormat::Kind::HexUpper));
14531453
NumericSubstitution SubstitutionN(&Context, "N", std::move(ExpressionN),
14541454
/*InsertIdx=*/30);
1455-
SubstValue = SubstitutionN.getResult();
1455+
SubstValue = SubstitutionN.getResultRegex();
14561456
ASSERT_THAT_EXPECTED(SubstValue, Succeeded());
14571457
EXPECT_EQ("A", *SubstValue);
14581458

14591459
// Substitution of an undefined numeric variable fails, error holds name of
14601460
// undefined variable.
14611461
NVar.clearValue();
1462-
SubstValue = SubstitutionN.getResult();
1462+
SubstValue = SubstitutionN.getResultRegex();
14631463
expectUndefErrors({"N"}, SubstValue.takeError());
14641464

14651465
// Substitution of a defined string variable returns the right value.
14661466
Pattern P(Check::CheckPlain, &Context, 1);
14671467
StringSubstitution = llvm::StringSubstitution(&Context, "FOO", 42);
1468-
SubstValue = StringSubstitution.getResult();
1468+
SubstValue = StringSubstitution.getResultRegex();
14691469
ASSERT_THAT_EXPECTED(SubstValue, Succeeded());
14701470
EXPECT_EQ("BAR", *SubstValue);
14711471
}

0 commit comments

Comments
 (0)