From a632165927e957a2a06f0bad14faf4f94cb6460a Mon Sep 17 00:00:00 2001 From: David Pichardie Date: Thu, 12 Dec 2024 22:10:59 -0800 Subject: [PATCH] [inferpython][specialization type] using new import_from type in Pulse models Summary: now we can use this new type to resolve more closures without specialization when the closure is associated with an `from .. import .. as ..` statement. Reviewed By: geralt-encore Differential Revision: D67140811 Privacy Context Container: L1208441 fbshipit-source-id: e11f2bbd91b51ded1f3d22e11a4808197cbc6812 --- infer/src/IR/PythonClassName.ml | 15 +++++++++++ infer/src/IR/PythonClassName.mli | 6 +++++ infer/src/IR/Typ.ml | 13 ++++++++++ infer/src/IR/Typ.mli | 4 +++ infer/src/pulse/PulseModelsPython.ml | 26 ++++++++++++++----- .../codetoanalyze/python/pulse/__init__.py | 4 +++ .../python/pulse/async_import_simple.py | 4 --- .../python/pulse/async_import_with_package.py | 12 --------- 8 files changed, 61 insertions(+), 23 deletions(-) diff --git a/infer/src/IR/PythonClassName.ml b/infer/src/IR/PythonClassName.ml index 244f43deea0..f0c1c9bb5b5 100644 --- a/infer/src/IR/PythonClassName.ml +++ b/infer/src/IR/PythonClassName.ml @@ -33,3 +33,18 @@ let is_module {classname} = String.is_prefix classname ~prefix:globals_prefix let get_module_name {classname} = String.chop_prefix classname ~prefix:globals_prefix let is_final name = is_module name + +let module_attribute_prefix = "PyModuleAttr::" + +let is_module_attribute {classname} = String.is_prefix classname ~prefix:module_attribute_prefix + +let get_module_attribute_infos {classname} = + let open IOption.Let_syntax in + let* last_pos = String.substr_index_all classname ~may_overlap:false ~pattern:"::" |> List.last in + let length = String.length classname in + let attribute_name = String.sub classname ~pos:(last_pos + 2) ~len:(length - last_pos - 2) in + let+ module_name = + String.sub classname ~pos:0 ~len:last_pos |> String.chop_prefix ~prefix:module_attribute_prefix + in + let classname = globals_prefix ^ module_name in + ({classname}, attribute_name) diff --git a/infer/src/IR/PythonClassName.mli b/infer/src/IR/PythonClassName.mli index 3ec252e200a..5800e4e90e7 100644 --- a/infer/src/IR/PythonClassName.mli +++ b/infer/src/IR/PythonClassName.mli @@ -26,6 +26,12 @@ val is_final : t -> bool val is_module : t -> bool +val is_module_attribute : t -> bool + +val get_module_attribute_infos : t -> (t * string) option +(** will return the pair (module_name, attribute) params of the type iff the type name is a module + attribute type *) + val get_module_name : t -> string option (** will return the string representation of the module iff type name is a module type *) diff --git a/infer/src/IR/Typ.ml b/infer/src/IR/Typ.ml index 601701754d7..9748a9089c3 100644 --- a/infer/src/IR/Typ.ml +++ b/infer/src/IR/Typ.ml @@ -603,10 +603,23 @@ module Name = struct match name with PythonClass py -> PythonClassName.is_module py | _ -> false + let is_python_module_attribute name = + match name with PythonClass py -> PythonClassName.is_module_attribute py | _ -> false + + let get_python_module_name name = match name with PythonClass py -> PythonClassName.get_module_name py | _ -> None + let get_python_module_attribute_infos name = + match name with + | PythonClass py -> + PythonClassName.get_module_attribute_infos py + |> Option.map ~f:(fun (py_name, str) -> (PythonClass py_name, str)) + | _ -> + None + + let is_same_type t1 t2 = match (t1, t2) with | CStruct _, CStruct _ diff --git a/infer/src/IR/Typ.mli b/infer/src/IR/Typ.mli index 9a10d497d09..4198ebd9e8b 100644 --- a/infer/src/IR/Typ.mli +++ b/infer/src/IR/Typ.mli @@ -208,8 +208,12 @@ module Name : sig val is_python_module : t -> bool + val is_python_module_attribute : t -> bool + val get_python_module_name : t -> string option + val get_python_module_attribute_infos : t -> (t * string) option + module C : sig val from_string : string -> t diff --git a/infer/src/pulse/PulseModelsPython.ml b/infer/src/pulse/PulseModelsPython.ml index ac8d80b08e6..7f8c6c4a5fa 100644 --- a/infer/src/pulse/PulseModelsPython.ml +++ b/infer/src/pulse/PulseModelsPython.ml @@ -51,14 +51,26 @@ module Dict = struct let propagate_static_type_on_load dict key load_res : unit DSL.model_monad = let open DSL.Syntax in + let rec propagate_field_type tname key = + let field = Fieldname.make tname key in + let* opt_info = tenv_resolve_field_info tname field in + option_iter opt_info ~f:(fun {Struct.typ= field_typ} -> + let opt_static_tname = Typ.name (Typ.strip_ptr field_typ) in + option_iter opt_static_tname ~f:(fun static_tname -> + if Typ.Name.is_python_module_attribute static_tname then + let tname, attr = + Typ.Name.get_python_module_attribute_infos static_tname |> Option.value_exn + in + propagate_field_type tname attr + else add_static_type static_tname load_res ) ) + in + let* opt_key = as_constant_string key in + let key = + Option.value_or_thunk opt_key ~default:(fun () -> + L.die InternalError "expecting constant string value" ) + in let* opt_static_type = get_static_type dict in - option_iter opt_static_type ~f:(fun tname -> - let* field = sil_fieldname_from_string_value_exn tname key in - let* opt_info = tenv_resolve_field_info tname field in - option_iter opt_info ~f:(fun {Struct.typ= field_typ} -> - let opt_static_tname = Typ.name (Typ.strip_ptr field_typ) in - option_iter opt_static_tname ~f:(fun static_tname -> - add_static_type static_tname load_res ) ) ) + option_iter opt_static_type ~f:(fun tname -> propagate_field_type tname key) let get dict key : DSL.aval DSL.model_monad = diff --git a/infer/tests/codetoanalyze/python/pulse/__init__.py b/infer/tests/codetoanalyze/python/pulse/__init__.py index e69de29bb2d..6264236915a 100644 --- a/infer/tests/codetoanalyze/python/pulse/__init__.py +++ b/infer/tests/codetoanalyze/python/pulse/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/infer/tests/codetoanalyze/python/pulse/async_import_simple.py b/infer/tests/codetoanalyze/python/pulse/async_import_simple.py index d8cf3525d88..bc751d03510 100644 --- a/infer/tests/codetoanalyze/python/pulse/async_import_simple.py +++ b/infer/tests/codetoanalyze/python/pulse/async_import_simple.py @@ -22,7 +22,3 @@ async def with_from_import_bad(): async def with_from_import_ok(): await async_await(asyncio.sleep(1)) - -#TODO: remove the need for explicit call here -asyncio.run(with_from_import_ok()) -asyncio.run(with_from_import_bad()) diff --git a/infer/tests/codetoanalyze/python/pulse/async_import_with_package.py b/infer/tests/codetoanalyze/python/pulse/async_import_with_package.py index 7ccb5724470..a747d6f478e 100644 --- a/infer/tests/codetoanalyze/python/pulse/async_import_with_package.py +++ b/infer/tests/codetoanalyze/python/pulse/async_import_with_package.py @@ -14,59 +14,47 @@ async def bad1(): await dont_await_it1(asyncio.sleep(1)) -asyncio.run(bad1()) async def ok1(): await await_it1(asyncio.sleep(1)) -asyncio.run(ok1()) async def bad2(): await dont_await_it2(asyncio.sleep(1)) -asyncio.run(bad2()) async def ok2(): await await_it2(asyncio.sleep(1)) -asyncio.run(ok2()) async def bad3(): await dont_await_it3(asyncio.sleep(1)) -asyncio.run(bad3()) async def ok3(): await await_it3(asyncio.sleep(1)) -asyncio.run(ok3()) async def bad4(): await dont_await_it4(asyncio.sleep(1)) -asyncio.run(bad4()) async def ok4(): await await_it4(asyncio.sleep(1)) -asyncio.run(ok4()) async def bad5(): await dont_await_it5(asyncio.sleep(1)) -asyncio.run(bad5()) async def ok5(): await await_it5(asyncio.sleep(1)) -asyncio.run(ok5()) async def bad6(): await dont_await_it6(asyncio.sleep(1)) -asyncio.run(bad6()) async def ok6(): await await_it6(asyncio.sleep(1)) -asyncio.run(ok6())