blob: 98119c090d866ba74ac2f6a9da08a8850998db58 [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Unit tests for cir::CIRDiagnosticHandler.
//
// The handler routes MLIR-side diagnostics emitted during CIR passes, the
// verifier, and CIR-to-LLVM lowering through clang::DiagnosticsEngine. These
// tests exercise severity mapping, location translation, note attachment, and
// edge cases (missing files, fused/callsite/name locations, 0-line/0-col)
// without depending on a particular pass-side trigger.
//
//===----------------------------------------------------------------------===//
#include "../../lib/CIR/FrontendAction/CIRDiagnosticHandler.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/MLIRContext.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticCodeGen.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gtest/gtest.h"
// Don't pull `using namespace clang;` or `using namespace mlir;` here:
// `clang::Diagnostic` and `mlir::Diagnostic` would clash.
namespace {
struct CapturedDiag {
clang::DiagnosticsEngine::Level Level;
unsigned ID;
std::string Message;
bool HasLoc;
unsigned Line = 0;
unsigned Column = 0;
};
class CapturingConsumer : public clang::DiagnosticConsumer {
public:
std::vector<CapturedDiag> Diags;
void HandleDiagnostic(clang::DiagnosticsEngine::Level Level,
const clang::Diagnostic &Info) override {
clang::DiagnosticConsumer::HandleDiagnostic(Level, Info);
CapturedDiag CD;
CD.Level = Level;
CD.ID = Info.getID();
llvm::SmallString<128> Buf;
Info.FormatDiagnostic(Buf);
CD.Message = std::string(Buf);
clang::SourceLocation Loc = Info.getLocation();
CD.HasLoc = Loc.isValid();
if (CD.HasLoc && Info.hasSourceManager()) {
clang::PresumedLoc PL = Info.getSourceManager().getPresumedLoc(Loc);
if (PL.isValid()) {
CD.Line = PL.getLine();
CD.Column = PL.getColumn();
}
}
Diags.push_back(std::move(CD));
}
};
class CIRDiagnosticHandlerTest : public ::testing::Test {
protected:
CIRDiagnosticHandlerTest()
: Consumer(new CapturingConsumer), FileMgr(FileMgrOpts),
Diags(clang::DiagnosticIDs::create(), DiagOpts, Consumer.get(),
/*ShouldOwnClient=*/false),
SrcMgr(Diags, FileMgr) {
// Register a virtual file so FileManager::getOptionalFileRef succeeds and
// SourceManager has a buffer for line/col translation.
constexpr llvm::StringLiteral Body =
"line 1\nline 2\nline 3\nline 4\nline 5\n";
auto Buf = llvm::MemoryBuffer::getMemBufferCopy(Body, srcName());
clang::FileEntryRef File =
FileMgr.getVirtualFileRef(srcName(), Buf->getBufferSize(), 0);
SrcMgr.overrideFileContents(File, std::move(Buf));
clang::FileID FID = SrcMgr.getOrCreateFileID(File, clang::SrcMgr::C_User);
SrcMgr.setMainFileID(FID);
}
static llvm::StringRef srcName() { return "/virtual/file.c"; }
mlir::Location fileLoc(unsigned Line, unsigned Col,
llvm::StringRef Path = srcName()) {
return mlir::FileLineColLoc::get(&MLIRCtx, Path, Line, Col);
}
std::unique_ptr<CapturingConsumer> Consumer;
clang::FileSystemOptions FileMgrOpts;
clang::FileManager FileMgr;
clang::DiagnosticOptions DiagOpts;
clang::DiagnosticsEngine Diags;
clang::SourceManager SrcMgr;
mlir::MLIRContext MLIRCtx{mlir::MLIRContext::Threading::DISABLED};
};
TEST_F(CIRDiagnosticHandlerTest, ErrorRoutedWithSeverityAndLocation) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitError(fileLoc(2, 3)) << "boom";
ASSERT_EQ(Consumer->Diags.size(), 1u);
const CapturedDiag &D = Consumer->Diags.front();
EXPECT_EQ(D.Level, clang::DiagnosticsEngine::Error);
EXPECT_EQ(D.ID, static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic));
EXPECT_EQ(D.Message, "boom");
EXPECT_TRUE(D.HasLoc);
EXPECT_EQ(D.Line, 2u);
EXPECT_EQ(D.Column, 3u);
}
TEST_F(CIRDiagnosticHandlerTest, WarningRoutesToWarningId) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitWarning(fileLoc(1, 1)) << "soft";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Warning);
EXPECT_EQ(Consumer->Diags[0].ID,
static_cast<unsigned>(clang::diag::warn_cir_mlir_diagnostic));
}
TEST_F(CIRDiagnosticHandlerTest, RemarkRoutesToRemarkId) {
// Remarks are off by default. Promote -Rclangir so the consumer sees them.
bool unknownGroup =
Diags.setSeverityForGroup(clang::diag::Flavor::Remark, "remark-clangir",
clang::diag::Severity::Remark);
ASSERT_FALSE(unknownGroup) << "remark-clangir group not registered";
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitRemark(fileLoc(1, 1)) << "fyi";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Remark);
EXPECT_EQ(Consumer->Diags[0].ID,
static_cast<unsigned>(clang::diag::remark_cir_mlir_diagnostic));
}
TEST_F(CIRDiagnosticHandlerTest, NotesAttachAfterPrimary) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
{
mlir::InFlightDiagnostic d = mlir::emitError(fileLoc(3, 4)) << "primary";
d.attachNote(fileLoc(4, 5)) << "extra info";
} // InFlightDiagnostic dtor reports.
ASSERT_EQ(Consumer->Diags.size(), 2u);
EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Error);
EXPECT_EQ(Consumer->Diags[0].ID,
static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic));
EXPECT_EQ(Consumer->Diags[1].Level, clang::DiagnosticsEngine::Note);
EXPECT_EQ(Consumer->Diags[1].ID,
static_cast<unsigned>(clang::diag::note_cir_mlir_diagnostic));
EXPECT_EQ(Consumer->Diags[1].Message, "extra info");
EXPECT_EQ(Consumer->Diags[1].Line, 4u);
EXPECT_EQ(Consumer->Diags[1].Column, 5u);
}
TEST_F(CIRDiagnosticHandlerTest, ZeroLineColumnFallsBackToInvalidLoc) {
// Module-level locations use line 0 / column 0; SourceManager would assert.
// The handler must guard and emit without a source location.
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitError(fileLoc(0, 0)) << "module-level";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_FALSE(Consumer->Diags[0].HasLoc);
EXPECT_EQ(Consumer->Diags[0].Message, "module-level");
}
TEST_F(CIRDiagnosticHandlerTest, FileNotInManagerKeepsLocationAsText) {
// A file the SourceManager does not know cannot be attached to the
// diagnostic, so the handler prefixes the message with file:line:col.
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitError(fileLoc(1, 1, "/not/registered.c")) << "stray";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_FALSE(Consumer->Diags[0].HasLoc);
EXPECT_EQ(Consumer->Diags[0].Message, "/not/registered.c:1:1: stray");
}
TEST_F(CIRDiagnosticHandlerTest, FusedLocResolvesToFirstTranslatableChild) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::Location unknown = mlir::UnknownLoc::get(&MLIRCtx);
mlir::Location good = fileLoc(2, 1);
mlir::Location fused = mlir::FusedLoc::get({unknown, good}, {}, &MLIRCtx);
mlir::emitError(fused) << "fused";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_TRUE(Consumer->Diags[0].HasLoc);
EXPECT_EQ(Consumer->Diags[0].Line, 2u);
}
TEST_F(CIRDiagnosticHandlerTest, CallSiteLocFollowsCallee) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::Location callee = fileLoc(3, 2);
mlir::Location caller = fileLoc(5, 1);
mlir::Location cs = mlir::CallSiteLoc::get(callee, caller);
mlir::emitError(cs) << "cs";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_EQ(Consumer->Diags[0].Line, 3u);
EXPECT_EQ(Consumer->Diags[0].Column, 2u);
}
TEST_F(CIRDiagnosticHandlerTest, NameLocFollowsChild) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::Location child = fileLoc(4, 1);
mlir::Location named =
mlir::NameLoc::get(mlir::StringAttr::get(&MLIRCtx, "tag"), child);
mlir::emitError(named) << "named";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_EQ(Consumer->Diags[0].Line, 4u);
}
TEST_F(CIRDiagnosticHandlerTest, UnknownLocEmitsUnattached) {
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitError(mlir::UnknownLoc::get(&MLIRCtx)) << "lost";
ASSERT_EQ(Consumer->Diags.size(), 1u);
EXPECT_FALSE(Consumer->Diags[0].HasLoc);
}
TEST_F(CIRDiagnosticHandlerTest, ScopedLifetimeUnregistersOnDestruction) {
// After the handler scope ends, MLIR's default handler takes over.
// Verify the consumer no longer receives diagnostics through our path.
{
cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
mlir::emitError(fileLoc(1, 1)) << "in-scope";
}
size_t scopedCount = Consumer->Diags.size();
mlir::emitError(fileLoc(1, 1)) << "out-of-scope";
// Default handler prints to stderr but does not call our consumer.
EXPECT_EQ(Consumer->Diags.size(), scopedCount);
}
} // namespace