From 59eaaaff8b033591855b2ffc2b1eae6589e9412c Mon Sep 17 00:00:00 2001 From: David Pichardie Date: Mon, 25 Sep 2023 08:34:53 -0700 Subject: [PATCH] [hack] adding a new builtin get_lazy_class Summary: This builtin is used on Hack expression like C::class. Like lazy_class_initialize, it takes a type as argument. Its model is the same for now, but we we may want to distinguish them later, because they don't have exactly the same semantics in term of HHVM errors. Reviewed By: artempyanykh Differential Revision: D49579240 fbshipit-source-id: d94e727968ca8f73d200d0ed8ab45bfda5a594d2 --- infer/src/IR/BuiltinDecl.ml | 2 + infer/src/IR/BuiltinDecl.mli | 5 +++ infer/src/pulse/PulseModelsHack.ml | 1 + infer/src/textual/Textual.ml | 9 ++++ infer/src/textual/Textual.mli | 2 + infer/src/textual/TextualSil.ml | 13 ++++-- infer/src/textual/TextualTypeVerification.ml | 7 +-- .../tests/codetoanalyze/hack/pulse/issues.exp | 2 + .../hack/pulse/late_binding.hack | 44 ++++++++++++++++--- 9 files changed, 72 insertions(+), 13 deletions(-) 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); + } + } + }