https://github.com/arichardson updated https://github.com/llvm/llvm-project/pull/227570
>From f252ad664d3587daba5197bd4953ebd68aa2885c Mon Sep 17 00:00:00 2001 From: Alex Richardson <[email protected]> Date: Sun, 18 Oct 2026 12:40:42 -0700 Subject: [PATCH] [nspr] initial commit --- llvm/docs/CommandGuide/llvm-mc.md | 13 +++++-- .../Support/SourceMgrDiagnosticVerifier.h | 36 +++++++++++-------- .../Support/SourceMgrDiagnosticVerifier.cpp | 35 +++++++++++++----- llvm/test/tools/llvm-mc/verify/basic.s | 28 ++++++++++++++- .../SourceMgrDiagnosticVerifierTest.cpp | 24 +++++++++++-- mlir/lib/IR/Diagnostics.cpp | 3 +- 6 files changed, 107 insertions(+), 32 deletions(-) diff --git a/llvm/docs/CommandGuide/llvm-mc.md b/llvm/docs/CommandGuide/llvm-mc.md index 1ab1c1dbf1b12..037eb57393ce5 100644 --- a/llvm/docs/CommandGuide/llvm-mc.md +++ b/llvm/docs/CommandGuide/llvm-mc.md @@ -168,23 +168,30 @@ comma-separated list of prefixes to recognize; a bare `--verify` (with no A directive has the form: ```text -<prefix>-<kind>[-re][@<offset>] {{<text>}} +<prefix>-<kind>[-re][@<offset>][:<col>] {{<text>}} ``` `<kind>` is one of `error`, `warning`, `note` or `remark`. Without an `@<offset>`, the directive applies to its own line; `@+N`/`@-N` apply to the line `N` lines below/above, `@above`/`@below` apply to the nearest non-directive line above/below, and `@unknown` matches -diagnostics reported without a source location. By default `<text>` is matched as a substring of +diagnostics reported without a source location. An optional `:<col>` (matching +the column shown in the tool's own `file:line:col:` diagnostic output) +additionally requires the diagnostic to be at that exact column; without it, +only the line is checked. By default `<text>` is matched as a substring of the diagnostic's message; with the `-re` suffix, any `{{...}}` block nested inside `<text>` is matched as a regular expression instead (the rest of `<text>` is matched literally), for example: ```asm -## Matches only diagnostics containing this exact text. +## Matches only diagnostics containing this exact text, on this line. .foo # expected-error@-1 {{unknown directive}} +## Also requires the diagnostic to be at column 1. +.foo +# expected-error@-1:1 {{unknown directive}} + ## Matches any diagnostic starting with "unknown" and ending with ## "directive"; only the nested {{.*}} is a regex, the surrounding text ## is literal. diff --git a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h index 9252b926eabb6..ba89052447011 100644 --- a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h +++ b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h @@ -16,10 +16,6 @@ #ifndef LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H #define LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H -#include <optional> -#include <string> -#include <vector> - #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" @@ -27,6 +23,9 @@ #include "llvm/Support/Regex.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/raw_ostream.h" +#include <optional> +#include <string> +#include <vector> namespace llvm { @@ -75,22 +74,26 @@ class LLVM_ABI SourceMgrDiagnosticVerifier { ArrayRef<std::string> CommentPrefixes = {}); /// Matches a single actual diagnostic against the expected diagnostics - /// recorded for \p Buf / \p LineNo, scanning \p Buf first if it hasn't been - /// scanned yet. If \p HasLoc is false, the diagnostic has no location and is - /// matched against the '@unknown' list instead (\p Buf / \p LineNo are - /// ignored). If \p HasLoc is true but \p Buf is null (e.g. the diagnostic's - /// file isn't a known buffer), the diagnostic is matched against an empty - /// list, i.e. it can never match and is always unexpected. On a near miss, - /// prints a message to \c OS. \p ReportUnexpected controls whether near - /// misses / unexpected diagnostics are reported at all. + /// recorded for \p Buf / \p LineNo / \p ColNo, scanning \p Buf first if it + /// hasn't been scanned yet. If \p HasLoc is false, the diagnostic has no + /// location and is matched against the '@unknown' list instead (\p Buf / + /// \p LineNo / \p ColNo are ignored). If \p HasLoc is true but \p Buf is + /// null (e.g. the diagnostic's file isn't a known buffer), the diagnostic + /// is matched against an empty list, i.e. it can never match and is always + /// unexpected. \p ColNo is only checked against expected diagnostics that + /// requested a column (via ':<col>'); others match on line alone regardless + /// of \p ColNo. On a near miss, prints a message to \c OS. \p + /// ReportUnexpected controls whether near misses / unexpected diagnostics + /// are reported at all. MatchResult process(SourceMgr::DiagKind Kind, bool HasLoc, - const MemoryBuffer *Buf, unsigned LineNo, + const MemoryBuffer *Buf, unsigned LineNo, unsigned ColNo, StringRef Message, bool ReportUnexpected = true); /// Convenience overload for diagnostics already packaged as an /// \c SMDiagnostic (e.g. from a \c SourceMgr diagnostic handler). Resolves - /// the source buffer and line from \p Diag, and if the diagnostic is - /// unexpected and \p ReportUnexpected is true, prints \p Diag to \c OS. + /// the source buffer and 1-based line/column from \p Diag, and if the + /// diagnostic is unexpected and \p ReportUnexpected is true, prints \p Diag + /// to \c OS. MatchResult process(const SMDiagnostic &Diag, bool ReportUnexpected = true); /// Reports (to \c OS) any expected diagnostic that was never matched by a @@ -112,6 +115,9 @@ class LLVM_ABI SourceMgrDiagnosticVerifier { SourceMgr::DiagKind Kind; /// The line number the expected diagnostic should be on. unsigned LineNo; + /// The column the expected diagnostic should be at, or 0 if the column + /// isn't checked (only the line is). + unsigned ColNo = 0; /// The location of the expected diagnostic within the input file. SMLoc FileLoc; /// A flag indicating if the expected diagnostic has been matched yet. diff --git a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp index 7efca8ce20d4c..f4bb90ddb5a5e 100644 --- a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp +++ b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp @@ -7,13 +7,11 @@ //===----------------------------------------------------------------------===// #include "llvm/Support/SourceMgrDiagnosticVerifier.h" - -#include <algorithm> - #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" +#include <algorithm> using namespace llvm; @@ -39,7 +37,7 @@ SourceMgrDiagnosticVerifier::SourceMgrDiagnosticVerifier( // clang-format off Expected = Regex("(^|[^[:alnum:]_-])(" + PrefixAlt + ")-(error|note|remark|warning)(-re)?" - " *(@([+-][0-9]+|above|below|unknown))?" + " *(@([+-][0-9]+|above|below|unknown))?(:([0-9]+))?" " *{{(.*)}}$"); // clang-format on } @@ -170,12 +168,19 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) { .Case("warning", SourceMgr::DK_Warning) .Case("remark", SourceMgr::DK_Remark) .Case("note", SourceMgr::DK_Note); - ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[7]); + ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[9]); // Check to see if this is a regex match, i.e. it includes the `-re`. if (!Matches[4].empty() && !computeRegex(Record)) continue; + // Parse an optional ':<col>' column requirement. + if (!Matches[8].empty() && + (Matches[8].getAsInteger(10, Record.ColNo) || Record.ColNo == 0)) { + emitError(Record, "invalid column '" + Matches[7] + "'"); + continue; + } + StringRef OffsetMatch = Matches[5]; if (!OffsetMatch.empty()) { OffsetMatch = OffsetMatch.drop_front(1); @@ -207,6 +212,12 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) { Record.LineNo -= Offset; } } else if (OffsetMatch.consume_front("unknown")) { + // An '@unknown' diagnostic has no source location, so it cannot + // also require a column. + if (Record.ColNo != 0) { + emitError(Record, "cannot specify a column with '@unknown'"); + continue; + } // This is matching unknown locations. Record.FileLoc = SMLoc(); ExpectedUnknownLocDiags.emplace_back(std::move(Record)); @@ -231,10 +242,9 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) { return ExpectedDiags; } -SourceMgrDiagnosticVerifier::MatchResult -SourceMgrDiagnosticVerifier::process(SourceMgr::DiagKind Kind, bool HasLoc, - const MemoryBuffer *Buf, unsigned LineNo, - StringRef Message, bool ReportUnexpected) { +SourceMgrDiagnosticVerifier::MatchResult SourceMgrDiagnosticVerifier::process( + SourceMgr::DiagKind Kind, bool HasLoc, const MemoryBuffer *Buf, + unsigned LineNo, unsigned ColNo, StringRef Message, bool ReportUnexpected) { MutableArrayRef<ExpectedDiag> Diags; if (HasLoc) { // If the buffer couldn't be resolved, `Diags` stays empty: a diagnostic @@ -258,6 +268,10 @@ SourceMgrDiagnosticVerifier::process(SourceMgr::DiagKind Kind, bool HasLoc, // File line must match (unless it's an unknown location). if (HasLoc && E.LineNo != LineNo) continue; + // Column is only checked against expected diagnostics that requested one + // (via ':<col>'); others match on line alone. + if (HasLoc && E.ColNo != 0 && E.ColNo != ColNo) + continue; if (!E.match(Message)) continue; if (E.Kind == Kind) { @@ -300,9 +314,12 @@ SourceMgrDiagnosticVerifier::process(const SMDiagnostic &Diag, if (unsigned ID = Mgr.FindBufferContainingLoc(Diag.getLoc())) Buf = Mgr.getMemoryBuffer(ID); } + // SMDiagnostic::getColumnNo() is 0-based; -verify directives are written + // against the printed (1-based) column, matching "file:line:col:" output. MatchResult Result = process(Diag.getKind(), HasLoc, Buf, HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0, + HasLoc ? static_cast<unsigned>(Diag.getColumnNo()) + 1 : 0, Diag.getMessage(), ReportUnexpected); if (Result == MatchResult::Unexpected) printDiag(Diag); diff --git a/llvm/test/tools/llvm-mc/verify/basic.s b/llvm/test/tools/llvm-mc/verify/basic.s index eb3e6236207ce..ea6a08717b9e4 100644 --- a/llvm/test/tools/llvm-mc/verify/basic.s +++ b/llvm/test/tools/llvm-mc/verify/basic.s @@ -5,9 +5,18 @@ ## Matching expected-error directives: a plain substring match, a '-re' ## regex match (note the nested {{...}} for the regex portion itself), an -## '@+1' forward offset, and '@above'/'@below' offsets all pass. +## '@+1' forward offset, '@above'/'@below' offsets, a bare ':<col>' column +## check, and a combined '@offset:col' all pass. # RUN: llvm-mc -triple riscv32 -verify %t/match.s +## A directive with a column that doesn't match the actual diagnostic's +## column (or with an invalid column such as ':0' or '@unknown:<col>') fails +## verification. +# RUN: not llvm-mc -triple riscv32 -verify %t/wrong-column.s 2>&1 \ +# RUN: | FileCheck %t/wrong-column.s --check-prefix=WRONG-COLUMN +# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-column.s 2>&1 \ +# RUN: | FileCheck %t/invalid-column.s --check-prefix=INVALID-COLUMN + ## Malformed '-re' regex directives (missing closing '}}' or invalid regex ## syntax) and out-of-bounds line offsets fail verification with an error at ## the directive. @@ -79,6 +88,11 @@ # expected-error@below {{unknown directive}} .below_directive +.qux_directive # expected-error:1 {{unknown directive}} + +.quux_directive +# expected-error@-1:1 {{unknown directive}} + ## Multiple expected-note directives on the same line whose substrings both ## match the first emitted note ("unexpected extra operand for instruction") ## still match both notes because unmatched directives are preferred over @@ -88,6 +102,18 @@ sfence.vma a0, 0x10 # expected-note@-2 {{r}} # expected-note@-3 {{register must be a GPR}} +#--- wrong-column.s +.foo_directive # expected-error:99 {{unknown directive}} +# WRONG-COLUMN: error: unknown directive +# WRONG-COLUMN: expected error "unknown directive" was not produced + +#--- invalid-column.s +addi a0, a0, 1 +# expected-error@-1:0 {{never happens}} +# expected-error@unknown:1 {{never happens}} +# INVALID-COLUMN: error: invalid column ':0' +# INVALID-COLUMN: error: cannot specify a column with '@unknown' + #--- invalid-regex.s addi a0, a0, 1 # expected-error-re@-1 {{unclosed {{regex}} diff --git a/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp b/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp index 222ed92ce5df1..74835df8ab396 100644 --- a/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp +++ b/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp @@ -99,7 +99,7 @@ TEST_F(SourceMgrDiagnosticVerifierTest, NullBufferWithHasLocIsUnexpected) { SourceMgrDiagnosticVerifier Verifier(SM, OS); EXPECT_EQ(Verifier.process(SourceMgr::DK_Error, /*HasLoc=*/true, - /*Buf=*/nullptr, 1, "anything"), + /*Buf=*/nullptr, 1, 1, "anything"), MatchResult::Unexpected); EXPECT_FALSE(Verifier.verify()); } @@ -129,10 +129,11 @@ TEST_F(SourceMgrDiagnosticVerifierTest, LineZeroAndLeadingZeroOffsets) { SourceMgrDiagnosticVerifier Verifier(SM, OS); EXPECT_EQ(Verifier.process(SourceMgr::DK_Remark, /*HasLoc=*/true, Buf, - /*LineNo=*/0, "implicit module at line 0"), + /*LineNo=*/0, /*ColNo=*/0, + "implicit module at line 0"), MatchResult::Matched); EXPECT_EQ(Verifier.process(SourceMgr::DK_Error, /*HasLoc=*/true, Buf, - /*LineNo=*/3, "decimal leading zero"), + /*LineNo=*/3, /*ColNo=*/1, "decimal leading zero"), MatchResult::Matched); EXPECT_TRUE(Verifier.verify()); EXPECT_EQ(Output, ""); @@ -192,3 +193,20 @@ TEST_F(SourceMgrDiagnosticVerifierTest, CustomPrefixesAndCommentScoping) { EXPECT_TRUE(Verifier.verify()); EXPECT_EQ(Output, ""); } + +TEST_F(SourceMgrDiagnosticVerifierTest, ColumnChecking) { + const MemoryBuffer *Buf = addBuffer("bad_op arg1, arg2\n" + "# expected-error@-1:8 {{bad arg1}}\n"); + SourceMgrDiagnosticVerifier Verifier(SM, OS); + + // Column 8 ('arg1') matches; column 1 ('bad_op') does not. + SMLoc Col1Loc = SMLoc::getFromPointer(Buf->getBufferStart()); + SMLoc Col8Loc = SMLoc::getFromPointer(Buf->getBufferStart() + 7); + EXPECT_EQ( + Verifier.process(SM.GetMessage(Col1Loc, SourceMgr::DK_Error, "bad arg1")), + MatchResult::Unexpected); + EXPECT_EQ( + Verifier.process(SM.GetMessage(Col8Loc, SourceMgr::DK_Error, "bad arg1")), + MatchResult::Matched); + EXPECT_FALSE(Verifier.verify()); +} diff --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp index 5f1a86e1ed495..3855fc70d4db7 100644 --- a/mlir/lib/IR/Diagnostics.cpp +++ b/mlir/lib/IR/Diagnostics.cpp @@ -721,7 +721,8 @@ void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc, using MatchResult = llvm::SourceMgrDiagnosticVerifier::MatchResult; MatchResult result = impl->verifier.process( getDiagKind(kind), /*hasLoc=*/static_cast<bool>(fileLoc), buf, - fileLoc ? fileLoc.getLine() : 0, msg, reportUnexpected); + fileLoc ? fileLoc.getLine() : 0, fileLoc ? fileLoc.getColumn() : 0, msg, + reportUnexpected); if (result == MatchResult::Unexpected) emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg, DiagnosticSeverity::Error); _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
