Skip to content

Commit

Permalink
[hack] adding a new builtin get_lazy_class
Browse files Browse the repository at this point in the history
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
  • Loading branch information
davidpichardie authored and facebook-github-bot committed Sep 25, 2023
1 parent 6a3b084 commit 59eaaaf
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 13 deletions.
2 changes: 2 additions & 0 deletions infer/src/IR/BuiltinDecl.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions infer/src/IR/BuiltinDecl.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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) *)
Expand Down
1 change: 1 addition & 0 deletions infer/src/pulse/PulseModelsHack.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions infer/src/textual/Textual.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -483,13 +487,18 @@ 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

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

Expand Down
2 changes: 2 additions & 0 deletions infer/src/textual/Textual.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions infer/src/textual/TextualSil.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions infer/src/textual/TextualTypeVerification.ml
Original file line number Diff line number Diff line change
Expand Up @@ -441,16 +441,17 @@ 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
| Call {proc; args} when ProcDecl.is_cast_builtin proc ->
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
Expand Down
2 changes: 2 additions & 0 deletions infer/tests/codetoanalyze/hack/pulse/issues.exp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
44 changes: 38 additions & 6 deletions infer/tests/codetoanalyze/hack/pulse/late_binding.hack
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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();
}

Expand All @@ -36,7 +31,7 @@ class C extends B {
}
}

class Main {
final class Main {

public function call_caller_bad(): void {
$tainted = \Level1\taintSource();
Expand Down Expand Up @@ -69,4 +64,41 @@ class Main {
\Level1\taintSink($tainted);
}
}

public static function call_with_classname(classname<A> $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);
}
}

}

0 comments on commit 59eaaaf

Please sign in to comment.