Skip to content
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

added support for Pysa Model Validation in VSCode Extension #433

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6c72b5d
added rough code for model validation for .pysa files
m0mosenpai Apr 16, 2021
0ce48d2
untrack documentation files
m0mosenpai Apr 16, 2021
ea2b235
hardcoded path value for testing publishing diagnostics
m0mosenpai May 3, 2021
a09ff0b
fixed errors highlighted one line below, fixed .pysa files not being …
m0mosenpai May 17, 2021
6c3b0b5
fixed error validation only working at startup
m0mosenpai May 18, 2021
fa008b7
fixed formatting for example files
m0mosenpai May 18, 2021
3d282e8
fixed function arguments not conforming to enforced typing
m0mosenpai May 18, 2021
b5d9268
removed PysaServerHandler, refactored things into PysaServer class
m0mosenpai May 24, 2021
da094da
removed debug statements, fixed formatting
m0mosenpai May 24, 2021
b1e22b5
added try catch for Pyre Parsing errors
m0mosenpai May 29, 2021
79dc130
minor refactoring, minor fixes
m0mosenpai May 29, 2021
d63f8f5
undo changes in exercises
m0mosenpai May 29, 2021
c001ebb
undo pysa specific changes back to refactor only
m0mosenpai May 29, 2021
10b76f3
added support for model validation in Pysa VSCode Plugin
m0mosenpai May 29, 2021
955dd8b
fixed missing typing for some arguments
m0mosenpai Jun 1, 2021
2377ab6
rebased with small changes
m0mosenpai Jun 9, 2021
b32d099
handle path being None
m0mosenpai Jun 9, 2021
3793e1a
changed to relative imports
m0mosenpai Jul 13, 2021
3f3c5e0
updated error message for None path
m0mosenpai Jul 19, 2021
1357024
undo .pyre_configuration changes
m0mosenpai Jul 19, 2021
5114aa2
misc fixes
m0mosenpai Jul 20, 2021
5d3fa19
fixes in import statements
m0mosenpai Aug 10, 2021
84a37d0
revert import changes
m0mosenpai Aug 10, 2021
f51ee97
sort imports
m0mosenpai Aug 10, 2021
917f931
import changes
m0mosenpai Aug 10, 2021
52d5e28
Revert "import changes"
m0mosenpai Aug 11, 2021
7b964fe
prelim changes to clear out errors in all previous files
m0mosenpai Aug 31, 2021
9b0e2a6
fixes, removed logging comments
m0mosenpai Aug 31, 2021
8f64264
updated tracking published diagnostic files instead of open ones
m0mosenpai Aug 31, 2021
19cf7e5
misc fixes
m0mosenpai Sep 2, 2021
516a32c
fix change due to rebase
m0mosenpai Sep 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions client/commands/v2/pysa_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@

import asyncio
import logging
from collections import defaultdict
from pathlib import Path
from typing import List, Sequence, Dict, Set

from ....api import query, connection as api_connection
from ....api.connection import PyreQueryError
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid relying on things in the api package -- those are considered "external" APIs that are currently going through multiple redundant levels of indirections (e.g. shelling out to pyre executable, which may or may not match the trunk version). For v2 command, there's no need to shell out to anything: we could query the background Pyre server by invoking command-internal logic directly.

Querying the Pyre server in pure Python can be done with commands.v2.query.query_server:

from . import query

try:
  socket_path = Path(self.pyre_arguments.base_arguments.log_path)  # You probably need a rebase to get `base_arguments` to appear
  response_json = query.query_server(socket_path, "validate_taint_models()").payload
except query.InvalidQueryResponse:
  ...

Some followup logic may be needed to extract useful info from the response_json dict.

from ... import (
json_rpc,
command_arguments,
Expand All @@ -28,6 +33,7 @@
_read_lsp_request,
try_initialize,
_log_lsp_event,
_publish_diagnostics,
InitializationExit,
InitializationSuccess,
InitializationFailure,
Expand Down Expand Up @@ -59,6 +65,60 @@ def __init__(
self.pyre_arguments = pyre_arguments
self.binary_location = binary_location
self.server_identifier = server_identifier
self.pyre_connection = api_connection.PyreConnection(
Path(self.pyre_arguments.global_root)
)
self.file_tracker: Set[Path] = set()

def invalid_model_to_diagnostic(
self, invalid_model: query.InvalidModel
) -> lsp.Diagnostic:
return lsp.Diagnostic(
range=lsp.Range(
start=lsp.Position(
line=invalid_model.line - 1, character=invalid_model.column
),
end=lsp.Position(
line=invalid_model.stop_line - 1,
character=invalid_model.stop_column,
),
),
message=invalid_model.full_error_message,
severity=lsp.DiagnosticSeverity.ERROR,
code=None,
source="Pysa",
)

def invalid_models_to_diagnostics(
self, invalid_models: Sequence[query.InvalidModel]
) -> Dict[Path, List[lsp.Diagnostic]]:
result: Dict[Path, List[lsp.Diagnostic]] = defaultdict(list)
for model in invalid_models:
if model.path is None:
self.log_and_show_message_to_client(
f"{model.full_error_message}", lsp.MessageType.ERROR
)
else:
result[Path(model.path)].append(self.invalid_model_to_diagnostic(model))
return result

async def update_errors(self) -> None:
# Publishing empty diagnostics to clear errors in VSCode and reset self.file_tracker
for document_path in self.file_tracker:
await _publish_diagnostics(self.output_channel, document_path, [])
self.file_tracker.clear()

try:
model_errors = query.get_invalid_taint_models(self.pyre_connection)
diagnostics = self.invalid_models_to_diagnostics(model_errors)
# Keep track of files we publish diagnostics for
self.file_tracker.update(diagnostics.keys())

await self.show_model_errors_to_client(diagnostics)
except PyreQueryError as e:
await self.log_and_show_message_to_client(
f"Error querying Pyre: {e}", lsp.MessageType.WARNING
)

async def show_message_to_client(
self, message: str, level: lsp.MessageType = lsp.MessageType.INFO
Expand Down Expand Up @@ -86,6 +146,12 @@ async def log_and_show_message_to_client(
LOG.debug(message)
await self.show_message_to_client(message, level)

async def show_model_errors_to_client(
self, diagnostics: Dict[Path, List[lsp.Diagnostic]]
) -> None:
for path, diagnostic in diagnostics.items():
await _publish_diagnostics(self.output_channel, path, diagnostic or [])

async def wait_for_exit(self) -> int:
while True:
async with _read_lsp_request(
Expand All @@ -102,15 +168,18 @@ async def process_open_request(
self, parameters: lsp.DidOpenTextDocumentParameters
) -> None:
document_path = parameters.text_document.document_uri().to_file_path()

if document_path is None:
raise json_rpc.InvalidRequestError(
f"Document URI is not a file: {parameters.text_document.uri}"
)
await self.update_errors()

async def process_close_request(
self, parameters: lsp.DidCloseTextDocumentParameters
) -> None:
document_path = parameters.text_document.document_uri().to_file_path()

if document_path is None:
raise json_rpc.InvalidRequestError(
f"Document URI is not a file: {parameters.text_document.uri}"
Expand All @@ -129,6 +198,7 @@ async def process_did_save_request(
raise json_rpc.InvalidRequestError(
f"Document URI is not a file: {parameters.text_document.uri}"
)
await self.update_errors()

async def run(self) -> int:
while True:
Expand Down