source/server/query.ml (902 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
open Ast
open Analysis
open Pyre
exception InvalidQuery of string
exception IncorrectParameters of Type.t
module Request = struct
type t =
| Attributes of Reference.t
| Batch of t list
| Callees of Reference.t
| CalleesWithLocation of Reference.t
| Defines of Reference.t list
| DumpCallGraph
| Help of string
| InlineDecorators of {
function_reference: Reference.t;
decorators_to_skip: Reference.t list;
}
| IsCompatibleWith of Expression.t * Expression.t
| LessOrEqual of Expression.t * Expression.t
| LocationOfDefinition of {
path: PyrePath.t;
position: Location.position;
}
| ModulesOfPath of PyrePath.t
| PathOfModule of Reference.t
| SaveServerState of PyrePath.t
| Superclasses of Reference.t list
| Type of Expression.t
| TypesInFiles of string list
| ValidateTaintModels of string option
[@@deriving sexp, compare]
let inline_decorators ?(decorators_to_skip = []) function_reference =
InlineDecorators { function_reference; decorators_to_skip }
end
module Response = struct
module Base = struct
type attribute_kind =
| Regular
| Property
[@@deriving sexp, compare, to_yojson]
type attribute = {
name: string;
annotation: Type.t;
kind: attribute_kind;
final: bool;
}
[@@deriving sexp, compare, to_yojson]
type type_at_location = {
location: Location.t;
annotation: Type.t;
}
[@@deriving sexp, compare, to_yojson]
type types_at_path = {
path: string;
types: type_at_location list;
}
[@@deriving sexp, compare, to_yojson]
type compatibility = {
actual: Type.t;
expected: Type.t;
result: bool;
}
[@@deriving sexp, compare]
type callee_with_instantiated_locations = {
callee: Analysis.Callgraph.callee;
locations: Location.WithPath.t list;
}
[@@deriving sexp, compare]
type callees = {
caller: Reference.t;
callees: callee_with_instantiated_locations list;
}
[@@deriving sexp, compare]
type parameter_representation = {
parameter_name: string;
parameter_annotation: Expression.t option;
}
[@@deriving sexp, compare]
type define = {
define_name: Reference.t;
parameters: parameter_representation list;
return_annotation: Expression.t option;
}
[@@deriving sexp, compare]
type superclasses_mapping = {
class_name: Reference.t;
superclasses: Reference.t list;
}
[@@deriving sexp, compare, to_yojson]
type position = {
line: int;
character: int;
}
[@@deriving sexp, compare, to_yojson]
type range = {
start: position;
end_: position;
}
[@@deriving sexp, compare]
let range_to_yojson { start; end_ } =
`Assoc ["start", position_to_yojson start; "end", position_to_yojson end_]
type location_of_definition = {
path: string;
range: range;
}
[@@deriving sexp, compare, to_yojson]
type t =
| Boolean of bool
| Callees of Analysis.Callgraph.callee list
| CalleesWithLocation of callee_with_instantiated_locations list
| Callgraph of callees list
| Compatibility of compatibility
| Errors of Analysis.AnalysisError.Instantiated.t list
| FoundAttributes of attribute list
| FoundDefines of define list
| FoundLocationsOfDefinitions of location_of_definition list
| FoundModules of Reference.t list
| FoundPath of string
| FunctionDefinition of Statement.Define.t
| Help of string
| ModelVerificationErrors of Taint.ModelVerificationError.t list
| Success of string
| Superclasses of superclasses_mapping list
| Type of Type.t
| TypesByPath of types_at_path list
[@@deriving sexp, compare]
let to_yojson response =
let open Analysis in
match response with
| Boolean boolean -> `Assoc ["boolean", `Bool boolean]
| Callees callees ->
`Assoc ["callees", `List (List.map callees ~f:Callgraph.callee_to_yojson)]
| CalleesWithLocation callees ->
let callee_to_yojson { callee; locations } =
Callgraph.callee_to_yojson ~locations callee
in
`Assoc ["callees", `List (List.map callees ~f:callee_to_yojson)]
| Callgraph callees ->
let callee_to_yojson { callee; locations } =
Callgraph.callee_to_yojson ~locations callee
in
`Assoc
(List.map callees ~f:(fun { caller; callees } ->
Reference.show caller, `List (List.map callees ~f:callee_to_yojson)))
| Compatibility { actual; expected; result } ->
`Assoc
[
"actual", Type.to_yojson actual;
"expected", Type.to_yojson expected;
"boolean", `Bool result;
]
| Errors errors ->
`Assoc
[
( "errors",
`List (List.map ~f:(fun error -> AnalysisError.Instantiated.to_yojson error) errors)
);
]
| Help string -> `Assoc ["help", `String string]
| ModelVerificationErrors errors ->
`Assoc ["errors", `List (List.map errors ~f:Taint.ModelVerificationError.to_json)]
| FoundAttributes attributes ->
let attribute_to_yojson { name; annotation; kind; final } =
let kind =
match kind with
| Regular -> "regular"
| Property -> "property"
in
`Assoc
[
"name", `String name;
"annotation", Type.to_yojson annotation;
"kind", `String kind;
"final", `Bool final;
]
in
`Assoc ["attributes", `List (List.map attributes ~f:attribute_to_yojson)]
| FoundDefines defines ->
let define_to_yojson { define_name; parameters; return_annotation } =
let annotation_to_yojson = function
| None -> `Null
| Some annotation ->
Ast.Expression.sanitized annotation
|> Expression.show
|> fun annotation -> `String annotation
in
let parameter_representation_to_yojson { parameter_name; parameter_annotation } =
`Assoc
[
"name", `String parameter_name;
"annotation", annotation_to_yojson parameter_annotation;
]
in
`Assoc
[
"name", `String (Reference.show define_name);
"parameters", `List (List.map parameters ~f:parameter_representation_to_yojson);
"return_annotation", annotation_to_yojson return_annotation;
]
in
`List (List.map defines ~f:define_to_yojson)
| FoundLocationsOfDefinitions locations ->
`List (List.map locations ~f:location_of_definition_to_yojson)
| FoundModules references ->
let reference_to_yojson reference = `String (Reference.show reference) in
`List (List.map references ~f:reference_to_yojson)
| FoundPath path -> `Assoc ["path", `String path]
| FunctionDefinition define ->
`Assoc
[
( "definition",
`String
(Statement.show
(Statement.Statement.Define define |> Node.create_with_default_location)) );
]
| Success message -> `Assoc ["message", `String message]
| Superclasses class_to_superclasses_mapping ->
let reference_to_yojson reference = `String (Reference.show reference) in
let mapping_to_yojson { class_name; superclasses } =
`Assoc [Reference.show class_name, `List (List.map superclasses ~f:reference_to_yojson)]
in
`List (List.map class_to_superclasses_mapping ~f:mapping_to_yojson)
| Type annotation -> `Assoc ["type", Type.to_yojson annotation]
| TypesByPath paths_to_annotations ->
`List (List.map paths_to_annotations ~f:types_at_path_to_yojson)
end
type t =
| Single of Base.t
| Batch of t list
| Error of string
[@@deriving sexp, compare]
let rec to_yojson = function
| Single base_response -> `Assoc ["response", Base.to_yojson base_response]
| Batch responses -> `Assoc ["response", `List (List.map ~f:to_yojson responses)]
| Error message -> `Assoc ["error", `String message]
let create_type_at_location (location, annotation) = { Base.location; annotation }
end
let help () =
let open Request in
let open Expression in
let help = function
| Batch _ ->
Some
"batch(query1(arg), query2(arg)): Runs a batch of queries and returns a map of \
responses. List of given queries may include any combination of other valid queries \
except for `batch` itself."
| Attributes _ ->
Some
"attributes(class_name): Returns a list of attributes, including functions, for a class."
| Callees _ -> Some "callees(function): calls from a given function."
| CalleesWithLocation _ ->
Some
"callees_with_location(function): calls from a given function, including the locations \
at which they are called."
| Defines _ ->
Some
"defines(module_or_class_name): Returns a JSON with the signature of all defines for \
given module or class."
| DumpCallGraph ->
Some "dump_call_graph(): Returns a comprehensive JSON of caller -> list of callees."
| InlineDecorators _ ->
Some
"inline_decorators(qualified_function_name, optional decorators_to_skip=[decorator1, \
decorator2]): Shows the function definition after decorators have been inlined."
| IsCompatibleWith _ -> None
| LessOrEqual _ -> Some "less_or_equal(T1, T2): Returns whether T1 is a subtype of T2."
| LocationOfDefinition _ ->
Some
"location_of_definition(path='<absolute path>', line=<line>, character=<character>): \
Returns the location of the definition for the symbol at the given line and character."
| ModulesOfPath _ ->
Some "modules_of_path(path): Returns the modules of a file pointed to by path."
| PathOfModule _ -> Some "path_of_module(module): Gives an absolute path for `module`."
| SaveServerState _ ->
Some "save_server_state('path'): Saves Pyre's serialized state into `path`."
| Superclasses _ ->
Some
"superclasses(class_name1, class_name2, ...): Returns a mapping of class_name to the \
list of superclasses for `class_name`."
| Type _ -> Some "type(expression): Evaluates the type of `expression`."
| TypesInFiles _ ->
Some
"types(path='path') or types('path1', 'path2', ...): Returns a map from each given path \
to a list of all types for that path."
| ValidateTaintModels _ ->
Some
"validate_taint_models('optional path'): Validates models and returns errors. Defaults \
to model path in configuration if no parameter is passed in."
| Help _ -> None
in
let path = PyrePath.current_working_directory () in
let empty = Expression.Name (Name.Identifier "") |> Node.create_with_default_location in
List.filter_map
~f:help
[
Batch [];
Attributes (Reference.create "");
Callees (Reference.create "");
CalleesWithLocation (Reference.create "");
Defines [Reference.create ""];
DumpCallGraph;
IsCompatibleWith (empty, empty);
LessOrEqual (empty, empty);
ModulesOfPath path;
PathOfModule (Reference.create "");
SaveServerState path;
Superclasses [Reference.empty];
Type (Node.create_with_default_location (Expression.Constant Constant.True));
TypesInFiles [""];
ValidateTaintModels None;
Request.inline_decorators (Reference.create "");
]
|> List.sort ~compare:String.compare
|> String.concat ~sep:"\n "
|> Format.sprintf "Possible queries:\n %s"
let rec parse_request_exn query =
let open Expression in
match PyreParser.Parser.parse [query] with
| Ok
[
{
Node.value =
Expression
{
Node.value =
Call { callee = { Node.value = Name (Name.Identifier name); _ }; arguments };
_;
};
_;
};
] -> (
let expression { Call.Argument.value; _ } = value in
let access = function
| { Call.Argument.value; _ } when has_identifier_base value -> value
| _ -> raise (InvalidQuery "expected access")
in
let reference = function
| { Call.Argument.value = { Node.value = Name name; _ }; _ } when is_simple_name name ->
name_to_reference_exn name
| _ -> raise (InvalidQuery "expected reference")
in
let string_of_expression = function
| {
Node.value =
Expression.Constant
(Constant.String { StringLiteral.value; kind = StringLiteral.String });
_;
} ->
value
| _ -> raise (InvalidQuery "expected string")
in
let parse_inline_decorators arguments =
match arguments with
| [name] -> Request.inline_decorators (reference name)
| [
name;
{
Call.Argument.name = Some { Node.value = "decorators_to_skip"; _ };
value = { Node.value = Expression.List decorators; _ };
};
] -> (
let decorator_to_reference = function
| { Node.value = Expression.Name name; _ } as decorator ->
name_to_reference name |> Result.of_option ~error:decorator
| decorator -> Result.Error decorator
in
let valid_decorators, invalid_decorators =
List.map decorators ~f:decorator_to_reference |> List.partition_result
in
match valid_decorators, invalid_decorators with
| decorators_to_skip, [] ->
InlineDecorators { function_reference = reference name; decorators_to_skip }
| _, invalid_decorators ->
InvalidQuery
(Format.asprintf
"inline_decorators: invalid decorators `(%s)`"
(List.map invalid_decorators ~f:Expression.show |> String.concat ~sep:", "))
|> raise)
| _ ->
raise
(InvalidQuery
"inline_decorators expects qualified name and optional `decorators_to_skip=[...]`")
in
let string argument = argument |> expression |> string_of_expression in
let integer argument =
let integer_of_expression = function
| { Node.value = Expression.Constant (Constant.Integer value); _ } -> value
| _ -> raise (InvalidQuery "expected integer")
in
argument |> expression |> integer_of_expression
in
match String.lowercase name, arguments with
| "attributes", [name] -> Request.Attributes (reference name)
| "batch", queries ->
let construct_batch batch_queries query =
match query with
| Request.Batch _ -> raise (InvalidQuery "cannot nest batch queries")
| query -> query :: batch_queries
in
List.map ~f:expression queries
|> List.map ~f:Expression.show
|> List.map ~f:parse_request_exn
|> List.fold ~f:construct_batch ~init:[]
|> List.rev
|> fun query_list -> Request.Batch query_list
| "callees", [name] -> Request.Callees (reference name)
| "callees_with_location", [name] -> Request.CalleesWithLocation (reference name)
| "defines", names -> Request.Defines (List.map names ~f:reference)
| "dump_call_graph", [] -> Request.DumpCallGraph
| "dump_class_hierarchy", [] -> Request.Superclasses []
| "help", _ -> Request.Help (help ())
| "inline_decorators", arguments -> parse_inline_decorators arguments
| "is_compatible_with", [left; right] -> Request.IsCompatibleWith (access left, access right)
| "less_or_equal", [left; right] -> Request.LessOrEqual (access left, access right)
| "location_of_definition", [path; line; column] ->
Request.LocationOfDefinition
{
path = PyrePath.create_absolute (string path);
position = { line = integer line; column = integer column };
}
| "modules_of_path", [path] -> Request.ModulesOfPath (PyrePath.create_absolute (string path))
| "path_of_module", [module_access] -> Request.PathOfModule (reference module_access)
| "save_server_state", [path] ->
Request.SaveServerState (PyrePath.create_absolute (string path))
| "superclasses", names -> Superclasses (List.map ~f:reference names)
| "type", [argument] -> Type (expression argument)
| "types", paths -> Request.TypesInFiles (List.map ~f:string paths)
| "validate_taint_models", [] -> ValidateTaintModels None
| "validate_taint_models", [argument] -> Request.ValidateTaintModels (Some (string argument))
| _ -> raise (InvalidQuery "unexpected query"))
| Ok _ when String.equal query "help" -> Help (help ())
| Ok _ -> raise (InvalidQuery "unexpected query")
| Error _ -> raise (InvalidQuery "failed to parse query")
let parse_request query =
try Result.Ok (parse_request_exn query) with
| InvalidQuery reason -> Result.Error reason
module InlineDecorators = struct
let inline_decorators ~environment ~decorators_to_skip function_reference =
let define =
GlobalResolution.define
(TypeEnvironment.ReadOnly.global_resolution environment)
function_reference
in
match define with
| Some define -> (
let get_source =
AstEnvironment.ReadOnly.get_processed_source
(TypeEnvironment.ReadOnly.ast_environment environment)
in
let define_with_inlining =
InlineDecorator.inline_decorators_for_define
~get_decorator_body:
(InlineDecorator.decorator_body
~should_skip_decorator:(Set.mem decorators_to_skip)
~get_source)
~location:Location.any
define
in
match Statement.Statement.Define define_with_inlining |> Transform.sanitize_statement with
| Statement.Statement.Define define -> Response.Single (FunctionDefinition define)
| _ -> failwith "Expected define")
| None ->
Response.Error
(Format.asprintf "Could not find function `%s`" (Reference.show function_reference))
end
let rec process_request ~environment ~build_system ~configuration request =
let process_request () =
let module_tracker = TypeEnvironment.module_tracker environment in
let read_only_environment = TypeEnvironment.read_only environment in
let global_resolution = TypeEnvironment.ReadOnly.global_resolution read_only_environment in
let order = GlobalResolution.class_hierarchy global_resolution in
let resolution =
TypeCheck.resolution
global_resolution
(* TODO(T65923817): Eliminate the need of creating a dummy context here *)
(module TypeCheck.DummyContext)
in
let parse_and_validate
?(unknown_is_top = false)
?(fill_missing_type_parameters_with_any = false)
expression
=
let annotation =
(* Return untracked so we can specifically message the user about them. *)
GlobalResolution.parse_annotation ~validation:NoValidation global_resolution expression
in
let annotation =
if unknown_is_top then
let constraints = function
| Type.Primitive "unknown" -> Some Type.Top
| _ -> None
in
Type.instantiate annotation ~constraints
else
annotation
in
let annotation =
match fill_missing_type_parameters_with_any, annotation with
| true, Type.Primitive annotation -> (
let generics = GlobalResolution.variables global_resolution annotation in
match generics with
| Some generics
when (not (List.is_empty generics))
&& List.for_all generics ~f:(function
| Type.Variable.Unary _ -> true
| _ -> false) ->
Type.parametric
annotation
(List.map generics ~f:(fun _ -> Type.Parameter.Single Type.Any))
| _ -> Type.Primitive annotation)
| _ -> annotation
in
if ClassHierarchy.is_instantiated order annotation then
let mismatches, _ =
GlobalResolution.check_invalid_type_parameters global_resolution annotation
in
if List.is_empty mismatches then
annotation
else
raise (IncorrectParameters annotation)
else
raise (ClassHierarchy.Untracked (Type.show annotation))
in
let unannotated_global_environment =
GlobalResolution.unannotated_global_environment global_resolution
in
let get_error_paths errors =
List.fold
~init:""
~f:(fun sofar (path, error_reason) ->
let print_reason = function
| LocationBasedLookupProcessor.StubShadowing -> " (file shadowed by .pyi stub file)"
| LocationBasedLookupProcessor.FileNotFound -> " (file not found)"
in
Format.asprintf
"%s%s`%s`%s"
sofar
(if String.is_empty sofar then "" else ", ")
path
(print_reason error_reason))
errors
in
let open Response in
match request with
| Request.Attributes annotation ->
let to_attribute attribute =
let name = Annotated.Attribute.name attribute in
let instantiated_annotation =
GlobalResolution.instantiate_attribute
~resolution:global_resolution
~accessed_through_class:false
attribute
in
let annotation =
instantiated_annotation |> Annotated.Attribute.annotation |> Annotation.annotation
in
let property = Annotated.Attribute.property attribute in
let kind =
if property then
Base.Property
else
Base.Regular
in
let final = Annotated.Attribute.is_final instantiated_annotation in
{ Base.name; annotation; kind; final }
in
parse_and_validate (Expression.from_reference ~location:Location.any annotation)
|> Type.split
|> fst
|> Type.primitive_name
>>= GlobalResolution.attributes ~resolution:global_resolution
>>| List.map ~f:to_attribute
>>| (fun attributes -> Single (Base.FoundAttributes attributes))
|> Option.value
~default:
(Error
(Format.sprintf "No class definition found for %s" (Reference.show annotation)))
| Batch requests ->
Batch (List.map ~f:(process_request ~environment ~build_system ~configuration) requests)
| Callees caller ->
(* We don't yet support a syntax for fetching property setters. *)
Single
(Base.Callees
(Callgraph.get ~caller:(Callgraph.FunctionCaller caller)
|> List.map ~f:(fun { Callgraph.callee; _ } -> callee)))
| CalleesWithLocation caller ->
let instantiate =
Location.WithModule.instantiate
~lookup:
(AstEnvironment.ReadOnly.get_real_path_relative
~configuration
(TypeEnvironment.ReadOnly.ast_environment read_only_environment))
in
let callees =
(* We don't yet support a syntax for fetching property setters. *)
Callgraph.get ~caller:(Callgraph.FunctionCaller caller)
|> List.map ~f:(fun { Callgraph.callee; locations } ->
{ Base.callee; locations = List.map locations ~f:instantiate })
in
Single (Base.CalleesWithLocation callees)
| Defines module_or_class_names ->
let ast_environment = TypeEnvironment.ReadOnly.ast_environment read_only_environment in
let defines_of_module module_or_class_name =
let module_name, filter_define =
if AstEnvironment.ReadOnly.is_module_tracked ast_environment module_or_class_name then
Some module_or_class_name, fun _ -> false
else
let filter
{ Statement.Define.signature = { Statement.Define.Signature.parent; _ }; _ }
=
not (Option.equal Reference.equal parent (Some module_or_class_name))
in
let rec find_module_name current_reference =
if AstEnvironment.ReadOnly.is_module_tracked ast_environment current_reference then
Some current_reference
else
Reference.prefix current_reference >>= find_module_name
in
find_module_name module_or_class_name, filter
in
let defines =
module_name
>>= AstEnvironment.ReadOnly.get_processed_source ast_environment
>>| Analysis.FunctionDefinition.collect_defines
>>| List.map ~f:snd
>>| List.concat_map ~f:Analysis.FunctionDefinition.all_bodies
>>| List.filter ~f:(fun { Node.value = define; _ } ->
not
(Statement.Define.is_toplevel define
|| Statement.Define.is_class_toplevel define
|| Statement.Define.is_overloaded_function define
|| filter_define define))
|> Option.value ~default:[]
in
let represent
{
Node.value =
{ Statement.Define.signature = { name; return_annotation; parameters; _ }; _ };
_;
}
=
let represent_parameter { Node.value = { Expression.Parameter.name; annotation; _ }; _ }
=
{ Base.parameter_name = Identifier.sanitized name; parameter_annotation = annotation }
in
{
Base.define_name = name;
parameters = List.map parameters ~f:represent_parameter;
return_annotation;
}
in
List.map defines ~f:represent
in
List.concat_map module_or_class_names ~f:defines_of_module
|> fun defines -> Single (Base.FoundDefines defines)
| DumpCallGraph ->
let get_callgraph module_qualifier =
let callees
{
Node.value =
{
Statement.Define.signature = { Statement.Define.Signature.name = caller; _ };
_;
};
_;
}
=
let instantiate =
Location.WithModule.instantiate
~lookup:
(AstEnvironment.ReadOnly.get_real_path_relative
~configuration
(TypeEnvironment.ReadOnly.ast_environment read_only_environment))
in
Callgraph.get ~caller:(Callgraph.FunctionCaller caller)
|> List.map ~f:(fun { Callgraph.callee; locations } ->
{ Base.callee; locations = List.map locations ~f:instantiate })
|> fun callees -> { Base.caller; callees }
in
let ast_environment = TypeEnvironment.ReadOnly.ast_environment read_only_environment in
AstEnvironment.ReadOnly.get_processed_source ast_environment module_qualifier
>>| Preprocessing.defines ~include_toplevels:false ~include_stubs:false
>>| List.map ~f:callees
|> Option.value ~default:[]
in
let qualifiers = ModuleTracker.tracked_explicit_modules module_tracker in
Single (Base.Callgraph (List.concat_map qualifiers ~f:get_callgraph))
| Help help_list -> Single (Base.Help help_list)
| InlineDecorators { function_reference; decorators_to_skip } ->
InlineDecorators.inline_decorators
~environment:(TypeEnvironment.read_only environment)
~decorators_to_skip:(Reference.Set.of_list decorators_to_skip)
function_reference
| IsCompatibleWith (left, right) ->
(* We need a special version of parse_and_validate to handle the "unknown" type that
Monkeycheck may send us *)
let left = parse_and_validate ~unknown_is_top:true left in
let right = parse_and_validate ~unknown_is_top:true right in
let right =
match Type.coroutine_value right with
| None -> right
| Some unwrapped -> unwrapped
in
GlobalResolution.is_compatible_with global_resolution ~left ~right
|> fun result -> Single (Base.Compatibility { actual = left; expected = right; result })
| ModulesOfPath path ->
let module_of_path path =
match ModuleTracker.lookup_path ~configuration module_tracker path with
| ModuleTracker.PathLookup.Found { SourcePath.qualifier; _ } -> Some qualifier
| ShadowedBy _
| NotFound ->
None
in
let artifiact_paths = BuildSystem.lookup_artifact build_system path in
Single (Base.FoundModules (List.filter_map ~f:module_of_path artifiact_paths))
| LessOrEqual (left, right) ->
let left = parse_and_validate left in
let right = parse_and_validate right in
GlobalResolution.less_or_equal global_resolution ~left ~right
|> fun response -> Single (Base.Boolean response)
| LocationOfDefinition { path; position } ->
let module_of_path path =
let relative_path =
let { Configuration.Analysis.local_root = root; _ } = configuration in
PyrePath.create_relative ~root ~relative:(PyrePath.absolute path)
in
match
BuildSystem.lookup_artifact build_system relative_path
|> List.map ~f:(ModuleTracker.lookup_path ~configuration module_tracker)
with
| [ModuleTracker.PathLookup.Found { SourcePath.qualifier; _ }] -> Some qualifier
| _ -> None
in
let open Base in
let location_to_location_of_definition
Location.WithModule.(
{
start = { line = start_line; column = start_column };
stop = { line = stop_line; column = stop_column };
_;
} as location_with_module)
=
let module_to_absolute_path reference =
AstEnvironment.ReadOnly.get_real_path
~configuration
(TypeEnvironment.ReadOnly.ast_environment read_only_environment)
reference
>>| PyrePath.absolute
in
let Location.WithPath.{ path; _ } =
Location.WithModule.instantiate location_with_module ~lookup:module_to_absolute_path
in
{
path;
range =
{
start = { line = start_line; character = start_column };
end_ = { line = stop_line; character = stop_column };
};
}
in
module_of_path path
>>= (fun module_reference ->
LocationBasedLookup.location_of_definition
~type_environment:(TypeEnvironment.read_only environment)
~module_reference
position)
>>| location_to_location_of_definition
|> Option.to_list
|> fun definitions -> Single (Base.FoundLocationsOfDefinitions definitions)
| PathOfModule module_name ->
ModuleTracker.lookup_source_path module_tracker module_name
>>= (fun source_path ->
let path = SourcePath.full_path ~configuration source_path |> PyrePath.absolute in
Some (Single (Base.FoundPath path)))
|> Option.value
~default:
(Error (Format.sprintf "No path found for module `%s`" (Reference.show module_name)))
| SaveServerState path ->
let path = PyrePath.absolute path in
Log.info "Saving server state into `%s`" path;
Memory.save_shared_memory ~path ~configuration;
Single (Base.Success (Format.sprintf "Saved state."))
| Superclasses class_names ->
let get_superclasses class_name =
Reference.show class_name
|> GlobalResolution.successors ~resolution:global_resolution
|> List.sort ~compare:String.compare
|> List.map ~f:Reference.create
|> fun superclasses -> { Base.class_name; superclasses }
in
let class_names =
match class_names with
| [] ->
UnannotatedGlobalEnvironment.ReadOnly.all_classes unannotated_global_environment
|> List.map ~f:Reference.create
| _ ->
List.filter class_names ~f:(fun class_name ->
Reference.show class_name |> GlobalResolution.class_exists global_resolution)
in
Single (Superclasses (List.map ~f:get_superclasses class_names))
| Type expression ->
let annotation = Resolution.resolve_expression_to_type resolution expression in
Single (Type annotation)
| TypesInFiles paths ->
let find_resolved_types path =
match
LocationBasedLookupProcessor.find_all_resolved_types_for_path
~environment
~build_system
~configuration
path
with
| Result.Ok types ->
Either.First { Base.path; types = List.map ~f:create_type_at_location types }
| Result.Error error_reason -> Either.Second (path, error_reason)
in
let results, errors = List.partition_map ~f:find_resolved_types paths in
if List.is_empty errors then
Single (Base.TypesByPath results)
else
Error (Format.asprintf "Not able to get lookups in: %s" (get_error_paths errors))
| ValidateTaintModels path -> (
try
let paths =
match path with
| Some path ->
if String.is_prefix ~prefix:"/" path then
[PyrePath.create_absolute ~follow_symbolic_links:true path]
else
let { Configuration.Analysis.local_root = root; _ } = configuration in
[PyrePath.create_relative ~root ~relative:path]
| None -> configuration.Configuration.Analysis.taint_model_paths
in
let configuration =
Taint.TaintConfiguration.create
~rule_filter:None
~find_missing_flows:None
~dump_model_query_results_path:None
~maximum_trace_length:None
~maximum_tito_depth:None
~taint_model_paths:paths
|> Taint.TaintConfiguration.abort_on_error
in
let get_model_errors sources =
let model_errors (path, source) =
Taint.ModelParser.parse
~resolution:
(TypeCheck.resolution
global_resolution
(* TODO(T65923817): Eliminate the need of creating a dummy context here *)
(module TypeCheck.DummyContext))
~path
~source
~configuration
~callables:None
~stubs:(Interprocedural.Target.HashSet.create ())
()
|> fun { Taint.ModelParser.errors; _ } -> errors
in
List.concat_map sources ~f:model_errors
in
let errors = Taint.ModelParser.get_model_sources ~paths |> get_model_errors in
if List.is_empty errors then
Single
(Base.Success
(Format.asprintf
"Models in `%s` are valid."
(paths |> List.map ~f:PyrePath.show |> String.concat ~sep:", ")))
else
Single (Base.ModelVerificationErrors errors)
with
| error -> Error (Exn.to_string error))
in
try process_request () with
| ClassHierarchy.Untracked untracked ->
let untracked_response =
Format.asprintf "Type `%s` was not found in the type order." untracked
in
Error untracked_response
| IncorrectParameters untracked ->
let untracked_response =
Format.asprintf "Type `%a` has the wrong number of parameters." Type.pp untracked
in
Error untracked_response
| ClassHierarchy.Cyclic trace ->
Error
(Format.asprintf
"Cyclic class hierarchy: {%s}"
(Hash_set.to_list trace |> String.concat ~sep:", "))
let parse_and_process_request ~environment ~build_system ~configuration request =
match parse_request request with
| Result.Error reason -> Response.Error reason
| Result.Ok request -> process_request ~environment ~build_system ~configuration request