diff --git a/infer/src/IR/PythonClassName.ml b/infer/src/IR/PythonClassName.ml index 244f43deea..f0c1c9bb5b 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 3ec252e200..5800e4e90e 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 601701754d..9748a9089c 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 9a10d497d0..4198ebd9e8 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 ac8d80b08e..7f8c6c4a5f 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 e69de29bb2..6264236915 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 d8cf3525d8..bc751d0351 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 7ccb572447..a747d6f478 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())