Skip to content

Commit 37e13d4

Browse files
authored
[mlir-lsp] Log invalid notification params (#89856)
When the `lsp::MessageHandler` processes a request with invalid params (that is, the "params" JSON sent along with the request does not match the shape expected by the message handler for the given method), it replies by sending an error response to the client. On the other hand, the language server protocol specifies that notifications must not result in responses. As a result, when the JSON params accompanying a notification cannot be parsed, no error is sent back; there is no indication that an error has occurred at all. This patch adds an error log for that case. Although clients cannot parse error logs, this at least provides an indication that something went wrong on the language server side.
1 parent 5779483 commit 37e13d4

File tree

2 files changed

+90
-28
lines changed

2 files changed

+90
-28
lines changed

mlir/include/mlir/Tools/lsp-server-support/Transport.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,15 @@ class MessageHandler {
147147
void (ThisT::*handler)(const Param &)) {
148148
notificationHandlers[method] = [method, handler,
149149
thisPtr](llvm::json::Value rawParams) {
150-
llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
151-
if (!param)
152-
return llvm::consumeError(param.takeError());
150+
llvm::Expected<Param> param =
151+
parse<Param>(rawParams, method, "notification");
152+
if (!param) {
153+
return llvm::consumeError(
154+
llvm::handleErrors(param.takeError(), [](const LSPError &lspError) {
155+
Logger::error("JSON parsing error: {0}",
156+
lspError.message.c_str());
157+
}));
158+
}
153159
(thisPtr->*handler)(*param);
154160
};
155161
}

mlir/unittests/Tools/lsp-server-support/Transport.cpp

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "mlir/Tools/lsp-server-support/Transport.h"
10-
#include "llvm/ADT/ScopeExit.h"
10+
#include "mlir/Tools/lsp-server-support/Logging.h"
11+
#include "mlir/Tools/lsp-server-support/Protocol.h"
1112
#include "llvm/Support/FileSystem.h"
1213
#include "gmock/gmock.h"
1314
#include "gtest/gtest.h"
@@ -29,37 +30,92 @@ TEST(TransportTest, SendReply) {
2930
EXPECT_THAT(out, HasSubstr("\"result\":null"));
3031
}
3132

32-
TEST(TransportTest, MethodNotFound) {
33-
auto tempOr = llvm::sys::fs::TempFile::create("lsp-unittest-%%%%%%.json");
34-
ASSERT_TRUE((bool)tempOr);
35-
auto discardTemp =
36-
llvm::make_scope_exit([&]() { ASSERT_FALSE((bool)tempOr->discard()); });
33+
class TransportInputTest : public Test {
34+
std::optional<llvm::sys::fs::TempFile> inputTempFile;
35+
std::FILE *in = nullptr;
36+
std::string output = "";
37+
llvm::raw_string_ostream os;
38+
std::optional<JSONTransport> transport = std::nullopt;
39+
std::optional<MessageHandler> messageHandler = std::nullopt;
3740

38-
{
41+
protected:
42+
TransportInputTest() : os(output) {}
43+
44+
void SetUp() override {
45+
auto tempOr = llvm::sys::fs::TempFile::create("lsp-unittest-%%%%%%.json");
46+
ASSERT_TRUE((bool)tempOr);
47+
llvm::sys::fs::TempFile t = std::move(*tempOr);
48+
inputTempFile = std::move(t);
49+
50+
in = std::fopen(inputTempFile->TmpName.c_str(), "r");
51+
transport.emplace(in, os, JSONStreamStyle::Delimited);
52+
messageHandler.emplace(*transport);
53+
}
54+
55+
void TearDown() override {
56+
EXPECT_FALSE(inputTempFile->discard());
57+
EXPECT_EQ(std::fclose(in), 0);
58+
}
59+
60+
void writeInput(StringRef buffer) {
3961
std::error_code ec;
40-
llvm::raw_fd_ostream os(tempOr->TmpName, ec);
62+
llvm::raw_fd_ostream os(inputTempFile->TmpName, ec);
4163
ASSERT_FALSE(ec);
42-
os << "{\"jsonrpc\":\"2.0\",\"id\":29,\"method\":\"ack\"}\n";
64+
os << buffer;
4365
os.close();
4466
}
4567

46-
std::string out;
47-
llvm::raw_string_ostream os(out);
48-
std::FILE *in = std::fopen(tempOr->TmpName.c_str(), "r");
49-
auto closeIn = llvm::make_scope_exit([&]() { std::fclose(in); });
68+
StringRef getOutput() const { return output; }
69+
MessageHandler &getMessageHandler() { return *messageHandler; }
5070

51-
JSONTransport transport(in, os, JSONStreamStyle::Delimited);
52-
MessageHandler handler(transport);
71+
void runTransport() {
72+
bool gotEOF = false;
73+
llvm::Error err = llvm::handleErrors(
74+
transport->run(*messageHandler), [&](const llvm::ECError &ecErr) {
75+
gotEOF = ecErr.convertToErrorCode() == std::errc::io_error;
76+
});
77+
llvm::consumeError(std::move(err));
78+
EXPECT_TRUE(gotEOF);
79+
}
80+
};
81+
82+
TEST_F(TransportInputTest, RequestWithInvalidParams) {
83+
struct Handler {
84+
void onMethod(const TextDocumentItem &params,
85+
mlir::lsp::Callback<TextDocumentIdentifier> callback) {}
86+
} handler;
87+
getMessageHandler().method("invalid-params-request", &handler,
88+
&Handler::onMethod);
89+
90+
writeInput("{\"jsonrpc\":\"2.0\",\"id\":92,"
91+
"\"method\":\"invalid-params-request\",\"params\":{}}\n");
92+
runTransport();
93+
EXPECT_THAT(getOutput(), HasSubstr("error"));
94+
EXPECT_THAT(getOutput(), HasSubstr("missing value at (root).uri"));
95+
}
96+
97+
TEST_F(TransportInputTest, NotificationWithInvalidParams) {
98+
// JSON parsing errors are only reported via error logging. As a result, this
99+
// test can't make any expectations -- but it prints the output anyway, by way
100+
// of demonstration.
101+
Logger::setLogLevel(Logger::Level::Error);
102+
103+
struct Handler {
104+
void onNotification(const TextDocumentItem &params) {}
105+
} handler;
106+
getMessageHandler().notification("invalid-params-notification", &handler,
107+
&Handler::onNotification);
108+
109+
writeInput("{\"jsonrpc\":\"2.0\",\"method\":\"invalid-params-notification\","
110+
"\"params\":{}}\n");
111+
runTransport();
112+
}
53113

54-
bool gotEOF = false;
55-
llvm::Error err = llvm::handleErrors(
56-
transport.run(handler), [&](const llvm::ECError &ecErr) {
57-
gotEOF = ecErr.convertToErrorCode() == std::errc::io_error;
58-
});
59-
llvm::consumeError(std::move(err));
60-
EXPECT_TRUE(gotEOF);
61-
EXPECT_THAT(out, HasSubstr("\"id\":29"));
62-
EXPECT_THAT(out, HasSubstr("\"error\""));
63-
EXPECT_THAT(out, HasSubstr("\"message\":\"method not found: ack\""));
114+
TEST_F(TransportInputTest, MethodNotFound) {
115+
writeInput("{\"jsonrpc\":\"2.0\",\"id\":29,\"method\":\"ack\"}\n");
116+
runTransport();
117+
EXPECT_THAT(getOutput(), HasSubstr("\"id\":29"));
118+
EXPECT_THAT(getOutput(), HasSubstr("\"error\""));
119+
EXPECT_THAT(getOutput(), HasSubstr("\"message\":\"method not found: ack\""));
64120
}
65121
} // namespace

0 commit comments

Comments
 (0)