[clang-tools-extra] [clangd] Fix SIGSEGV crash when receiving a textDocument/didOpen request with the URI pointing to a directory (PR #177834)

via cfe-commits cfe-commits at lists.llvm.org
Sat Jan 31 07:28:28 PST 2026


https://github.com/dhr412 updated https://github.com/llvm/llvm-project/pull/177834

>From 1422d023b254999b33bee9ca6667bb1a3166a131 Mon Sep 17 00:00:00 2001
From: Dhruv <dhruv.baweja4 at gmail.com>
Date: Sun, 25 Jan 2026 10:58:24 +0530
Subject: [PATCH 1/3] [clangd] Handle directory URIs in didOpen and
 documentSymbol

---
 clang-tools-extra/clangd/ClangdLSPServer.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 761b07eceec83..58b130d185a4e 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -33,6 +33,7 @@
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/SHA1.h"
@@ -730,6 +731,9 @@ void ClangdLSPServer::onSync(const NoParams &, Callback<std::nullptr_t> Reply) {
 void ClangdLSPServer::onDocumentDidOpen(
     const DidOpenTextDocumentParams &Params) {
   PathRef File = Params.textDocument.uri.file();
+  if (llvm::sys::fs::is_directory(File)) {
+    return;
+  }
 
   const std::string &Contents = Params.textDocument.text;
 
@@ -1027,6 +1031,10 @@ flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols,
 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
                                        Callback<llvm::json::Value> Reply) {
   URIForFile FileURI = Params.textDocument.uri;
+  if (llvm::sys::fs::is_directory(FileURI.file())) {
+    return Reply(llvm::make_error<LSPError>("URI is a directory",
+                                            ErrorCode::InvalidParams));
+  }
   Server->documentSymbols(
       Params.textDocument.uri.file(),
       [this, FileURI, Reply = std::move(Reply)](

>From 5fa8dbdd6fc3b246b9d6bab03c8d3531443be6f6 Mon Sep 17 00:00:00 2001
From: Dhruv <dhruv.baweja4 at gmail.com>
Date: Sun, 25 Jan 2026 21:50:34 +0530
Subject: [PATCH 2/3] Update formatting to match coding standards

---
 clang-tools-extra/clangd/ClangdLSPServer.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 58b130d185a4e..6f70ec3690ebd 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -731,9 +731,8 @@ void ClangdLSPServer::onSync(const NoParams &, Callback<std::nullptr_t> Reply) {
 void ClangdLSPServer::onDocumentDidOpen(
     const DidOpenTextDocumentParams &Params) {
   PathRef File = Params.textDocument.uri.file();
-  if (llvm::sys::fs::is_directory(File)) {
+  if (llvm::sys::fs::is_directory(File))
     return;
-  }
 
   const std::string &Contents = Params.textDocument.text;
 
@@ -1031,10 +1030,9 @@ flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols,
 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
                                        Callback<llvm::json::Value> Reply) {
   URIForFile FileURI = Params.textDocument.uri;
-  if (llvm::sys::fs::is_directory(FileURI.file())) {
+  if (llvm::sys::fs::is_directory(FileURI.file()))
     return Reply(llvm::make_error<LSPError>("URI is a directory",
                                             ErrorCode::InvalidParams));
-  }
   Server->documentSymbols(
       Params.textDocument.uri.file(),
       [this, FileURI, Reply = std::move(Reply)](

>From d5b709977be0e8ea5fb6e45ab19d065890a5f31c Mon Sep 17 00:00:00 2001
From: Dhruv <dhruv.baweja4 at gmail.com>
Date: Sat, 31 Jan 2026 20:58:11 +0530
Subject: [PATCH 3/3] Add err logging & lit test

---
 clang-tools-extra/clangd/ClangdLSPServer.cpp  |  7 ++--
 .../clangd/test/directory-uri.test            | 42 +++++++++++++++++++
 2 files changed, 45 insertions(+), 4 deletions(-)
 create mode 100644 clang-tools-extra/clangd/test/directory-uri.test

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 6f70ec3690ebd..5f95c3580f4ab 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -731,8 +731,10 @@ void ClangdLSPServer::onSync(const NoParams &, Callback<std::nullptr_t> Reply) {
 void ClangdLSPServer::onDocumentDidOpen(
     const DidOpenTextDocumentParams &Params) {
   PathRef File = Params.textDocument.uri.file();
-  if (llvm::sys::fs::is_directory(File))
+  if (llvm::sys::fs::is_directory(File)) {
+    elog("Ignoring didOpen for directory: {0}", File);
     return;
+  }
 
   const std::string &Contents = Params.textDocument.text;
 
@@ -1030,9 +1032,6 @@ flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols,
 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
                                        Callback<llvm::json::Value> Reply) {
   URIForFile FileURI = Params.textDocument.uri;
-  if (llvm::sys::fs::is_directory(FileURI.file()))
-    return Reply(llvm::make_error<LSPError>("URI is a directory",
-                                            ErrorCode::InvalidParams));
   Server->documentSymbols(
       Params.textDocument.uri.file(),
       [this, FileURI, Reply = std::move(Reply)](
diff --git a/clang-tools-extra/clangd/test/directory-uri.test b/clang-tools-extra/clangd/test/directory-uri.test
new file mode 100644
index 0000000000000..27b27a1f27d78
--- /dev/null
+++ b/clang-tools-extra/clangd/test/directory-uri.test
@@ -0,0 +1,42 @@
+# RUN: not --crash clangd -lit-test < %s 2> %t.err
+# RUN: FileCheck %s < %t.err
+# RUN: not --crash clangd -lit-test -sync=0 < %s 2> %t.async.err
+# RUN: FileCheck %s < %t.async.err
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
+  "uri":"file:///",
+  "languageId":"c",
+  "version":1,
+  "text":""
+}}}
+#      CHECK: I[{{.*}}] Ignoring didOpen for directory: /
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
+  "uri":"file:///tmp",
+  "languageId":"cpp",
+  "version":1,
+  "text":"int main() {}"
+}}}
+#      CHECK: I[{{.*}}] Ignoring didOpen for directory: /tmp
+---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"file:///"}}}
+#      CHECK: "error": {
+# CHECK-NEXT:   "code": -32602,
+# CHECK-NEXT:   "message": "URI is a directory"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0"
+---
+{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"file:///tmp"}}}
+#      CHECK: "error": {
+# CHECK-NEXT:   "code": -32602,
+# CHECK-NEXT:   "message": "URI is a directory"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0"
+---
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}



More information about the cfe-commits mailing list