diff --git a/infer/src/IR/BuiltinDecl.ml b/infer/src/IR/BuiltinDecl.ml index bc6c1110ba8..e6e308aafa7 100644 --- a/infer/src/IR/BuiltinDecl.ml +++ b/infer/src/IR/BuiltinDecl.ml @@ -168,6 +168,8 @@ let __instanceof = create_procname "__instanceof" let __java_throw = create_procname "__java_throw" +let __get_lazy_class = create_procname "__get_lazy_class" + let __lazy_class_initialize = create_procname "__lazy_class_initialize" let __method_set_ignore_attribute = create_procname "__method_set_ignore_attribute" diff --git a/infer/src/IR/BuiltinDecl.mli b/infer/src/IR/BuiltinDecl.mli index 11e9110029d..1ff9deaed0b 100644 --- a/infer/src/IR/BuiltinDecl.mli +++ b/infer/src/IR/BuiltinDecl.mli @@ -102,6 +102,11 @@ val __builtin_cxx_co_return : Procname.t val __builtin_cxx_co_await : Procname.t +val __get_lazy_class : t +(** returns the a LazyClass representation of its arguement (a type name). A LazyClass represents a + class that we know the name of but don't necessarily know if the class has been defined + somewhere. *) + val __lazy_class_initialize : t (** returns the singleton object associated with a given type, and performs lazily its initialization (only generated by the Hack frontend currently) *) diff --git a/infer/src/pulse/PulseModelsHack.ml b/infer/src/pulse/PulseModelsHack.ml index 82462d8e987..82562de8e21 100644 --- a/infer/src/pulse/PulseModelsHack.ml +++ b/infer/src/pulse/PulseModelsHack.ml @@ -511,6 +511,7 @@ let matchers : matcher list = let open ProcnameDispatcher.Call in [ -"$builtins" &:: "nondet" <>$$--> Basic.nondet ~desc:"nondet" ; +BuiltinDecl.(match_builtin __lazy_class_initialize) <>$ capt_exp $--> lazy_class_initialize + ; +BuiltinDecl.(match_builtin __get_lazy_class) <>$ capt_exp $--> lazy_class_initialize ; -"$builtins" &:: "hhbc_await" <>$ capt_arg_payload $--> hack_await ; -"$builtins" &:: "hack_array_get" <>$ capt_arg $++$--> hack_array_get ; -"$builtins" &:: "hack_array_cow_set" <>$ capt_arg $++$--> hack_array_cow_set diff --git a/infer/src/textual/Textual.ml b/infer/src/textual/Textual.ml index 9f836799ff8..e301339fd2e 100644 --- a/infer/src/textual/Textual.ml +++ b/infer/src/textual/Textual.ml @@ -121,6 +121,8 @@ let builtin_allocate_array = "__sil_allocate_array" let builtin_lazy_class_initialize = "__sil_lazy_class_initialize" +let builtin_get_lazy_class = "__sil_get_lazy_class" + let builtin_cast = "__sil_cast" let builtin_instanceof = "__sil_instanceof" @@ -366,6 +368,8 @@ module ProcDecl = struct let lazy_class_initialize_name = make_toplevel_name builtin_lazy_class_initialize Location.Unknown + let get_lazy_class_name = make_toplevel_name builtin_get_lazy_class Location.Unknown + let cast_name = make_toplevel_name builtin_cast Location.Unknown let instanceof_name = make_toplevel_name builtin_instanceof Location.Unknown @@ -483,6 +487,10 @@ module ProcDecl = struct equal_qualified_procname lazy_class_initialize_name qualified_name + let is_get_lazy_class_builtin qualified_name = + equal_qualified_procname get_lazy_class_name qualified_name + + let is_cast_builtin = equal_qualified_procname cast_name let is_instanceof_builtin = equal_qualified_procname instanceof_name @@ -490,6 +498,7 @@ module ProcDecl = struct let is_type_builtin qualified_name = is_allocate_object_builtin qualified_name || is_allocate_array_builtin qualified_name + || is_get_lazy_class_builtin qualified_name || is_lazy_class_initialize_builtin qualified_name || is_instanceof_builtin qualified_name diff --git a/infer/src/textual/Textual.mli b/infer/src/textual/Textual.mli index 953a79cd37b..f1fe8ee0856 100644 --- a/infer/src/textual/Textual.mli +++ b/infer/src/textual/Textual.mli @@ -214,6 +214,8 @@ module ProcDecl : sig val is_allocate_array_builtin : qualified_procname -> bool + val is_get_lazy_class_builtin : qualified_procname -> bool + val is_lazy_class_initialize_builtin : qualified_procname -> bool val is_side_effect_free_sil_expr : qualified_procname -> bool diff --git a/infer/src/textual/TextualSil.ml b/infer/src/textual/TextualSil.ml index 69e44b3a044..2c85177a1e9 100644 --- a/infer/src/textual/TextualSil.ml +++ b/infer/src/textual/TextualSil.ml @@ -722,7 +722,8 @@ module InstrBridge = struct let builtin_new = SilExp.Const (SilConst.Cfun BuiltinDecl.__new_array) in Call ((ret, class_type), builtin_new, args, loc, CallFlags.default) | Let {id; exp= Call {proc; args= [Typ typ]}; loc} - when ProcDecl.is_lazy_class_initialize_builtin proc -> + when ProcDecl.is_lazy_class_initialize_builtin proc || ProcDecl.is_get_lazy_class_builtin proc + -> let typ = TypBridge.to_sil lang typ in let sizeof = SilExp.Sizeof {typ; nbytes= None; dynamic_length= None; subtype= Subtype.exact} @@ -731,11 +732,15 @@ module InstrBridge = struct let args = [(sizeof, class_type)] in let ret = IdentBridge.to_sil id in let loc = LocationBridge.to_sil sourcefile loc in - let builtin_lazy_class_initialize = - SilExp.Const (Cfun BuiltinDecl.__lazy_class_initialize) + let builtin = + if ProcDecl.is_lazy_class_initialize_builtin proc then + SilExp.Const (Cfun BuiltinDecl.__lazy_class_initialize) + else if ProcDecl.is_get_lazy_class_builtin proc then + SilExp.Const (Cfun BuiltinDecl.__get_lazy_class) + else L.die InternalError "should not happen because of the pattern-matching test" in (* TODO: we may want to use the class_of_class type here *) - Call ((ret, class_type), builtin_lazy_class_initialize, args, loc, CallFlags.default) + Call ((ret, class_type), builtin, args, loc, CallFlags.default) | Let {id; exp= Call {proc; args; kind}; loc} -> let ret = IdentBridge.to_sil id in let procsig = Exp.call_sig proc args (TextualDecls.lang decls_env) in diff --git a/infer/src/textual/TextualTypeVerification.ml b/infer/src/textual/TextualTypeVerification.ml index a4d646149ae..c93038e72f6 100644 --- a/infer/src/textual/TextualTypeVerification.ml +++ b/infer/src/textual/TextualTypeVerification.ml @@ -441,7 +441,10 @@ and typeof_exp (exp : Exp.t) : (Exp.t * Typ.t) monad = (Exp.Index (exp1, exp2), typ) | Const const -> ret (exp, typeof_const const) - | Call {proc; args} when ProcDecl.is_allocate_object_builtin proc -> + | Call {proc; args} + when ProcDecl.is_allocate_object_builtin proc + || ProcDecl.is_lazy_class_initialize_builtin proc + || ProcDecl.is_get_lazy_class_builtin proc -> typeof_allocate_builtin proc args | Call {proc; args} when ProcDecl.is_allocate_array_builtin proc -> typeof_allocate_array_builtin proc args @@ -449,8 +452,6 @@ and typeof_exp (exp : Exp.t) : (Exp.t * Typ.t) monad = typeof_cast_builtin proc args | Call {proc; args} when ProcDecl.is_instanceof_builtin proc -> typeof_instanceof_builtin proc args - | Call {proc; args} when ProcDecl.is_lazy_class_initialize_builtin proc -> - typeof_allocate_builtin proc args | Call {proc; args; kind} -> let* lang = get_lang in let procsig = Exp.call_sig proc args lang in diff --git a/infer/tests/codetoanalyze/hack/pulse/issues.exp b/infer/tests/codetoanalyze/hack/pulse/issues.exp index 84a2b019e60..77cfd4f7e8b 100644 --- a/infer/tests/codetoanalyze/hack/pulse/issues.exp +++ b/infer/tests/codetoanalyze/hack/pulse/issues.exp @@ -79,6 +79,8 @@ intra_file_flow.hack, IntraFile::IntraFileFlow.explicitSinkClassDirectBad, 0, TA intra_file_flow.hack, IntraFile::IntraFileFlow$static.explicitSinkClassDirectBad, 3, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value passed as argument `#0` to `IntraFile::IntraFileFlow$static.explicitSinkClassDirectBad` with kind `Simple`,value passed as argument `#0` to `IntraFile::Logger$static.someLogMethod` with kind `Simple`], source: IntraFile::IntraFileFlow$static.explicitSinkClassDirectBad, sink: IntraFile::Logger$static.someLogMethod, tainted expression: $sc late_binding.hack, LateBinding::Main.call_caller_bad, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: $tainted late_binding.hack, LateBinding::Main.call_parent_caller_bad, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: $tainted +late_binding.hack, LateBinding::Main.call_C_with_classname_bad, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: $tainted +late_binding.hack, LateBinding::Main.call_A_with_classname_bad, 4, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: $tainted level1.hack, $root.Level1::basicFlowBad, 2, TAINT_ERROR, no_bucket, ERROR, [source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: $root.Level1::taintSource() level1_with_extends.hack, ExtendsTests::Main.fromABad, 2, TAINT_ERROR, no_bucket, ERROR, [in call to `ExtendsTests::A.sourceIfA`,source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,return from call to `ExtendsTests::A.sourceIfA`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: ExtendsTests::A.sourceIfA() level1_with_extends.hack, ExtendsTests::Main.fromBBad, 2, TAINT_ERROR, no_bucket, ERROR, [in call to `ExtendsTests::A.sourceIfA`,source of the taint here: value returned from `$root.Level1::taintSource` with kind `Simple`,return from call to `ExtendsTests::A.sourceIfA`,value passed as argument `#0` to `$root.Level1::taintSink` with kind `Simple`], source: $root.Level1::taintSource, sink: $root.Level1::taintSink, tainted expression: ExtendsTests::A.sourceIfA() diff --git a/infer/tests/codetoanalyze/hack/pulse/late_binding.hack b/infer/tests/codetoanalyze/hack/pulse/late_binding.hack index 468522fbf1d..6bf423caadd 100644 --- a/infer/tests/codetoanalyze/hack/pulse/late_binding.hack +++ b/infer/tests/codetoanalyze/hack/pulse/late_binding.hack @@ -2,8 +2,6 @@ namespace LateBinding; class A { public static function caller1(): int { - // Dynamic type specialization is not strong enough yet here. - // The following late-binding call is wrongly turned into A::call_with_late_binding() return static::call_with_late_binding(); } @@ -22,9 +20,6 @@ class B extends A { class C extends B { public static function parent_caller(): int { - // here is the problem: C::parent_caller() needs specialization because - // A::caller need specialization. We did not implement propagation of - // specialization needs yet. return parent::caller1(); } @@ -36,7 +31,7 @@ class C extends B { } } -class Main { +final class Main { public function call_caller_bad(): void { $tainted = \Level1\taintSource(); @@ -69,4 +64,41 @@ class Main { \Level1\taintSink($tainted); } } + + public static function call_with_classname(classname $cn): int { + return $cn::call_with_late_binding(); + } + + public function call_C_with_classname_bad(): void { + $tainted = \Level1\taintSource(); + $i = self::call_with_classname(C::class); + if ($i == 0) { + \Level1\taintSink($tainted); + } + } + + public function call_C_with_classname_good(): void { + $tainted = \Level1\taintSource(); + $i = self::call_with_classname(C::class); + if ($i != 0) { + \Level1\taintSink($tainted); + } + } + + public function call_A_with_classname_bad(): void { + $tainted = \Level1\taintSource(); + $i = self::call_with_classname(A::class); + if ($i == 1) { + \Level1\taintSink($tainted); + } + } + + public function call_A_with_classname_good(): void { + $tainted = \Level1\taintSource(); + $i = self::call_with_classname(A::class); + if ($i != 1) { + \Level1\taintSink($tainted); + } + } + }