-
Notifications
You must be signed in to change notification settings - Fork 12
Use LSP-over-legacy for hover #278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||||||||||||||||
| package com.jetbrains.lang.dart.analyzer; | ||||||||||||||||||
|
|
||||||||||||||||||
| import com.jetbrains.lang.dart.logging.PluginLogger; | ||||||||||||||||||
| import com.intellij.openapi.diagnostic.Logger; | ||||||||||||||||||
| import com.intellij.openapi.project.Project; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.LanguageServerFactory; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.client.features.LSPHoverFeature; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.client.features.LSPCompletionFeature; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.client.features.LSPDiagnosticFeature; | ||||||||||||||||||
| import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature; | ||||||||||||||||||
| import com.intellij.psi.PsiFile; | ||||||||||||||||||
| import org.jetbrains.annotations.NotNull; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.io.InputStream; | ||||||||||||||||||
| import java.io.OutputStream; | ||||||||||||||||||
| import java.io.PipedInputStream; | ||||||||||||||||||
| import java.io.PipedOutputStream; | ||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||
| import com.google.gson.JsonObject; | ||||||||||||||||||
| import com.google.gson.JsonParser; | ||||||||||||||||||
| import com.google.dart.server.ResponseListener; | ||||||||||||||||||
|
|
||||||||||||||||||
| public class DartLanguageServerFactory implements LanguageServerFactory { | ||||||||||||||||||
|
|
||||||||||||||||||
| private static final Logger LOG = PluginLogger.INSTANCE.createLogger(DartLanguageServerFactory.class); | ||||||||||||||||||
|
|
||||||||||||||||||
| public DartLanguageServerFactory() { | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public @NotNull StreamConnectionProvider createConnectionProvider(@NotNull Project project) { | ||||||||||||||||||
| return new StreamConnectionProvider() { | ||||||||||||||||||
| private PipedInputStream lspInputStream; // lsp4ij reads from here | ||||||||||||||||||
| private PipedOutputStream dartToLspStream; // we write Dart server responses here | ||||||||||||||||||
|
|
||||||||||||||||||
| private PipedInputStream lspToDartStream; // we read lsp4ij requests from here | ||||||||||||||||||
| private PipedOutputStream lspOutputStream; // lsp4ij writes to here | ||||||||||||||||||
|
|
||||||||||||||||||
| private Thread lspToDartThread; | ||||||||||||||||||
| private ResponseListener dartResponseListener; | ||||||||||||||||||
| private DartAnalysisServerService dasService; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| public void start() { | ||||||||||||||||||
| try { | ||||||||||||||||||
| // Setup streams | ||||||||||||||||||
| lspInputStream = new PipedInputStream(); | ||||||||||||||||||
| dartToLspStream = new PipedOutputStream(lspInputStream); | ||||||||||||||||||
|
|
||||||||||||||||||
| lspToDartStream = new PipedInputStream(); | ||||||||||||||||||
| lspOutputStream = new PipedOutputStream(lspToDartStream); | ||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||
| LOG.error("Failed to initialize piped streams for lsp4ij", e); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| dasService = DartAnalysisServerService.getInstance(project); | ||||||||||||||||||
|
|
||||||||||||||||||
| // 1. Listen for Dart server responses and unwraps them for lsp4ij | ||||||||||||||||||
| dartResponseListener = new ResponseListener() { | ||||||||||||||||||
| @Override | ||||||||||||||||||
| public void onResponse(String jsonString) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); | ||||||||||||||||||
| JsonObject lspPayload = null; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check if it is a response to our lsp.handle request | ||||||||||||||||||
| if (jsonObject.has("result") && jsonObject.get("result").isJsonObject()) { | ||||||||||||||||||
| JsonObject result = jsonObject.getAsJsonObject("result"); | ||||||||||||||||||
| if (result.has("lspResponse")) { | ||||||||||||||||||
| lspPayload = result.getAsJsonObject("lspResponse"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check if it is a notification or server-to-client request | ||||||||||||||||||
| if (lspPayload == null && jsonObject.has("params") && jsonObject.get("params").isJsonObject()) { | ||||||||||||||||||
| JsonObject params = jsonObject.getAsJsonObject("params"); | ||||||||||||||||||
| if (params.has("lspMessage")) { | ||||||||||||||||||
| lspPayload = params.getAsJsonObject("lspMessage"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (lspPayload != null) { | ||||||||||||||||||
| String lspMessageString = lspPayload.toString(); | ||||||||||||||||||
| LOG.info("Dart server sent lsp payload: " + lspMessageString); | ||||||||||||||||||
| // Wrap in LSP standard Content-Length headers | ||||||||||||||||||
| String message = "Content-Length: " + lspMessageString.getBytes(StandardCharsets.UTF_8).length | ||||||||||||||||||
| + "\r\n\r\n" + lspMessageString; | ||||||||||||||||||
| try { | ||||||||||||||||||
| dartToLspStream.write(message.getBytes(StandardCharsets.UTF_8)); | ||||||||||||||||||
| dartToLspStream.flush(); | ||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||
| LOG.warn("Failed to write to dartToLspStream", e); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||
| LOG.warn("Failed to parse lspToDartStream", e); | ||||||||||||||||||
| // Ignore parse errors from non-json or unrelated traffic | ||||||||||||||||||
| } | ||||||||||||||||||
|
||||||||||||||||||
| } catch (Exception e) { | |
| LOG.warn("Failed to parse lspToDartStream", e); | |
| // Ignore parse errors from non-json or unrelated traffic | |
| } | |
| } catch (com.google.gson.JsonParseException e) { | |
| LOG.warn("Failed to parse message from Dart Analysis Server", e); | |
| // Ignore parse errors from non-json or unrelated traffic | |
| } |
References
- This change improves maintainability by making error handling more specific, which aligns with the goal of reducing "'clever' code that is hard to read" and improving overall code quality. (link)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[MUST-FIX] The current implementation for parsing LSP messages is not robust. As noted in the code comment, it's a naive implementation that relies on checking for a closing brace and counting braces. This approach is brittle and can easily fail if:
- The JSON payload contains a
}character within a string value. - Multiple messages arrive in a single read, or a single message arrives in multiple reads.
A correct implementation must adhere to the Language Server Protocol specification by:
- Reading the
Content-Length: NNN\r\n\r\nheader. - Reading exactly
NNNbytes for the JSON content.
While this is a proof-of-concept, this is a fundamental correctness issue that should be addressed before this approach is built upon further.
References
- This is a logical bug that can lead to incorrect behavior and instability, which falls under the [MUST-FIX] severity category. (link)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The id field from an incoming JSON-RPC request is concatenated directly into a JSON response string without proper escaping or quoting, leading to a JSON injection vulnerability. An attacker could inject arbitrary keys into the JSON response, causing unexpected behavior. This issue is located within the start() method, which is also over 100 lines long, violating the repository style guide and making it difficult to read and maintain. Consider refactoring this method into smaller, focused private methods to improve readability and address the injection vulnerability.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application logs raw LSP payloads and messages at INFO and WARN levels, which can expose sensitive information like source code or file paths, leading to intellectual property leakage if logs are shared. This logging occurs within a section where a raw new Thread() is created. It's a best practice in IntelliJ Platform development to use managed thread pools via ApplicationManager.getApplication().executeOnPooledThread() for proper thread lifecycle and resource management, especially when handling potentially sensitive data streams.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CONCERN] Similar to the other comment, catching the generic Exception is too broad. Please catch a more specific exception, like com.google.gson.JsonParseException, to make the error handling more precise and avoid masking other potential issues.
| } catch (Exception e) { | |
| LOG.warn("Failed to parse lsp4ij message: " + jsonPart, e); | |
| } | |
| } catch (com.google.gson.JsonParseException e) { | |
| LOG.warn("Failed to parse lsp4ij message: " + jsonPart, e); | |
| } |
References
- This change improves maintainability by making error handling more specific, which aligns with the goal of reducing "'clever' code that is hard to read" and improving overall code quality. (link)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application logs raw LSP payloads and messages at
INFOandWARNlevels. LSP messages frequently contain sensitive information, including source code snippets, file paths, and symbol names from the project being analyzed. Logging this data can lead to the exposure of intellectual property or other sensitive information if the IDE logs are shared for debugging or support purposes.