diff --git a/hphp/doc/bytecode.specification b/hphp/doc/bytecode.specification index c27af6cf9d55d..68c2da139c0e8 100644 --- a/hphp/doc/bytecode.specification +++ b/hphp/doc/bytecode.specification @@ -3670,6 +3670,33 @@ FuncNumArgs [] -> [C:Int] Push the number of arguments the current function was called with. +StaticLocCheck [] -> [C:Bool] + + Static variable. This instruction first checks if the static variable named + %2 has been marked as initialized. If the static variable has been marked as + initialized, this instruction binds the static variable to the local variable + %1 and pushes true. Otherwise, this instruction pushes false. + +StaticLocDef [C] -> [] + + Initialize static variable. This instruction binds the static variable to + the local variable, assigns $1 to the local variable, and marks the static + variable as initialized. + + Precondition: Except in memoized functions, the static local named %2 has + not been initialized + +StaticLocInit [C] -> [] + + Static variable with initializer. This instruction first checks if the static + variable named %2 has been marked as initialized. If the static variable has + been marked as initialized, this instruction binds the static variable to the + local variable %1. Otherwise, this instruction binds the static variable to + the local variable, assigns $1 to the local variable, and marks the static + variable as initialized. + + The cell in $1 must not be a reference counted type. + Catch [] -> [C:Obj] Catch. Retrieves the current exception object and pushes it onto the stack. diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index 90c7d925bfbb8..00f8ef58ad6e4 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -1523,6 +1523,16 @@ To string conversions: Load the length of the string in S0. +| LdClosureStaticLoc, D(PtrToPropGen), S(Obj), NF + + Get pointer to static local named 'staticLocName' for function 'func' whose + closure object is S0. func must either be a Closure, or generatorFromClosure + +| LdStaticLoc, D(BoxedInitCell), NA, PRc + + Load the address of the static local variable named 'staticLocalName' for + function 'func' in RDS. The variable must be initialized + | FuncSupportsAsyncEagerReturn, D(Bool), S(Func), NF Tests for Func::m_attrs & AttrSupportsAsyncEagerReturn. @@ -2296,6 +2306,16 @@ To string conversions: Raises a runtime error unless whether each generic in S0 is reified or erased matches exactly to the expectations of the func. +| CheckStaticLoc, ND, NA, B + + Check whether the static local variable named 'staticLocalName' for + function 'func' is initialized in RDS, and branch if not. + +| InitStaticLoc, ND, S(Cell), NF + + Initialize the static local variable named 'staticLocalName' for + function 'func' with S0. + | InitClsCns, DCns, NA, PRc Initialize the RDS entry for a constant for a class, invoking autoload if it diff --git a/hphp/hack/src/annotated_ast/aast.ml b/hphp/hack/src/annotated_ast/aast.ml index 6977f112aa1c8..3b79449056e13 100644 --- a/hphp/hack/src/annotated_ast/aast.ml +++ b/hphp/hack/src/annotated_ast/aast.ml @@ -72,6 +72,7 @@ and stmt_ = | Return of expr option | GotoLabel of pstring | Goto of pstring + | Static_var of expr list | Global_var of expr list | Awaitall of (lid option * expr) list | If of expr * block * block diff --git a/hphp/hack/src/annotated_ast/aast_mapper.ml b/hphp/hack/src/annotated_ast/aast_mapper.ml index 380d73a02de85..65de08255e6d0 100644 --- a/hphp/hack/src/annotated_ast/aast_mapper.ml +++ b/hphp/hack/src/annotated_ast/aast_mapper.ml @@ -239,6 +239,7 @@ struct | S.Return oe -> T.Return (Option.map oe (map_expr menv)) | S.GotoLabel label -> T.GotoLabel label | S.Goto label -> T.Goto label + | S.Static_var el -> T.Static_var (map_exprl menv el) | S.Global_var el -> T.Global_var (map_exprl menv el) | S.Awaitall el -> let el = List.map el (fun (lid, expr) -> (lid, map_expr menv expr)) in diff --git a/hphp/hack/src/facts/flatten_smart_constructors.ml b/hphp/hack/src/facts/flatten_smart_constructors.ml index 465971ac673d3..615bc7f927329 100644 --- a/hphp/hack/src/facts/flatten_smart_constructors.ml +++ b/hphp/hack/src/facts/flatten_smart_constructors.ml @@ -267,6 +267,12 @@ module WithOp(Op : Op_S) = struct let make_continue_statement arg0 arg1 arg2 state = if Op.is_zero arg0 && Op.is_zero arg1 && Op.is_zero arg2 then state, Op.zero else state, Op.flatten [arg0; arg1; arg2] + let make_function_static_statement arg0 arg1 arg2 state = + if Op.is_zero arg0 && Op.is_zero arg1 && Op.is_zero arg2 then state, Op.zero + else state, Op.flatten [arg0; arg1; arg2] + let make_static_declarator arg0 arg1 state = + if Op.is_zero arg0 && Op.is_zero arg1 then state, Op.zero + else state, Op.flatten [arg0; arg1] let make_echo_statement arg0 arg1 arg2 state = if Op.is_zero arg0 && Op.is_zero arg1 && Op.is_zero arg2 then state, Op.zero else state, Op.flatten [arg0; arg1; arg2] diff --git a/hphp/hack/src/hackfmt/hack_format.ml b/hphp/hack/src/hackfmt/hack_format.ml index ddf16ec28a751..06dd0146f21a5 100644 --- a/hphp/hack/src/hackfmt/hack_format.ml +++ b/hphp/hack/src/hackfmt/hack_format.ml @@ -157,6 +157,7 @@ let rec t (env: Env.t) (node: Syntax.t) : Doc.t = | Syntax.PipeVariableExpression _ | Syntax.PropertyDeclarator _ | Syntax.ConstantDeclarator _ + | Syntax.StaticDeclarator _ | Syntax.ScopeResolutionExpression _ | Syntax.EmbeddedMemberSelectionExpression _ | Syntax.EmbeddedSubscriptExpression _ @@ -1236,6 +1237,11 @@ let rec t (env: Env.t) (node: Syntax.t) : Doc.t = continue_level = level; continue_semicolon = semi; } -> transform_keyword_expression_statement env kw level semi + | Syntax.FunctionStaticStatement { + static_static_keyword = static_kw; + static_declarations = declarators; + static_semicolon = semi; } -> + transform_keyword_expr_list_statement env static_kw declarators semi | Syntax.EchoStatement { echo_keyword = kw; echo_expressions = expr_list; diff --git a/hphp/hack/src/hh_single_compile.ml b/hphp/hack/src/hh_single_compile.ml index 2b29a16c4c402..27c54a0db831c 100644 --- a/hphp/hack/src/hh_single_compile.ml +++ b/hphp/hack/src/hh_single_compile.ml @@ -429,6 +429,7 @@ let make_popt () = ~enable_await_as_an_expression:(enable_await_as_an_expression co) ~disable_nontoplevel_declarations:(phpism_disable_nontoplevel_declarations co) ~disable_static_closures:(phpism_disable_static_closures co) + ~disable_static_local_variables:(phpism_disable_static_local_variables co) ~enable_hh_syntax_for_hhvm:(enable_hiphop_syntax co) ~enable_stronger_await_binding:(enable_stronger_await_binding co) ~disable_lval_as_an_expression:(disable_lval_as_an_expression co) diff --git a/hphp/hack/src/hhbc/closure_convert.ml b/hphp/hack/src/hhbc/closure_convert.ml index 9b5c23117d4c7..61311f1d2f4cb 100644 --- a/hphp/hack/src/hhbc/closure_convert.ml +++ b/hphp/hack/src/hhbc/closure_convert.ml @@ -88,6 +88,9 @@ type state = { inout_wrappers : fun_ list; (* The current namespace environment *) namespace: Namespace_env.env; + (* Static variables in closures have special properties with mangled names + * defined for them *) + static_vars : ULS.t; (* Set of closure names that used to have explicit 'use' language construct in original anonymous function *) explicit_use_set: SSet.t; @@ -140,6 +143,7 @@ let initial_state popt = named_hoisted_functions = SMap.empty; inout_wrappers = []; namespace = Namespace_env.empty popt; + static_vars = ULS.empty; explicit_use_set = SSet.empty; closure_namespaces = SMap.empty; closure_enclosing_classes = SMap.empty; @@ -368,8 +372,12 @@ let enter_lambda st = captured_vars = ULS.empty; captured_this = false; captured_generics = ULS.empty; + static_vars = ULS.empty; } +let add_static_var st var = + { st with static_vars = ULS.add st.static_vars var } + let set_namespace st ns = { st with namespace = ns } @@ -433,6 +441,9 @@ let make_closure ~class_num let cvl = List.map lambda_vars (fun name -> (p, (p, Hhbc_string_utils.Locals.strip_dollar name), None)) in + let cvl = cvl @ (List.map (ULS.items st.static_vars) + (fun name -> (p, (p, + "86static_" ^ (Hhbc_string_utils.Locals.strip_dollar name)), None))) in let cd = { c_mode = fd.f_mode; c_user_attributes = []; @@ -878,6 +889,7 @@ and convert_lambda env st p fd use_vars_opt = let captured_vars = st.captured_vars in let captured_this = st.captured_this in let captured_generics = st.captured_generics in + let static_vars = st.static_vars in let old_function_state = st.current_function_state in let st = enter_lambda st in let old_env = env in @@ -970,6 +982,7 @@ and convert_lambda env st p fd use_vars_opt = let st = { st with captured_vars; captured_this; captured_generics; + static_vars; explicit_use_set; closure_enclosing_classes; closure_namespaces = SMap.add @@ -1014,6 +1027,16 @@ and convert_stmt env st (p, stmt_ as stmt) : _ * stmt = | Return opt_e -> let st, opt_e = convert_opt_expr env st opt_e in st, (p, Return opt_e) + | Static_var el -> + let visit_static_var st e = + begin match snd e with + | Lvar (_, name) + | Binop (Eq None, (_, Lvar (_, name)), _) -> add_static_var st name + | _ -> failwith "Static var - impossible" + end in + let st = List.fold_left el ~init:st ~f:visit_static_var in + let st, el = convert_exprs env st el in + st, (p, Static_var el) | Awaitall el -> let st, el = List.map_env st el (convert_snd_expr env) in st, (p, Awaitall el) diff --git a/hphp/hack/src/hhbc/emit_body.ml b/hphp/hack/src/hhbc/emit_body.ml index 04f05929eb66d..871b679c7ee4a 100644 --- a/hphp/hack/src/hhbc/emit_body.ml +++ b/hphp/hack/src/hhbc/emit_body.ml @@ -134,7 +134,7 @@ and emit_defs env defs = let make_body body_instrs decl_vars is_memoize_wrapper is_memoize_wrapper_lsb - params return_type_info doc_comment + params return_type_info static_inits doc_comment env = let body_instrs = rewrite_user_labels body_instrs in let body_instrs = rewrite_class_refs body_instrs in @@ -154,6 +154,7 @@ let make_body body_instrs decl_vars is_memoize_wrapper_lsb params return_type_info + static_inits doc_comment env @@ -332,6 +333,10 @@ let emit_body then remove_this vars @ ["$this"] else vars in + let starts_with s prefix = + String.length s >= String.length prefix && + String.sub s 0 (String.length prefix) = prefix in + let has_this = Ast_scope.Scope.has_this scope in let is_toplevel = Ast_scope.Scope.is_toplevel scope in (* see comment in decl_vars.ml, method on_efun of declvar_visitor @@ -360,7 +365,9 @@ let emit_body |> List.concat_map ~f:(fun item -> match item with | Ast.ClassVars { Ast.cv_names = cvl; _ } -> - List.map cvl ~f:(fun (_, (_, id), _) -> ("$" ^ id)) + List.filter_map cvl ~f:(fun (_, (_, id), _) -> + if not (starts_with id "86static_") + then Some ("$" ^ id) else None) | _ -> []) in "$0Closure" :: captured_vars @ @@ -418,6 +425,15 @@ let emit_body let generator_instr = if is_generator then gather [instr_createcont; instr_popc] else empty in + let svar_map = Static_var.make_static_map body in + let emit_expr env e = + gather [ + Emit_expression.emit_expr env ~need_ref:false e; + Emit_pos.emit_pos (fst e) + ] in + let stmt_instrs = + rewrite_static_instrseq svar_map emit_expr env stmt_instrs + in let first_instruction_is_label = match Instruction_sequence.first stmt_instrs with | Some (ILabel _) -> true @@ -447,6 +463,7 @@ let emit_body begin_label; header_content; ] in + let svar_instrs = SMap.ordered_keys svar_map in let body_instrs = gather [ header; stmt_instrs; @@ -464,6 +481,7 @@ let emit_body false (*is_memoize_wrapper_lsb*) params (Some return_type_info) + svar_instrs doc_comment (Some env), is_generator, diff --git a/hphp/hack/src/hhbc/emit_body.mli b/hphp/hack/src/hhbc/emit_body.mli index 581b8c71b0225..521d178bdae1c 100644 --- a/hphp/hack/src/hhbc/emit_body.mli +++ b/hphp/hack/src/hhbc/emit_body.mli @@ -14,6 +14,7 @@ val make_body: bool -> Hhas_param.t list -> Hhas_type_info.t option -> + string list -> string option -> Emit_env.t option -> Hhas_body.t diff --git a/hphp/hack/src/hhbc/emit_class.ml b/hphp/hack/src/hhbc/emit_class.ml index 5e1f0c219f55f..2114ae3098a6e 100644 --- a/hphp/hack/src/hhbc/emit_class.ml +++ b/hphp/hack/src/hhbc/emit_class.ml @@ -59,6 +59,7 @@ let make_86method let method_is_memoize_wrapper_lsb = false in let method_no_injection = true in let method_inout_wrapper = false in + let method_static_inits = [] in let method_doc_comment = None in let method_is_interceptable = false in let method_is_memoize_impl = false in @@ -70,6 +71,7 @@ let make_86method method_is_memoize_wrapper_lsb params method_return_type + method_static_inits method_doc_comment method_env in Hhas_method.make diff --git a/hphp/hack/src/hhbc/emit_inout_function.ml b/hphp/hack/src/hhbc/emit_inout_function.ml index 07b79ff476573..ab61311ad0112 100644 --- a/hphp/hack/src/hhbc/emit_inout_function.ml +++ b/hphp/hack/src/hhbc/emit_inout_function.ml @@ -107,6 +107,7 @@ let make_wrapper_body env doc decl_vars return_type params instrs = false (* is_memoize_wrapper_lsb *) params (Some return_type) + [] (* static_inits: this is intentionally empty *) doc (Some env) diff --git a/hphp/hack/src/hhbc/emit_memoize_function.ml b/hphp/hack/src/hhbc/emit_memoize_function.ml index db575cb0e942b..ac46ea90123a9 100644 --- a/hphp/hack/src/hhbc/emit_memoize_function.ml +++ b/hphp/hack/src/hhbc/emit_memoize_function.ml @@ -120,6 +120,7 @@ let make_wrapper_body env return_type params instrs = false (* is_memoize_wrapper_lsb *) params (Some return_type) + [] (* static_inits: this is intentionally empty *) None (* doc *) (Some env) diff --git a/hphp/hack/src/hhbc/emit_memoize_method.ml b/hphp/hack/src/hhbc/emit_memoize_method.ml index 8a327b44441f1..643263d2dbdd5 100644 --- a/hphp/hack/src/hhbc/emit_memoize_method.ml +++ b/hphp/hack/src/hhbc/emit_memoize_method.ml @@ -288,6 +288,7 @@ let make_wrapper env return_type params instrs with_lsb = with_lsb (* is_memoize_wrapper_lsb *) params (Some return_type) + [] (* static_inits *) None (* doc *) (Some env) diff --git a/hphp/hack/src/hhbc/emit_native_opcode.ml b/hphp/hack/src/hhbc/emit_native_opcode.ml index 6b2068901d46c..d1e67eb06320a 100644 --- a/hphp/hack/src/hhbc/emit_native_opcode.ml +++ b/hphp/hack/src/hhbc/emit_native_opcode.ml @@ -78,5 +78,6 @@ let emit_body scope namespace class_attrs name params ret = false params (Some return_type_info) + [] None None diff --git a/hphp/hack/src/hhbc/emit_program.ml b/hphp/hack/src/hhbc/emit_program.ml index b05dc7cfb71a9..27f1cd945a8a6 100644 --- a/hphp/hack/src/hhbc/emit_program.ml +++ b/hphp/hack/src/hhbc/emit_program.ml @@ -49,6 +49,7 @@ let emit_fatal_program ~ignore_message op pos message = false (*is_memoize_wrapper_lsb*) [] (* params *) None (* return_type_info *) + [] (* static_inits static_inits *) None (* doc *) None (* env *) in diff --git a/hphp/hack/src/hhbc/emit_statement.ml b/hphp/hack/src/hhbc/emit_statement.ml index a2c80d61bbe57..660e03ac07bf2 100644 --- a/hphp/hack/src/hhbc/emit_statement.ml +++ b/hphp/hack/src/hhbc/emit_statement.ml @@ -286,6 +286,8 @@ let rec emit_stmt env (pos, st_) = emit_foreach env pos collection await_pos iterator (pos, A.Block block) | A.Def_inline def -> emit_def_inline def + | A.Static_var es -> + emit_static_var pos es | A.Global_var es -> emit_global_vars pos es | A.Awaitall el -> @@ -359,6 +361,19 @@ and emit_global_vars p es = end in Emit_pos.emit_pos_then p @@ gather (List.rev instrs) +and emit_static_var pos es = + let emit_static_var_single e = + match snd e with + | A.Lvar (_, name) + | A.Binop (A.Eq _, (_, A.Lvar (_, name)), _) -> + gather [ + Emit_pos.emit_pos pos; + instr_static_loc_init name + ] + | _ -> failwith "Static var - impossible" + in + gather @@ List.map es ~f:emit_static_var_single + and emit_awaitall env pos el = match el with | [] -> empty diff --git a/hphp/hack/src/hhbc/hhas_body.ml b/hphp/hack/src/hhbc/hhas_body.ml index 97ea1d1f4f8c1..0d0d9e8dc48e9 100644 --- a/hphp/hack/src/hhbc/hhas_body.ml +++ b/hphp/hack/src/hhbc/hhas_body.ml @@ -17,6 +17,7 @@ type t = { body_is_memoize_wrapper_lsb: bool; body_params : Hhas_param.t list; body_return_type : Hhas_type_info.t option; + body_static_inits : string list; body_doc_comment : string option; body_env : Emit_env.t option; } @@ -30,6 +31,7 @@ let make is_memoize_wrapper_lsb params return_type + static_inits doc_comment env = { @@ -41,6 +43,7 @@ let make body_is_memoize_wrapper_lsb = is_memoize_wrapper_lsb; body_params = params; body_return_type = return_type; + body_static_inits = static_inits; body_doc_comment = doc_comment; body_env = env; } @@ -54,5 +57,6 @@ let return_type body = body.body_return_type let is_memoize_wrapper body = body.body_is_memoize_wrapper let is_memoize_wrapper_lsb body = body.body_is_memoize_wrapper_lsb let with_instrs body instrs = { body with body_instrs = instrs } +let static_inits body = body.body_static_inits let doc_comment body = body.body_doc_comment let env body = body.body_env diff --git a/hphp/hack/src/hhbc/hhbc_ast.ml b/hphp/hack/src/hhbc/hhbc_ast.ml index 619a1065c0aab..2071e2d24a2d7 100644 --- a/hphp/hack/src/hhbc/hhbc_ast.ml +++ b/hphp/hack/src/hhbc/hhbc_ast.ml @@ -468,6 +468,9 @@ type instruct_misc = | CheckThis | InitThisLoc of local_id | FuncNumArgs + | StaticLocCheck of local_id * string + | StaticLocDef of local_id * string + | StaticLocInit of local_id * string | Catch | ChainFaults | OODeclExists of class_kind diff --git a/hphp/hack/src/hhbc/hhbc_hhas.ml b/hphp/hack/src/hhbc/hhbc_hhas.ml index 436f805d6eb1f..07e43d9830ec2 100644 --- a/hphp/hack/src/hhbc/hhbc_hhas.ml +++ b/hphp/hack/src/hhbc/hhbc_hhas.ml @@ -532,6 +532,12 @@ let string_of_misc instruction = | CheckThis -> "CheckThis" | CGetCUNop -> "CGetCUNop" | UGetCUNop -> "UGetCUNop" + | StaticLocCheck (local, text) -> + sep ["StaticLocCheck"; string_of_local_id local; "\"" ^ text ^ "\""] + | StaticLocDef (local, text) -> + sep ["StaticLocDef"; string_of_local_id local; "\"" ^ text ^ "\""] + | StaticLocInit (local, text) -> + sep ["StaticLocInit"; string_of_local_id local; "\"" ^ text ^ "\""] | MemoGet (label, Some (Local.Unnamed first, local_count)) -> Printf.sprintf "MemoGet %s L:%d+%d" (string_of_label label) first local_count @@ -1354,6 +1360,13 @@ let add_num_iters buf indent num_iters = then add_indented_line buf indent (Printf.sprintf ".numiters %d;" num_iters) +let add_static_default_value_option buf indent label = + add_indented_line buf indent (".static " ^ label ^ ";") + +let add_static_values buf indent lst = + Hh_core.List.iter lst + (fun label -> add_static_default_value_option buf indent label) + let add_doc buf indent doc_comment = match doc_comment with | Some cmt -> @@ -1370,6 +1383,7 @@ let add_body buf indent body = add_num_iters buf indent (Hhas_body.num_iters body); add_num_cls_ref_slots buf indent (Hhas_body.num_cls_ref_slots body); add_decl_vars buf indent (Hhas_body.decl_vars body); + add_static_values buf indent (Hhas_body.static_inits body); Acc.add buf "\n"; add_instruction_list buf indent (Instruction_sequence.instr_seq_to_list (Hhas_body.instrs body)) diff --git a/hphp/hack/src/hhbc/hhbc_options.ml b/hphp/hack/src/hhbc/hhbc_options.ml index f403d22488d1d..058683c65913e 100644 --- a/hphp/hack/src/hhbc/hhbc_options.ml +++ b/hphp/hack/src/hhbc/hhbc_options.ml @@ -54,6 +54,7 @@ type t = { option_phpism_disallow_execution_operator: bool; option_phpism_disable_nontoplevel_declarations : bool; option_phpism_disable_static_closures : bool; + option_phpism_disable_static_local_variables : bool; option_emit_func_pointers : bool; option_emit_cls_meth_pointers : bool; option_emit_inst_meth_pointers : bool; @@ -109,6 +110,7 @@ let default = { option_phpism_disallow_execution_operator = false; option_phpism_disable_nontoplevel_declarations = false; option_phpism_disable_static_closures = false; + option_phpism_disable_static_local_variables = false; option_emit_func_pointers = true; option_emit_cls_meth_pointers = true; option_emit_inst_meth_pointers = true; @@ -160,6 +162,7 @@ let phpism_undefined_function_fallback o = o.option_phpism_undefined_function_fa let phpism_disallow_execution_operator o = o.option_phpism_disallow_execution_operator let phpism_disable_nontoplevel_declarations o = o.option_phpism_disable_nontoplevel_declarations let phpism_disable_static_closures o = o.option_phpism_disable_static_closures +let phpism_disable_static_local_variables o = o.option_phpism_disable_static_local_variables let emit_func_pointers o = o.option_emit_func_pointers let emit_cls_meth_pointers o = o.option_emit_cls_meth_pointers let emit_inst_meth_pointers o = o.option_emit_inst_meth_pointers @@ -218,6 +221,8 @@ let to_string o = @@ phpism_disable_nontoplevel_declarations o ; Printf.sprintf "phpism_disable_static_closures %B" @@ phpism_disable_static_closures o + ; Printf.sprintf "phpism_disable_static_local_variables %B" + @@ phpism_disable_static_local_variables o ; Printf.sprintf "emit_func_pointers: %B" @@ emit_func_pointers o ; Printf.sprintf "emit_cls_meth_pointers: %B" @@ emit_cls_meth_pointers o ; Printf.sprintf "emit_inst_meth_pointers: %B" @@ emit_inst_meth_pointers o @@ -311,6 +316,8 @@ let set_option options name value = { options with option_phpism_disable_nontoplevel_declarations = as_bool value } | "hack.lang.phpism.disablestaticclosures" -> { options with option_phpism_disable_static_closures = as_bool value } + | "hack.lang.phpism.disablestaticlocalvariables" -> + { options with option_phpism_disable_static_local_variables = as_bool value } | "hack.lang.enableconcurrent" -> { options with option_enable_concurrent = as_bool value } @@ -476,6 +483,8 @@ let value_setters = [ fun opts v -> { opts with option_phpism_disable_nontoplevel_declarations = (v = 1) }); (set_value "hhvm.hack.lang.phpism.disable_static_closures" get_value_from_config_int @@ fun opts v -> { opts with option_phpism_disable_static_closures = (v = 1) }); + (set_value "hhvm.hack.lang.phpism.disable_static_local_variables" get_value_from_config_int @@ + fun opts v -> { opts with option_phpism_disable_static_local_variables = (v = 1) }); (set_value "hhvm.emit_func_pointers" get_value_from_config_int @@ fun opts v -> { opts with option_emit_func_pointers = (v > 0) }); (set_value "hhvm.emit_cls_meth_pointers" get_value_from_config_int @@ diff --git a/hphp/hack/src/hhbc/instruction_sequence.ml b/hphp/hack/src/hhbc/instruction_sequence.ml index f9efe90e7e6ea..1c3072d0621b0 100644 --- a/hphp/hack/src/hhbc/instruction_sequence.ml +++ b/hphp/hack/src/hhbc/instruction_sequence.ml @@ -183,6 +183,7 @@ let instr_unbox = instr (IBasic Unbox) let instr_box = instr (IBasic Box) let instr_entrynop = instr (IBasic EntryNop) let instr_typedvalue xs = instr (ILitConst (TypedValue xs)) +let instr_staticlocinit local text = instr (IMisc (StaticLocInit(local, text))) let instr_basel local mode = instr (IBase(BaseL(local, mode))) let instr_basec stack_index mode = instr (IBase (BaseC(stack_index, mode))) let instr_basesc y mode = @@ -243,6 +244,18 @@ let instr_yieldk = instr (IGenerator YieldK) let instr_createcont = instr (IGenerator CreateCont) let instr_awaitall range = instr (IAsync (AwaitAll range)) +let instr_static_loc_check name = + instr (IMisc (StaticLocCheck (Local.Named name, + Hhbc_string_utils.Locals.strip_dollar name))) + +let instr_static_loc_def name = + instr (IMisc (StaticLocDef (Local.Named name, + Hhbc_string_utils.Locals.strip_dollar name))) + +let instr_static_loc_init name = + instr (IMisc (StaticLocInit (Local.Named name, + Hhbc_string_utils.Locals.strip_dollar name))) + let instr_exit = instr (IOp Hhbc_ast.Exit) let instr_idx = instr (IMisc Idx) let instr_array_idx = instr (IMisc ArrayIdx) @@ -622,6 +635,34 @@ let rec can_initialize_static_var e = end | _ -> false +let rewrite_static_instrseq static_var_map emit_expr env instrseq = + let rewrite_static_instr instruction = + match instruction with + | IMisc (StaticLocInit (Local.Named name, _)) -> + begin match (SMap.get name static_var_map) with + | None -> + failwith "rewrite_static_instr: No value in static map!" + | Some None -> gather [instr_null; instr_static_loc_init name;] + | Some (Some e) -> + if can_initialize_static_var e then + gather [ + emit_expr env e; + instr_static_loc_init name; + ] + else + let l = Label.next_regular () in + gather [ + instr_static_loc_check name; + instr_jmpnz l; + emit_expr env e; + instr_static_loc_def name; + instr_label l; + ] + end + | _ -> instr instruction + in + InstrSeq.flat_map_seq instrseq rewrite_static_instr + let is_srcloc i = match i with | ISrcLoc _ -> true diff --git a/hphp/hack/src/hhbc/jump_targets.ml b/hphp/hack/src/hhbc/jump_targets.ml index 19a64ee2f8508..b1f7c28c40c37 100644 --- a/hphp/hack/src/hhbc/jump_targets.ml +++ b/hphp/hack/src/hhbc/jump_targets.ml @@ -47,6 +47,7 @@ let rec collect_valid_target_labels_aux is_hh_file acc s = | A.Throw _ | A.Return _ | A.Goto _ + | A.Static_var _ | A.Global_var _ | A.Awaitall _ | A.Markup _ @@ -95,6 +96,7 @@ and collect_valid_target_labels_aux_tast is_hh_file acc s = | T.Throw _ | T.Return _ | T.Goto _ + | T.Static_var _ | T.Global_var _ | T.Awaitall _ | T.Markup _ diff --git a/hphp/hack/src/hhbc/static_var.ml b/hphp/hack/src/hhbc/static_var.ml new file mode 100644 index 0000000000000..fa5489a520278 --- /dev/null +++ b/hphp/hack/src/hhbc/static_var.ml @@ -0,0 +1,22 @@ +open Core_kernel + +module A = Ast + +class static_var_visitor = object + inherit [A.expr option SMap.t] Ast_visitor.ast_visitor + + method! on_static_var acc el = + List.fold_left el ~init:acc ~f: (fun acc e -> + match snd e with + | A.Lvar (_, name) -> SMap.add name None acc + | A.Binop (A.Eq _, (_, A.Lvar (_, name)), exp) -> SMap.add name (Some exp) acc + | _ -> failwith "Static var - impossible") + + method! on_class_ acc _ = acc + method! on_fun_ acc _ = acc + +end + +let make_static_map b = + let visitor = new static_var_visitor in + visitor#on_program (SMap.empty) b diff --git a/hphp/hack/src/naming/ast_to_nast.ml b/hphp/hack/src/naming/ast_to_nast.ml index c6e48c64bc60a..e4e3a90268ea5 100644 --- a/hphp/hack/src/naming/ast_to_nast.ml +++ b/hphp/hack/src/naming/ast_to_nast.ml @@ -362,6 +362,7 @@ and on_stmt_ p st : Aast.stmt_ = | Return e -> Aast.Return (optional on_expr e) | GotoLabel label -> Aast.GotoLabel label | Goto label -> Aast.Goto label + | Static_var el -> Aast.Static_var (on_list on_expr el) | Global_var el -> Aast.Global_var (on_list on_expr el) | Awaitall el -> Aast.Awaitall (on_list on_awaitall_expr el) | If (e, b1, b2) -> Aast.If (on_expr e, on_block b1, on_block b2) diff --git a/hphp/hack/src/naming/naming.ml b/hphp/hack/src/naming/naming.ml index 38ccc0106d84d..86de8578dd2eb 100644 --- a/hphp/hack/src/naming/naming.ml +++ b/hphp/hack/src/naming/naming.ml @@ -1826,6 +1826,7 @@ module Make (GetLocals : GetLocals) = struct | Aast.Return e -> N.Return (Option.map e (aast_expr env)) | Aast.GotoLabel label -> name_goto_label env label | Aast.Goto label -> name_goto env label + | Aast.Static_var el -> N.Static_var (aast_static_varl env el) | Aast.Global_var el -> N.Global_var (aast_global_varl env el) | Aast.Awaitall el -> aast_awaitall_stmt env el | Aast.If (e, b1, b2) -> aast_if_stmt env st e b1 b2 diff --git a/hphp/hack/src/naming/nast.ml b/hphp/hack/src/naming/nast.ml index b9eb0e09beeec..b546ab5ef7503 100644 --- a/hphp/hack/src/naming/nast.ml +++ b/hphp/hack/src/naming/nast.ml @@ -451,6 +451,7 @@ class virtual ['a] visitor: ['a] visitor_type = object(this) | Noop -> this#on_noop acc | Unsafe_block b -> this#on_unsafe_block acc b | Fallthrough -> this#on_fallthrough acc + | Static_var el -> this#on_static_var acc el | Global_var el -> this#on_global_var acc el | Awaitall el -> this#on_awaitall acc el | Def_inline d -> this#on_def_inline acc d diff --git a/hphp/hack/src/options/globalOptions.ml b/hphp/hack/src/options/globalOptions.ml index c04fc0e00634a..1a6646ad44291 100644 --- a/hphp/hack/src/options/globalOptions.ml +++ b/hphp/hack/src/options/globalOptions.ml @@ -20,6 +20,7 @@ type t = { po_disallow_execution_operator : bool; po_disable_nontoplevel_declarations : bool; po_disable_static_closures : bool; + po_disable_static_local_variables : bool; po_allow_goto: bool; po_enable_concurrent : bool; po_enable_await_as_an_expression : bool; @@ -210,6 +211,7 @@ let default = { po_deregister_php_stdlib = false; po_disable_nontoplevel_declarations = false; po_disable_static_closures = false; + po_disable_static_local_variables = false; po_allow_goto = true; po_enable_concurrent = false; po_enable_await_as_an_expression = false; @@ -250,6 +252,7 @@ let make ?(po_disallow_execution_operator = default.po_disallow_execution_operator) ?(po_disable_nontoplevel_declarations = default.po_disable_nontoplevel_declarations) ?(po_disable_static_closures = default.po_disable_static_closures) + ?(po_disable_static_local_variables = default.po_disable_static_local_variables) ?(po_allow_goto = default.po_allow_goto) ?(po_enable_concurrent = default.po_enable_concurrent) ?(po_enable_await_as_an_expression = default.po_enable_await_as_an_expression) @@ -302,6 +305,7 @@ let make po_disallow_execution_operator; po_disable_nontoplevel_declarations; po_disable_static_closures; + po_disable_static_local_variables; po_allow_goto; po_enable_concurrent; po_enable_await_as_an_expression; @@ -346,6 +350,7 @@ let po_auto_namespace_map t = t.po_auto_namespace_map let po_deregister_php_stdlib t = t.po_deregister_php_stdlib let po_disable_nontoplevel_declarations t = t.po_disable_nontoplevel_declarations let po_disable_static_closures t = t.po_disable_static_closures +let po_disable_static_local_variables t = t.po_disable_static_local_variables let po_allow_goto t = t.po_allow_goto let po_enable_concurrent t = t.po_enable_concurrent let po_enable_await_as_an_expression t = t.po_enable_await_as_an_expression diff --git a/hphp/hack/src/options/globalOptions.mli b/hphp/hack/src/options/globalOptions.mli index 60f31240a8076..4b65960a4e5ab 100644 --- a/hphp/hack/src/options/globalOptions.mli +++ b/hphp/hack/src/options/globalOptions.mli @@ -78,6 +78,9 @@ type t = { (* Flag to disable PHP's static closures *) po_disable_static_closures : bool; + (* Flag to disable PHP's static local variables *) + po_disable_static_local_variables : bool; + (* Flag to enable PHP's `goto` operator *) po_allow_goto: bool; @@ -226,6 +229,7 @@ val make : ?po_disallow_execution_operator: bool -> ?po_disable_nontoplevel_declarations: bool -> ?po_disable_static_closures: bool -> + ?po_disable_static_local_variables: bool -> ?po_allow_goto: bool -> ?po_enable_concurrent: bool -> ?po_enable_await_as_an_expression: bool -> @@ -276,6 +280,7 @@ val po_deregister_php_stdlib : t -> bool val po_disallow_execution_operator : t -> bool val po_disable_nontoplevel_declarations : t -> bool val po_disable_static_closures : t -> bool +val po_disable_static_local_variables : t -> bool val po_allow_goto : t -> bool val po_enable_concurrent : t -> bool val po_enable_await_as_an_expression : t -> bool diff --git a/hphp/hack/src/options/parserOptions.ml b/hphp/hack/src/options/parserOptions.ml index 6a57d8c7483fe..37e5f3d69e0da 100644 --- a/hphp/hack/src/options/parserOptions.ml +++ b/hphp/hack/src/options/parserOptions.ml @@ -18,6 +18,7 @@ let enable_await_as_an_expression = GlobalOptions.po_enable_await_as_an_expressi let default = GlobalOptions.default let disable_nontoplevel_declarations = GlobalOptions.po_disable_nontoplevel_declarations let disable_static_closures = GlobalOptions.po_disable_static_closures +let disable_static_local_variables = GlobalOptions.po_disable_static_local_variables let with_hh_syntax_for_hhvm po b = { po with GlobalOptions.po_enable_hh_syntax_for_hhvm = b } let with_enable_await_as_an_expression po b = @@ -37,6 +38,7 @@ let make ~disallow_execution_operator ~disable_nontoplevel_declarations ~disable_static_closures + ~disable_static_local_variables ~enable_stronger_await_binding ~disable_lval_as_an_expression = { default with @@ -47,6 +49,7 @@ let make GlobalOptions.po_disallow_execution_operator = disallow_execution_operator; GlobalOptions.po_disable_nontoplevel_declarations = disable_nontoplevel_declarations; GlobalOptions.po_disable_static_closures = disable_static_closures; + GlobalOptions.po_disable_static_local_variables = disable_static_local_variables; GlobalOptions.po_enable_stronger_await_binding = enable_stronger_await_binding; GlobalOptions.po_disable_lval_as_an_expression = disable_lval_as_an_expression; } diff --git a/hphp/hack/src/parser/ast.ml b/hphp/hack/src/parser/ast.ml index 3f2e8cf197a12..f2c63fe44e1f6 100644 --- a/hphp/hack/src/parser/ast.ml +++ b/hphp/hack/src/parser/ast.ml @@ -383,6 +383,7 @@ and stmt_ = | Return of expr option | GotoLabel of pstring | Goto of pstring + | Static_var of expr list | Global_var of expr list | If of expr * block * block | Do of block * expr diff --git a/hphp/hack/src/parser/ast_visitor.ml b/hphp/hack/src/parser/ast_visitor.ml index 4474654f1834c..dc5e446eb5633 100644 --- a/hphp/hack/src/parser/ast_visitor.ml +++ b/hphp/hack/src/parser/ast_visitor.ml @@ -377,6 +377,7 @@ class virtual ['a] ast_visitor: ['a] ast_visitor_type = object(this) this#on_def_inline acc d | Noop -> this#on_noop acc | Fallthrough -> this#on_fallthrough acc + | Static_var el -> this#on_static_var acc el | Global_var el -> this#on_global_var acc el | Awaitall el -> this#on_awaitall acc el | Markup (s, e) -> this#on_markup acc s e diff --git a/hphp/hack/src/parser/coroutine/coroutine_state_machine_generator.ml b/hphp/hack/src/parser/coroutine/coroutine_state_machine_generator.ml index a63035ff20cb3..13e60e479208a 100644 --- a/hphp/hack/src/parser/coroutine/coroutine_state_machine_generator.ml +++ b/hphp/hack/src/parser/coroutine/coroutine_state_machine_generator.ml @@ -73,6 +73,7 @@ let rec has_reachable_endpoint body = | WhileStatement _ | DoStatement _ | ForStatement _ + | FunctionStaticStatement _ | EchoStatement _ | GlobalStatement _ | _ -> true diff --git a/hphp/hack/src/parser/coroutine/coroutine_suspend_rewriter.ml b/hphp/hack/src/parser/coroutine/coroutine_suspend_rewriter.ml index 78e9b2985196c..1d8cd88c4c500 100644 --- a/hphp/hack/src/parser/coroutine/coroutine_suspend_rewriter.ml +++ b/hphp/hack/src/parser/coroutine/coroutine_suspend_rewriter.ml @@ -1254,6 +1254,7 @@ let rewrite_suspends ?(only_tail_call_suspends = false) node = | GotoStatement _ (* Suspends are invalid in goto statements. *) | BreakStatement _ (* Suspends are impossible in break statements. *) | ContinueStatement _ (* Suspends are impossible in continue statements. *) + | FunctionStaticStatement _ (* Suspends are impossible in these. *) | GlobalStatement _ (* Suspends are impossible in global statements. *) | _ -> acc, Rewriter.Result.Keep diff --git a/hphp/hack/src/parser/full_fidelity_ast.ml b/hphp/hack/src/parser/full_fidelity_ast.ml index aa7422aa54d01..dad1994ca0ee8 100644 --- a/hphp/hack/src/parser/full_fidelity_ast.ml +++ b/hphp/hack/src/parser/full_fidelity_ast.ml @@ -2200,6 +2200,29 @@ and pStmt : stmt parser = fun node env -> pBlock finally_body env | _ -> [] ) + | FunctionStaticStatement { static_declarations; _ } -> + if (ParserOptions.disable_static_local_variables env.parser_options) then + raise_parsing_error env (`Node node) + SyntaxError.static_locals_variables_are_disabled; + + let pStaticDeclarator node env = + match syntax node with + | StaticDeclarator { static_name; static_initializer } -> + let lhs = + match pExpr static_name env with + | p, Id (p', s) -> p, Lvar (p', s) + | x -> x + in + (match syntax static_initializer with + | SimpleInitializer { simple_initializer_value; _ } -> + ( pPos static_initializer env + , Binop (Eq None, lhs, pExpr simple_initializer_value env) + ) + | _ -> lhs + ) + | _ -> missing_syntax "static declarator" node env + in + pos, Static_var (couldMap ~f:pStaticDeclarator static_declarations env) | ReturnStatement { return_expression; _ } -> lift_awaits_in_statement env pos (fun () -> let expr = match syntax return_expression with diff --git a/hphp/hack/src/parser/full_fidelity_parser_errors.ml b/hphp/hack/src/parser/full_fidelity_parser_errors.ml index b99866f2c300f..2049afa566f55 100644 --- a/hphp/hack/src/parser/full_fidelity_parser_errors.ml +++ b/hphp/hack/src/parser/full_fidelity_parser_errors.ml @@ -4043,6 +4043,7 @@ let find_syntax_errors env = let errors = check_constant_expression errors init in trait_require_clauses, names, errors + | StaticDeclarator { static_initializer = init; _ } | XHPClassAttribute { xhp_attribute_decl_initializer = init; _ } -> let errors = check_constant_expression errors init in trait_require_clauses, names, errors diff --git a/hphp/hack/src/parser/full_fidelity_statement_parser.ml b/hphp/hack/src/parser/full_fidelity_statement_parser.ml index be3ffcee10d79..ef5c52b66238e 100644 --- a/hphp/hack/src/parser/full_fidelity_statement_parser.ml +++ b/hphp/hack/src/parser/full_fidelity_statement_parser.ml @@ -162,7 +162,7 @@ module WithExpressionAndDeclAndTypeParser | Throw -> parse_throw_statement parser | LeftBrace -> parse_compound_statement parser | Static -> - parse_expression_statement parser + parse_function_static_declaration_or_expression_statement parser | Echo -> parse_echo_statement parser | Global -> parse_global_statement_or_expression_statement parser | Concurrent -> parse_concurrent_statement parser @@ -1141,11 +1141,61 @@ module WithExpressionAndDeclAndTypeParser else parse_expression_statement parser + and parse_function_static_declaration_or_expression_statement parser = + (* Determine if the current token is a late-bound static scope to be + * resolved by the '::' operator. (E.g., "static::foo".) + *) + if Token.kind (peek_token ~lookahead:1 parser) == TokenKind.ColonColon then + parse_expression_statement parser + else + parse_function_static_declaration parser + and parse_concurrent_statement parser = let (parser1, keyword) = assert_token parser Concurrent in let (parser_body, statement) = parse_statement parser1 in Make.concurrent_statement parser_body keyword statement + and parse_function_static_declaration parser = + (* SPEC + + function-static-declaration: + static static-declarator-list ; + + static-declarator-list: + static-declarator + static-declarator-list , static-declarator + + *) + let (parser, static) = assert_token parser Static in + let (parser, decls) = parse_comma_list + parser Semicolon SyntaxError.error1008 parse_static_declarator in + let (parser, semicolon) = require_semicolon parser in + Make.function_static_statement parser static decls semicolon + + and parse_static_declarator parser = + (* SPEC + static-declarator: + variable-name function-static-initializer-opt + *) + (* TODO: ERROR RECOVERY not very sophisticated here *) + let (parser, variable_name) = require_variable parser in + let (parser, init) = parse_static_initializer_opt parser in + Make.static_declarator parser variable_name init + + and parse_static_initializer_opt parser = + (* SPEC + function-static-initializer: + = const-expression + *) + let (parser1, token) = next_token parser in + match (Token.kind token) with + | Equal -> + (* TODO: Detect if expression is not const *) + let (parser, equal) = Make.token parser1 token in + let (parser, value) = parse_expression parser in + Make.simple_initializer parser equal value + | _ -> Make.missing parser (pos parser) + (* SPEC: TODO: update the spec to reflect that echo and print must be a statement echo-intrinsic: diff --git a/hphp/hack/src/parser/full_fidelity_syntax.ml b/hphp/hack/src/parser/full_fidelity_syntax.ml index 4988a291fef60..6e2ee6fbe8dc9 100644 --- a/hphp/hack/src/parser/full_fidelity_syntax.ml +++ b/hphp/hack/src/parser/full_fidelity_syntax.ml @@ -138,6 +138,8 @@ module WithToken(Token: TokenType) = struct | ThrowStatement _ -> SyntaxKind.ThrowStatement | BreakStatement _ -> SyntaxKind.BreakStatement | ContinueStatement _ -> SyntaxKind.ContinueStatement + | FunctionStaticStatement _ -> SyntaxKind.FunctionStaticStatement + | StaticDeclarator _ -> SyntaxKind.StaticDeclarator | EchoStatement _ -> SyntaxKind.EchoStatement | GlobalStatement _ -> SyntaxKind.GlobalStatement | ConcurrentStatement _ -> SyntaxKind.ConcurrentStatement @@ -329,6 +331,8 @@ module WithToken(Token: TokenType) = struct let is_throw_statement = has_kind SyntaxKind.ThrowStatement let is_break_statement = has_kind SyntaxKind.BreakStatement let is_continue_statement = has_kind SyntaxKind.ContinueStatement + let is_function_static_statement = has_kind SyntaxKind.FunctionStaticStatement + let is_static_declarator = has_kind SyntaxKind.StaticDeclarator let is_echo_statement = has_kind SyntaxKind.EchoStatement let is_global_statement = has_kind SyntaxKind.GlobalStatement let is_concurrent_statement = has_kind SyntaxKind.ConcurrentStatement @@ -1386,6 +1390,22 @@ module WithToken(Token: TokenType) = struct let acc = f acc continue_level in let acc = f acc continue_semicolon in acc + | FunctionStaticStatement { + static_static_keyword; + static_declarations; + static_semicolon; + } -> + let acc = f acc static_static_keyword in + let acc = f acc static_declarations in + let acc = f acc static_semicolon in + acc + | StaticDeclarator { + static_name; + static_initializer; + } -> + let acc = f acc static_name in + let acc = f acc static_initializer in + acc | EchoStatement { echo_keyword; echo_expressions; @@ -3336,6 +3356,22 @@ module WithToken(Token: TokenType) = struct continue_level; continue_semicolon; ] + | FunctionStaticStatement { + static_static_keyword; + static_declarations; + static_semicolon; + } -> [ + static_static_keyword; + static_declarations; + static_semicolon; + ] + | StaticDeclarator { + static_name; + static_initializer; + } -> [ + static_name; + static_initializer; + ] | EchoStatement { echo_keyword; echo_expressions; @@ -5287,6 +5323,22 @@ module WithToken(Token: TokenType) = struct "continue_level"; "continue_semicolon"; ] + | FunctionStaticStatement { + static_static_keyword; + static_declarations; + static_semicolon; + } -> [ + "static_static_keyword"; + "static_declarations"; + "static_semicolon"; + ] + | StaticDeclarator { + static_name; + static_initializer; + } -> [ + "static_name"; + "static_initializer"; + ] | EchoStatement { echo_keyword; echo_expressions; @@ -7371,6 +7423,24 @@ module WithToken(Token: TokenType) = struct continue_level; continue_semicolon; } + | (SyntaxKind.FunctionStaticStatement, [ + static_static_keyword; + static_declarations; + static_semicolon; + ]) -> + FunctionStaticStatement { + static_static_keyword; + static_declarations; + static_semicolon; + } + | (SyntaxKind.StaticDeclarator, [ + static_name; + static_initializer; + ]) -> + StaticDeclarator { + static_name; + static_initializer; + } | (SyntaxKind.EchoStatement, [ echo_keyword; echo_expressions; @@ -9772,6 +9842,30 @@ module WithToken(Token: TokenType) = struct let value = ValueBuilder.value_from_syntax syntax in make syntax value + let make_function_static_statement + static_static_keyword + static_declarations + static_semicolon + = + let syntax = FunctionStaticStatement { + static_static_keyword; + static_declarations; + static_semicolon; + } in + let value = ValueBuilder.value_from_syntax syntax in + make syntax value + + let make_static_declarator + static_name + static_initializer + = + let syntax = StaticDeclarator { + static_name; + static_initializer; + } in + let value = ValueBuilder.value_from_syntax syntax in + make syntax value + let make_echo_statement echo_keyword echo_expressions diff --git a/hphp/hack/src/parser/full_fidelity_syntax_error.ml b/hphp/hack/src/parser/full_fidelity_syntax_error.ml index 38eac9dbd08ea..56a24bd55b80a 100644 --- a/hphp/hack/src/parser/full_fidelity_syntax_error.ml +++ b/hphp/hack/src/parser/full_fidelity_syntax_error.ml @@ -679,6 +679,9 @@ let concurrent_is_disabled = let static_closures_are_disabled = "Static closures are not supported in Hack" +let static_locals_variables_are_disabled = + "Static local variables are not supported in Hack" + let invalid_await_position = "Await cannot be used as an expression in this " ^ "location because it's conditionally executed." diff --git a/hphp/hack/src/parser/full_fidelity_syntax_error.mli b/hphp/hack/src/parser/full_fidelity_syntax_error.mli index f6d76486b422b..48d7a952792ae 100644 --- a/hphp/hack/src/parser/full_fidelity_syntax_error.mli +++ b/hphp/hack/src/parser/full_fidelity_syntax_error.mli @@ -346,6 +346,7 @@ val invalid_syntax_concurrent_block : string val statement_without_await_in_concurrent_block : string val concurrent_is_disabled : string val static_closures_are_disabled : string +val static_locals_variables_are_disabled : string val invalid_await_position : string val misplaced_reactivity_annotation : string val mutability_annotation_on_static_method : string diff --git a/hphp/hack/src/parser/full_fidelity_syntax_kind.ml b/hphp/hack/src/parser/full_fidelity_syntax_kind.ml index 3e3c27a8f047d..d937c3341ea0e 100644 --- a/hphp/hack/src/parser/full_fidelity_syntax_kind.ml +++ b/hphp/hack/src/parser/full_fidelity_syntax_kind.ml @@ -99,6 +99,8 @@ type t = | ThrowStatement | BreakStatement | ContinueStatement + | FunctionStaticStatement + | StaticDeclarator | EchoStatement | GlobalStatement | ConcurrentStatement @@ -284,6 +286,8 @@ let to_string kind = | ThrowStatement -> "throw_statement" | BreakStatement -> "break_statement" | ContinueStatement -> "continue_statement" + | FunctionStaticStatement -> "function_static_statement" + | StaticDeclarator -> "static_declarator" | EchoStatement -> "echo_statement" | GlobalStatement -> "global_statement" | ConcurrentStatement -> "concurrent_statement" diff --git a/hphp/hack/src/parser/full_fidelity_syntax_type.ml b/hphp/hack/src/parser/full_fidelity_syntax_type.ml index 6f4ec0670046f..91f652076d733 100644 --- a/hphp/hack/src/parser/full_fidelity_syntax_type.ml +++ b/hphp/hack/src/parser/full_fidelity_syntax_type.ml @@ -623,6 +623,15 @@ module MakeSyntaxType(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct ; continue_level : t ; continue_semicolon : t } + | FunctionStaticStatement of + { static_static_keyword : t + ; static_declarations : t + ; static_semicolon : t + } + | StaticDeclarator of + { static_name : t + ; static_initializer : t + } | EchoStatement of { echo_keyword : t ; echo_expressions : t @@ -1252,6 +1261,7 @@ module MakeValidated(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | TLDThrow of throw_statement | TLDBreak of break_statement | TLDContinue of continue_statement + | TLDFunctionStatic of function_static_statement | TLDEcho of echo_statement | TLDGlobal of global_statement and expression = @@ -1373,6 +1383,7 @@ module MakeValidated(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | StmtThrow of throw_statement | StmtBreak of break_statement | StmtContinue of continue_statement + | StmtFunctionStatic of function_static_statement | StmtEcho of echo_statement | StmtGlobal of global_statement | StmtConcurrent of concurrent_statement @@ -1987,6 +1998,15 @@ module MakeValidated(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct ; continue_level: literal_expression option value ; continue_semicolon: Token.t value } + and function_static_statement = + { static_static_keyword: Token.t value + ; static_declarations: static_declarator listesque value + ; static_semicolon: Token.t value + } + and static_declarator = + { static_name: Token.t value + ; static_initializer: simple_initializer option value + } and echo_statement = { echo_keyword: Token.t value ; echo_expressions: expression listesque value diff --git a/hphp/hack/src/parser/full_fidelity_validated_syntax.ml b/hphp/hack/src/parser/full_fidelity_validated_syntax.ml index faf587bb04c2f..e39857e4e046c 100644 --- a/hphp/hack/src/parser/full_fidelity_validated_syntax.ml +++ b/hphp/hack/src/parser/full_fidelity_validated_syntax.ml @@ -145,6 +145,7 @@ module Make(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | Syntax.ThrowStatement _ -> tag validate_throw_statement (fun x -> TLDThrow x) x | Syntax.BreakStatement _ -> tag validate_break_statement (fun x -> TLDBreak x) x | Syntax.ContinueStatement _ -> tag validate_continue_statement (fun x -> TLDContinue x) x + | Syntax.FunctionStaticStatement _ -> tag validate_function_static_statement (fun x -> TLDFunctionStatic x) x | Syntax.EchoStatement _ -> tag validate_echo_statement (fun x -> TLDEcho x) x | Syntax.GlobalStatement _ -> tag validate_global_statement (fun x -> TLDGlobal x) x | s -> aggregation_fail Def.TopLevelDeclaration s @@ -185,6 +186,7 @@ module Make(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | TLDThrow thing -> invalidate_throw_statement (value, thing) | TLDBreak thing -> invalidate_break_statement (value, thing) | TLDContinue thing -> invalidate_continue_statement (value, thing) + | TLDFunctionStatic thing -> invalidate_function_static_statement (value, thing) | TLDEcho thing -> invalidate_echo_statement (value, thing) | TLDGlobal thing -> invalidate_global_statement (value, thing) and validate_expression : expression validator = fun x -> @@ -409,6 +411,7 @@ module Make(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | Syntax.ThrowStatement _ -> tag validate_throw_statement (fun x -> StmtThrow x) x | Syntax.BreakStatement _ -> tag validate_break_statement (fun x -> StmtBreak x) x | Syntax.ContinueStatement _ -> tag validate_continue_statement (fun x -> StmtContinue x) x + | Syntax.FunctionStaticStatement _ -> tag validate_function_static_statement (fun x -> StmtFunctionStatic x) x | Syntax.EchoStatement _ -> tag validate_echo_statement (fun x -> StmtEcho x) x | Syntax.GlobalStatement _ -> tag validate_global_statement (fun x -> StmtGlobal x) x | Syntax.ConcurrentStatement _ -> tag validate_concurrent_statement (fun x -> StmtConcurrent x) x @@ -444,6 +447,7 @@ module Make(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct | StmtThrow thing -> invalidate_throw_statement (value, thing) | StmtBreak thing -> invalidate_break_statement (value, thing) | StmtContinue thing -> invalidate_continue_statement (value, thing) + | StmtFunctionStatic thing -> invalidate_function_static_statement (value, thing) | StmtEcho thing -> invalidate_echo_statement (value, thing) | StmtGlobal thing -> invalidate_global_statement (value, thing) | StmtConcurrent thing -> invalidate_concurrent_statement (value, thing) @@ -2167,6 +2171,36 @@ module Make(Token : TokenType)(SyntaxValue : SyntaxValueType) = struct } ; Syntax.value = v } + and validate_function_static_statement : function_static_statement validator = function + | { Syntax.syntax = Syntax.FunctionStaticStatement x; value = v } -> v, + { static_semicolon = validate_token x.static_semicolon + ; static_declarations = validate_list_with (validate_static_declarator) x.static_declarations + ; static_static_keyword = validate_token x.static_static_keyword + } + | s -> validation_fail (Some SyntaxKind.FunctionStaticStatement) s + and invalidate_function_static_statement : function_static_statement invalidator = fun (v, x) -> + { Syntax.syntax = + Syntax.FunctionStaticStatement + { static_static_keyword = invalidate_token x.static_static_keyword + ; static_declarations = invalidate_list_with (invalidate_static_declarator) x.static_declarations + ; static_semicolon = invalidate_token x.static_semicolon + } + ; Syntax.value = v + } + and validate_static_declarator : static_declarator validator = function + | { Syntax.syntax = Syntax.StaticDeclarator x; value = v } -> v, + { static_initializer = validate_option_with (validate_simple_initializer) x.static_initializer + ; static_name = validate_token x.static_name + } + | s -> validation_fail (Some SyntaxKind.StaticDeclarator) s + and invalidate_static_declarator : static_declarator invalidator = fun (v, x) -> + { Syntax.syntax = + Syntax.StaticDeclarator + { static_name = invalidate_token x.static_name + ; static_initializer = invalidate_option_with (invalidate_simple_initializer) x.static_initializer + } + ; Syntax.value = v + } and validate_echo_statement : echo_statement validator = function | { Syntax.syntax = Syntax.EchoStatement x; value = v } -> v, { echo_semicolon = validate_token x.echo_semicolon diff --git a/hphp/hack/src/parser/js/full_fidelity_editable.js b/hphp/hack/src/parser/js/full_fidelity_editable.js index 667188b1fc6f7..229e57fa5f09b 100644 --- a/hphp/hack/src/parser/js/full_fidelity_editable.js +++ b/hphp/hack/src/parser/js/full_fidelity_editable.js @@ -240,6 +240,10 @@ class EditableSyntax return BreakStatement.from_json(json, position, source); case 'continue_statement': return ContinueStatement.from_json(json, position, source); + case 'function_static_statement': + return FunctionStaticStatement.from_json(json, position, source); + case 'static_declarator': + return StaticDeclarator.from_json(json, position, source); case 'echo_statement': return EchoStatement.from_json(json, position, source); case 'global_statement': @@ -11846,6 +11850,153 @@ class ContinueStatement extends EditableSyntax return ContinueStatement._children_keys; } } +class FunctionStaticStatement extends EditableSyntax +{ + constructor( + static_keyword, + declarations, + semicolon) + { + super('function_static_statement', { + static_keyword: static_keyword, + declarations: declarations, + semicolon: semicolon }); + } + get static_keyword() { return this.children.static_keyword; } + get declarations() { return this.children.declarations; } + get semicolon() { return this.children.semicolon; } + with_static_keyword(static_keyword){ + return new FunctionStaticStatement( + static_keyword, + this.declarations, + this.semicolon); + } + with_declarations(declarations){ + return new FunctionStaticStatement( + this.static_keyword, + declarations, + this.semicolon); + } + with_semicolon(semicolon){ + return new FunctionStaticStatement( + this.static_keyword, + this.declarations, + semicolon); + } + rewrite(rewriter, parents) + { + if (parents == undefined) + parents = []; + let new_parents = parents.slice(); + new_parents.push(this); + var static_keyword = this.static_keyword.rewrite(rewriter, new_parents); + var declarations = this.declarations.rewrite(rewriter, new_parents); + var semicolon = this.semicolon.rewrite(rewriter, new_parents); + if ( + static_keyword === this.static_keyword && + declarations === this.declarations && + semicolon === this.semicolon) + { + return rewriter(this, parents); + } + else + { + return rewriter(new FunctionStaticStatement( + static_keyword, + declarations, + semicolon), parents); + } + } + static from_json(json, position, source) + { + let static_keyword = EditableSyntax.from_json( + json.static_static_keyword, position, source); + position += static_keyword.width; + let declarations = EditableSyntax.from_json( + json.static_declarations, position, source); + position += declarations.width; + let semicolon = EditableSyntax.from_json( + json.static_semicolon, position, source); + position += semicolon.width; + return new FunctionStaticStatement( + static_keyword, + declarations, + semicolon); + } + get children_keys() + { + if (FunctionStaticStatement._children_keys == null) + FunctionStaticStatement._children_keys = [ + 'static_keyword', + 'declarations', + 'semicolon']; + return FunctionStaticStatement._children_keys; + } +} +class StaticDeclarator extends EditableSyntax +{ + constructor( + name, + initializer) + { + super('static_declarator', { + name: name, + initializer: initializer }); + } + get name() { return this.children.name; } + get initializer() { return this.children.initializer; } + with_name(name){ + return new StaticDeclarator( + name, + this.initializer); + } + with_initializer(initializer){ + return new StaticDeclarator( + this.name, + initializer); + } + rewrite(rewriter, parents) + { + if (parents == undefined) + parents = []; + let new_parents = parents.slice(); + new_parents.push(this); + var name = this.name.rewrite(rewriter, new_parents); + var initializer = this.initializer.rewrite(rewriter, new_parents); + if ( + name === this.name && + initializer === this.initializer) + { + return rewriter(this, parents); + } + else + { + return rewriter(new StaticDeclarator( + name, + initializer), parents); + } + } + static from_json(json, position, source) + { + let name = EditableSyntax.from_json( + json.static_name, position, source); + position += name.width; + let initializer = EditableSyntax.from_json( + json.static_initializer, position, source); + position += initializer.width; + return new StaticDeclarator( + name, + initializer); + } + get children_keys() + { + if (StaticDeclarator._children_keys == null) + StaticDeclarator._children_keys = [ + 'name', + 'initializer']; + return StaticDeclarator._children_keys; + } +} class EchoStatement extends EditableSyntax { constructor( @@ -22483,6 +22634,8 @@ exports.GotoStatement = GotoStatement; exports.ThrowStatement = ThrowStatement; exports.BreakStatement = BreakStatement; exports.ContinueStatement = ContinueStatement; +exports.FunctionStaticStatement = FunctionStaticStatement; +exports.StaticDeclarator = StaticDeclarator; exports.EchoStatement = EchoStatement; exports.GlobalStatement = GlobalStatement; exports.ConcurrentStatement = ConcurrentStatement; diff --git a/hphp/hack/src/parser/js/full_fidelity_schema.json b/hphp/hack/src/parser/js/full_fidelity_schema.json index 50f0f121d2b2a..f35ccdf9f4998 100644 --- a/hphp/hack/src/parser/js/full_fidelity_schema.json +++ b/hphp/hack/src/parser/js/full_fidelity_schema.json @@ -1247,6 +1247,23 @@ { "field_name" : "level" }, { "field_name" : "semicolon" } ] }, + { "kind_name" : "FunctionStaticStatement", + "type_name" : "function_static_statement", + "description" : "function_static_statement", + "prefix" : "static", + "fields" : [ + { "field_name" : "static_keyword" }, + { "field_name" : "declarations" }, + { "field_name" : "semicolon" } + ] }, + { "kind_name" : "StaticDeclarator", + "type_name" : "static_declarator", + "description" : "static_declarator", + "prefix" : "static", + "fields" : [ + { "field_name" : "name" }, + { "field_name" : "initializer" } + ] }, { "kind_name" : "EchoStatement", "type_name" : "echo_statement", "description" : "echo_statement", diff --git a/hphp/hack/src/parser/parserSig.ml b/hphp/hack/src/parser/parserSig.ml index 8bce8696e6344..1a6d83e7f1d6d 100644 --- a/hphp/hack/src/parser/parserSig.ml +++ b/hphp/hack/src/parser/parserSig.ml @@ -127,6 +127,8 @@ module WithSyntax(Syntax : Syntax_sig.Syntax_S) = struct val throw_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r val break_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r val continue_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r + val function_static_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r + val static_declarator : t -> SC.r -> SC.r -> t * SC.r val echo_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r val global_statement : t -> SC.r -> SC.r -> SC.r -> t * SC.r val concurrent_statement : t -> SC.r -> SC.r -> t * SC.r diff --git a/hphp/hack/src/parser/schema/schema_definition.ml b/hphp/hack/src/parser/schema/schema_definition.ml index 9b9ea6922dbd9..19a8d0630acfd 100644 --- a/hphp/hack/src/parser/schema/schema_definition.ml +++ b/hphp/hack/src/parser/schema/schema_definition.ml @@ -1053,6 +1053,29 @@ let schema : schema_node list = ; "semicolon", Token ] } + ; { kind_name = "FunctionStaticStatement" + ; type_name = "function_static_statement" + ; func_name = "function_static_statement" + ; description = "function_static_statement" + ; prefix = "static" + ; aggregates = [ TopLevelDeclaration; Statement ] + ; fields = + [ "static_keyword", Token + ; "declarations", ZeroOrMore (Just "StaticDeclarator") + ; "semicolon", Token + ] + } + ; { kind_name = "StaticDeclarator" + ; type_name = "static_declarator" + ; func_name = "static_declarator" + ; description = "static_declarator" + ; prefix = "static" + ; aggregates = [] + ; fields = + [ "name", Token + ; "initializer", ZeroOrOne (Just "SimpleInitializer") + ] + } ; { kind_name = "EchoStatement" ; type_name = "echo_statement" ; func_name = "echo_statement" diff --git a/hphp/hack/src/parser/smart_constructors/smartConstructors.ml b/hphp/hack/src/parser/smart_constructors/smartConstructors.ml index 5393a4877d349..e3b4cf453f867 100644 --- a/hphp/hack/src/parser/smart_constructors/smartConstructors.ml +++ b/hphp/hack/src/parser/smart_constructors/smartConstructors.ml @@ -109,6 +109,8 @@ module type SmartConstructors_S = sig val make_throw_statement : r -> r -> r -> t -> t * r val make_break_statement : r -> r -> r -> t -> t * r val make_continue_statement : r -> r -> r -> t -> t * r + val make_function_static_statement : r -> r -> r -> t -> t * r + val make_static_declarator : r -> r -> t -> t * r val make_echo_statement : r -> r -> r -> t -> t * r val make_global_statement : r -> r -> r -> t -> t * r val make_concurrent_statement : r -> r -> t -> t * r @@ -301,6 +303,8 @@ end) = struct let throw_statement parser arg0 arg1 arg2 = call parser (SCI.make_throw_statement arg0 arg1 arg2) let break_statement parser arg0 arg1 arg2 = call parser (SCI.make_break_statement arg0 arg1 arg2) let continue_statement parser arg0 arg1 arg2 = call parser (SCI.make_continue_statement arg0 arg1 arg2) + let function_static_statement parser arg0 arg1 arg2 = call parser (SCI.make_function_static_statement arg0 arg1 arg2) + let static_declarator parser arg0 arg1 = call parser (SCI.make_static_declarator arg0 arg1) let echo_statement parser arg0 arg1 arg2 = call parser (SCI.make_echo_statement arg0 arg1 arg2) let global_statement parser arg0 arg1 arg2 = call parser (SCI.make_global_statement arg0 arg1 arg2) let concurrent_statement parser arg0 arg1 = call parser (SCI.make_concurrent_statement arg0 arg1) diff --git a/hphp/hack/src/parser/smart_constructors/smartConstructorsWrappers.ml b/hphp/hack/src/parser/smart_constructors/smartConstructorsWrappers.ml index d8c3bef7fc6d8..73c2f610ded8d 100644 --- a/hphp/hack/src/parser/smart_constructors/smartConstructorsWrappers.ml +++ b/hphp/hack/src/parser/smart_constructors/smartConstructorsWrappers.ml @@ -111,6 +111,8 @@ module type SyntaxKind_S = sig val is_throw_statement : r -> bool val is_break_statement : r -> bool val is_continue_statement : r -> bool + val is_function_static_statement : r -> bool + val is_static_declarator : r -> bool val is_echo_statement : r -> bool val is_global_statement : r -> bool val is_concurrent_statement : r -> bool @@ -313,6 +315,8 @@ module SyntaxKind(SC : SC_S) let make_throw_statement arg0 arg1 arg2 state = compose SK.ThrowStatement (SC.make_throw_statement (snd arg0) (snd arg1) (snd arg2) state) let make_break_statement arg0 arg1 arg2 state = compose SK.BreakStatement (SC.make_break_statement (snd arg0) (snd arg1) (snd arg2) state) let make_continue_statement arg0 arg1 arg2 state = compose SK.ContinueStatement (SC.make_continue_statement (snd arg0) (snd arg1) (snd arg2) state) + let make_function_static_statement arg0 arg1 arg2 state = compose SK.FunctionStaticStatement (SC.make_function_static_statement (snd arg0) (snd arg1) (snd arg2) state) + let make_static_declarator arg0 arg1 state = compose SK.StaticDeclarator (SC.make_static_declarator (snd arg0) (snd arg1) state) let make_echo_statement arg0 arg1 arg2 state = compose SK.EchoStatement (SC.make_echo_statement (snd arg0) (snd arg1) (snd arg2) state) let make_global_statement arg0 arg1 arg2 state = compose SK.GlobalStatement (SC.make_global_statement (snd arg0) (snd arg1) (snd arg2) state) let make_concurrent_statement arg0 arg1 state = compose SK.ConcurrentStatement (SC.make_concurrent_statement (snd arg0) (snd arg1) state) @@ -497,6 +501,8 @@ module SyntaxKind(SC : SC_S) let is_throw_statement = has_kind SK.ThrowStatement let is_break_statement = has_kind SK.BreakStatement let is_continue_statement = has_kind SK.ContinueStatement + let is_function_static_statement = has_kind SK.FunctionStaticStatement + let is_static_declarator = has_kind SK.StaticDeclarator let is_echo_statement = has_kind SK.EchoStatement let is_global_statement = has_kind SK.GlobalStatement let is_concurrent_statement = has_kind SK.ConcurrentStatement diff --git a/hphp/hack/src/parser/smart_constructors/syntaxSmartConstructors.ml b/hphp/hack/src/parser/smart_constructors/syntaxSmartConstructors.ml index 12813596db0d3..730523d08f45f 100644 --- a/hphp/hack/src/parser/smart_constructors/syntaxSmartConstructors.ml +++ b/hphp/hack/src/parser/smart_constructors/syntaxSmartConstructors.ml @@ -123,6 +123,8 @@ module WithSyntax(Syntax : Syntax_sig.Syntax_S) = struct let make_throw_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_throw_statement arg0 arg1 arg2 let make_break_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_break_statement arg0 arg1 arg2 let make_continue_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_continue_statement arg0 arg1 arg2 + let make_function_static_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_function_static_statement arg0 arg1 arg2 + let make_static_declarator arg0 arg1 state = State.next state [arg0; arg1], Syntax.make_static_declarator arg0 arg1 let make_echo_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_echo_statement arg0 arg1 arg2 let make_global_statement arg0 arg1 arg2 state = State.next state [arg0; arg1; arg2], Syntax.make_global_statement arg0 arg1 arg2 let make_concurrent_statement arg0 arg1 state = State.next state [arg0; arg1], Syntax.make_concurrent_statement arg0 arg1 diff --git a/hphp/hack/src/parser/smart_constructors/verifySmartConstructors.ml b/hphp/hack/src/parser/smart_constructors/verifySmartConstructors.ml index ecf40ea758e32..00fe740096b9c 100644 --- a/hphp/hack/src/parser/smart_constructors/verifySmartConstructors.ml +++ b/hphp/hack/src/parser/smart_constructors/verifySmartConstructors.ml @@ -695,6 +695,22 @@ module WithSyntax(Syntax : Syntax_sig.Syntax_S) = struct node :: rem, node | _ -> failwith "Unexpected stack state" + let make_function_static_statement p0 p1 p2 stack = + match stack with + | a2 :: a1 :: a0 :: rem -> + let () = verify ~stack [p0; p1; p2] [a0; a1; a2] "function_static_statement" in + let node = Syntax.make_function_static_statement p0 p1 p2 in + node :: rem, node + | _ -> failwith "Unexpected stack state" + + let make_static_declarator p0 p1 stack = + match stack with + | a1 :: a0 :: rem -> + let () = verify ~stack [p0; p1] [a0; a1] "static_declarator" in + let node = Syntax.make_static_declarator p0 p1 in + node :: rem, node + | _ -> failwith "Unexpected stack state" + let make_echo_statement p0 p1 p2 stack = match stack with | a2 :: a1 :: a0 :: rem -> diff --git a/hphp/hack/src/parser/syntax_sig.ml b/hphp/hack/src/parser/syntax_sig.ml index 4f717d98f8c51..13eabd5ae8d2c 100644 --- a/hphp/hack/src/parser/syntax_sig.ml +++ b/hphp/hack/src/parser/syntax_sig.ml @@ -510,6 +510,15 @@ module type Syntax_S = sig ; continue_level : t ; continue_semicolon : t } + | FunctionStaticStatement of + { static_static_keyword : t + ; static_declarations : t + ; static_semicolon : t + } + | StaticDeclarator of + { static_name : t + ; static_initializer : t + } | EchoStatement of { echo_keyword : t ; echo_expressions : t @@ -1191,6 +1200,8 @@ module type Syntax_S = sig val make_throw_statement : t -> t -> t -> t val make_break_statement : t -> t -> t -> t val make_continue_statement : t -> t -> t -> t + val make_function_static_statement : t -> t -> t -> t + val make_static_declarator : t -> t -> t val make_echo_statement : t -> t -> t -> t val make_global_statement : t -> t -> t -> t val make_concurrent_statement : t -> t -> t @@ -1374,6 +1385,8 @@ module type Syntax_S = sig val is_throw_statement : t -> bool val is_break_statement : t -> bool val is_continue_statement : t -> bool + val is_function_static_statement : t -> bool + val is_static_declarator : t -> bool val is_echo_statement : t -> bool val is_global_statement : t -> bool val is_concurrent_statement : t -> bool diff --git a/hphp/hack/src/server/serverConfig.ml b/hphp/hack/src/server/serverConfig.ml index 3f722fdba9b93..1b6d7b9f968d5 100644 --- a/hphp/hack/src/server/serverConfig.ml +++ b/hphp/hack/src/server/serverConfig.ml @@ -255,6 +255,7 @@ let load config_filename options = ?po_enable_await_as_an_expression:(bool_opt "enable_await_as_an_expression" config) ?po_allow_goto:(Option.map ~f:not (bool_opt "disallow_goto" config)) ?po_disable_static_closures:(bool_opt "disable_static_closures" config) + ?po_disable_static_local_variables:(bool_opt "disable_static_local_variables" config) ?tco_disallow_array_as_tuple:(bool_opt "disallow_array_as_tuple" config) ?tco_disallow_ambiguous_lambda:(bool_opt "disallow_ambiguous_lambda" config) ?tco_disallow_array_typehint:(bool_opt "disallow_array_typehint" config) diff --git a/hphp/hack/src/typing/nastCheck.ml b/hphp/hack/src/typing/nastCheck.ml index 49eda0c3f9738..0e3583dd4e577 100644 --- a/hphp/hack/src/typing/nastCheck.ml +++ b/hphp/hack/src/typing/nastCheck.ml @@ -68,7 +68,7 @@ module CheckFunctionBody = struct expr_allow_await_or_rx_move f_type env e; () | _, ( Noop | Fallthrough | GotoLabel _ | Goto _ | Break | Continue - | Global_var _ | Unsafe_block _ ) -> () + | Static_var _ | Global_var _ | Unsafe_block _ ) -> () | _, Awaitall el -> List.iter el (fun (_, y) -> expr f_type env y; @@ -595,6 +595,8 @@ and stmt_ env = function | Return (Some e) | Expr e | Throw (_, e) -> expr env e + | Static_var _ -> + () | Global_var _ -> () | Awaitall el -> diff --git a/hphp/hack/src/typing/nastInitCheck.ml b/hphp/hack/src/typing/nastInitCheck.ml index a701fa0e9d71c..e111dc764bfa8 100644 --- a/hphp/hack/src/typing/nastInitCheck.ml +++ b/hphp/hack/src/typing/nastInitCheck.ml @@ -280,6 +280,7 @@ and stmt env acc st = if are_all_init env acc then acc else raise (InitReturn acc) + | Static_var el | Global_var el -> List.fold_left ~f:expr ~init:acc el | Awaitall el -> diff --git a/hphp/hack/src/typing/tast_check/basic_reactivity_check.ml b/hphp/hack/src/typing/tast_check/basic_reactivity_check.ml index 9e873dd6ef046..61faacc0aa93f 100644 --- a/hphp/hack/src/typing/tast_check/basic_reactivity_check.ml +++ b/hphp/hack/src/typing/tast_check/basic_reactivity_check.ml @@ -463,6 +463,10 @@ let check = object(self) disallow_static_or_global ~is_static:false el; super#on_Global_var s el + method! on_Static_var s el = + disallow_static_or_global ~is_static:true el; + super#on_Static_var s el + method! on_Awaitall (env, ctx) els = let allow_awaitable_s = (env, allow_awaitable ctx) in List.iter els ~f:(fun (_, rhs) -> diff --git a/hphp/hack/src/typing/tast_typecheck.ml b/hphp/hack/src/typing/tast_typecheck.ml index 8a758abaca129..f7661c24a4856 100644 --- a/hphp/hack/src/typing/tast_typecheck.ml +++ b/hphp/hack/src/typing/tast_typecheck.ml @@ -179,6 +179,7 @@ let rec check_stmt env (stmt:ETast.stmt) (gamma:gamma) : delta = | Using _ | Def_inline _ | Let _ + | Static_var _ | Global_var _ | Awaitall _ -> raise Not_implemented diff --git a/hphp/hack/src/typing/typing.ml b/hphp/hack/src/typing/typing.ml index 370f98f1ef899..d0c466497dff1 100644 --- a/hphp/hack/src/typing/typing.ml +++ b/hphp/hack/src/typing/typing.ml @@ -923,6 +923,15 @@ and stmt_ env pos st = | Def_inline _ -> (* Do nothing, this doesn't occur in Hack code. *) failwith "Should never typecheck nested definitions" + | Static_var el -> + let env = List.fold_left el ~f:begin fun env e -> + match e with + | _, Binop (Ast.Eq _, (_, Lvar (p, x)), _) -> + Env.add_todo env (TGen.no_generic p x) + | _ -> env + end ~init:env in + let env, tel, _ = exprs env el in + env, T.Static_var tel | Global_var el -> let env = List.fold_left el ~f:begin fun env e -> match e with diff --git a/hphp/hack/src/typing/typing_get_locals.ml b/hphp/hack/src/typing/typing_get_locals.ml index 082cf8fb13b03..73f2ba3ac3a84 100644 --- a/hphp/hack/src/typing/typing_get_locals.ml +++ b/hphp/hack/src/typing/typing_get_locals.ml @@ -73,6 +73,7 @@ and terminal_ nsenv ~in_try (_, st_) = | Break _ (* TODO this is terminal sometimes too, except switch, see above. *) | GotoLabel _ | Goto _ + | Static_var _ | Global_var _ | Awaitall _ -> () @@ -155,7 +156,7 @@ let rec stmt (acc:(Namespace_env.env * Pos.t SMap.t)) (_, st_) = acc end (* match *) | Declare _ - | Return _ | GotoLabel _ | Goto _ + | Return _ | GotoLabel _ | Goto _ | Static_var _ | Global_var _ | Def_inline _ | Noop -> acc | Awaitall el -> List.fold_left el ~init:acc ~f:(fun acc (_, e2) -> @@ -361,6 +362,7 @@ and aast_terminal_ nsenv ~in_try st = | Aast.Fallthrough | Aast.GotoLabel _ | Aast.Goto _ + | Aast.Static_var _ | Aast.Global_var _ | Aast.Awaitall _ | Aast.Unsafe_block _ @@ -551,6 +553,7 @@ let rec aast_stmt (acc:(Namespace_env.env * Pos.t SMap.t)) st = | Aast.Return _ | Aast.Goto _ | Aast.GotoLabel _ + | Aast.Static_var _ | Aast.Global_var _ | Aast.Def_inline _ | Aast.Noop -> acc diff --git a/hphp/hack/test/find_local/static1.php b/hphp/hack/test/find_local/static1.php new file mode 100644 index 0000000000000..b5a2442765050 --- /dev/null +++ b/hphp/hack/test/find_local/static1.php @@ -0,0 +1,7 @@ + $x[0] = $x; // Vector <: v --> error + static $y = $x; } diff --git a/hphp/hack/test/typecheck/new_inference/recursive_type_expansion.php.exp b/hphp/hack/test/typecheck/new_inference/recursive_type_expansion.php.exp index 3015186836b33..06d005a03a354 100644 --- a/hphp/hack/test/typecheck/new_inference/recursive_type_expansion.php.exp +++ b/hphp/hack/test/typecheck/new_inference/recursive_type_expansion.php.exp @@ -1,2 +1,4 @@ +File "recursive_type_expansion.php", line 15, characters 15-15: +Invalid expression in constant initializer (Parsing[1002]) File "recursive_type_expansion.php", line 13, characters 12-17: Type circularity: in order to type-check this expression it is necessary for a type [rec] to be equal to type \Vector<[rec]> (Typing[4155]) diff --git a/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php b/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php new file mode 100644 index 0000000000000..09657bbdefebf --- /dev/null +++ b/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php @@ -0,0 +1,19 @@ + { + public function foo(T $v): void { + static $x = null; + if (is_null($x)) { + $x = $v; + } + } +} diff --git a/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php.exp b/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php.exp new file mode 100644 index 0000000000000..8d019b4d974e5 --- /dev/null +++ b/hphp/hack/test/typecheck/new_inference/typing_fail_static_generic2.php.exp @@ -0,0 +1,2 @@ +File "typing_fail_static_generic2.php", line 14, characters 12-13: +This static variable cannot use the type parameter T. (Typing[4046]) diff --git a/hphp/hack/test/typecheck/nonnull/assign_nonnull_to_static_var.php b/hphp/hack/test/typecheck/nonnull/assign_nonnull_to_static_var.php new file mode 100644 index 0000000000000..ef92774cc8c28 --- /dev/null +++ b/hphp/hack/test/typecheck/nonnull/assign_nonnull_to_static_var.php @@ -0,0 +1,6 @@ + C::f($$); +} diff --git a/hphp/hack/test/typecheck/pipe_in_static.php.exp b/hphp/hack/test/typecheck/pipe_in_static.php.exp new file mode 100644 index 0000000000000..875e327ccb405 --- /dev/null +++ b/hphp/hack/test/typecheck/pipe_in_static.php.exp @@ -0,0 +1,2 @@ +File "pipe_in_static.php", line 7, characters 15-30: +Invalid expression in constant initializer (Parsing[1002]) diff --git a/hphp/hack/test/typecheck/reactive/test_global_class2.php b/hphp/hack/test/typecheck/reactive/test_global_class2.php new file mode 100644 index 0000000000000..da5e5319bd1f1 --- /dev/null +++ b/hphp/hack/test/typecheck/reactive/test_global_class2.php @@ -0,0 +1,8 @@ +> +function foo(): int { + static $a; + $a = 5; + return $a; +} diff --git a/hphp/hack/test/typecheck/reactive/test_global_class2.php.exp b/hphp/hack/test/typecheck/reactive/test_global_class2.php.exp new file mode 100644 index 0000000000000..9681c53bc85d9 --- /dev/null +++ b/hphp/hack/test/typecheck/reactive/test_global_class2.php.exp @@ -0,0 +1,2 @@ +File "test_global_class2.php", line 5, characters 10-11: +Static $a cannot be used in a reactive context. (Typing[4229]) diff --git a/hphp/hack/test/typecheck/recursive_type_expansion.php b/hphp/hack/test/typecheck/recursive_type_expansion.php new file mode 100644 index 0000000000000..2f761ef984a71 --- /dev/null +++ b/hphp/hack/test/typecheck/recursive_type_expansion.php @@ -0,0 +1,16 @@ +> +abstract class Singleton { + public static function instance(): Singleton { + static $instance = new static(); + return $instance; + } + + public static function bad_idea(): Singleton { + return self::instance(); + } + + abstract public function get(): T; +} + +class IntSingleton extends Singleton { + public function get(): int { + return 0; + } +} + +class StrSingleton extends Singleton { + public function get(): string { + return ''; + } +} + +function expects_int(int $x): void {} + +function unsound(): void { + // sets the static scope variable $instance in Singleton::instance() + // to an instance of StrSingleton + StrSingleton::bad_idea(); + + // will invoke Singleton::instance(), returning StrSingleton, but + // we would believe it returns Singleton + $i = IntSingleton::bad_idea(); + + // Run time error would result since we will pass a string, not an int + expects_int($i->get()); +} diff --git a/hphp/hack/test/typecheck/static_var_no_generic.php.exp b/hphp/hack/test/typecheck/static_var_no_generic.php.exp new file mode 100644 index 0000000000000..6ef4b91557fec --- /dev/null +++ b/hphp/hack/test/typecheck/static_var_no_generic.php.exp @@ -0,0 +1,2 @@ +File "static_var_no_generic.php", line 6, characters 24-34: +Invalid expression in constant initializer (Parsing[1002]) diff --git a/hphp/hack/test/typecheck/this_static_var.php b/hphp/hack/test/typecheck/this_static_var.php new file mode 100644 index 0000000000000..f91b1c839884a --- /dev/null +++ b/hphp/hack/test/typecheck/this_static_var.php @@ -0,0 +1,21 @@ +> +class A { + public static function getInstance(): this { + static $instance = null; + if (null === $instance) { + $instance = new static(); + } + return $instance; + } +} diff --git a/hphp/hack/test/typecheck/this_static_var.php.exp b/hphp/hack/test/typecheck/this_static_var.php.exp new file mode 100644 index 0000000000000..4269126fcebc1 --- /dev/null +++ b/hphp/hack/test/typecheck/this_static_var.php.exp @@ -0,0 +1 @@ +No errors diff --git a/hphp/hack/test/typecheck/typing_fail_static_generic2.php b/hphp/hack/test/typecheck/typing_fail_static_generic2.php new file mode 100644 index 0000000000000..09657bbdefebf --- /dev/null +++ b/hphp/hack/test/typecheck/typing_fail_static_generic2.php @@ -0,0 +1,19 @@ + { + public function foo(T $v): void { + static $x = null; + if (is_null($x)) { + $x = $v; + } + } +} diff --git a/hphp/hack/test/typecheck/typing_fail_static_generic2.php.exp b/hphp/hack/test/typecheck/typing_fail_static_generic2.php.exp new file mode 100644 index 0000000000000..8d019b4d974e5 --- /dev/null +++ b/hphp/hack/test/typecheck/typing_fail_static_generic2.php.exp @@ -0,0 +1,2 @@ +File "typing_fail_static_generic2.php", line 14, characters 12-13: +This static variable cannot use the type parameter T. (Typing[4046]) diff --git a/hphp/hhbbc/analyze.cpp b/hphp/hhbbc/analyze.cpp index 51c69c4ee23d8..bb22c76e00088 100644 --- a/hphp/hhbbc/analyze.cpp +++ b/hphp/hhbbc/analyze.cpp @@ -193,14 +193,19 @@ State entry_state(const Index& index, Context const ctx, auto afterParamsLocId = uint32_t{0}; for (; locId < ctx.func->locals.size(); ++locId, ++afterParamsLocId) { /* - * Some of the closure locals are mapped to used variables. The types of - * use vars are looked up from the index. + * Some of the closure locals are mapped to used variables or static + * locals. The types of use vars are looked up from the index, but we + * don't currently do anything to try to track closure static local types. */ if (ctx.func->isClosureBody) { if (afterParamsLocId < useVars.size()) { ret.locals[locId] = useVars[afterParamsLocId]; continue; } + if (afterParamsLocId < ctx.func->staticLocals.size()) { + ret.locals[locId] = TGen; + continue; + } } // Otherwise the local will start uninitialized, like normal. @@ -370,10 +375,16 @@ FuncAnalysis do_analyze_collect(const Index& index, * chains in the type lattice. */ auto totalVisits = std::vector(ctx.func->blocks.size()); + auto totalLoops = uint32_t{0}; // For debugging, count how many times basic blocks get interpreted. auto interp_counter = uint32_t{0}; + // Used to force blocks that depended on the types of local statics + // to be re-analyzed when the local statics change. + hphp_fast_map> + usedLocalStatics; + /* * Iterate until a fixed point. * @@ -431,6 +442,12 @@ FuncAnalysis do_analyze_collect(const Index& index, !collect.effectFree) { break; } + // We only care about the usedLocalStatics from the last visit + if (flags.usedLocalStatics) { + usedLocalStatics[bid] = std::move(*flags.usedLocalStatics); + } else { + usedLocalStatics.erase(bid); + } if (flags.returned) { ai.inferredReturn |= std::move(*flags.returned); @@ -450,6 +467,26 @@ FuncAnalysis do_analyze_collect(const Index& index, !collect.effectFree) { break; } + + // maybe some local statics changed type since the last time their + // blocks were visited. + + if (totalLoops++ >= options.analyzeFuncWideningLimit) { + // If we loop too many times because of static locals, widen them to + // ensure termination. + for (auto& t : collect.localStaticTypes) { + t = widen_type(std::move(t)); + } + } + + for (auto const& elm : usedLocalStatics) { + for (auto const& ls : elm.second) { + if (collect.localStaticTypes[ls.first] != ls.second) { + incompleteQ.push(rpoId(ai, elm.first)); + break; + } + } + } } while (!incompleteQ.empty()); ai.closureUseTypes = std::move(collect.closureUseTypes); @@ -499,6 +536,9 @@ FuncAnalysis do_analyze_collect(const Index& index, ret += bsep; return ret; }()); + + // Do this after the tracing above + ai.localStaticTypes = std::move(collect.localStaticTypes); return ai; } @@ -716,7 +756,8 @@ ClassAnalysis analyze_class(const Index& index, Context const ctx) { * * Closures will not have an 86pinit body, but still may have * properties of kind KindOfUninit (they will later contain - * used variables). We don't want to touch those. + * used variables or static locals for the closure body). We + * don't want to touch those. */ t = TBottom; } else if (!(prop.attrs & AttrSystemInitialValue)) { @@ -730,7 +771,8 @@ ClassAnalysis analyze_class(const Index& index, Context const ctx) { } else { // Same thing as the above regarding TUninit and TBottom. // Static properties don't need to exclude closures for this, - // though---we use instance properties for the closure use vars. + // though---we use instance properties for the closure + // 86static_* properties. auto t = cellTy.subtypeOf(BUninit) ? TBottom : (prop.attrs & AttrSystemInitialValue) diff --git a/hphp/hhbbc/analyze.h b/hphp/hhbbc/analyze.h index b8e6f906a3e7c..c3db5df05c86d 100644 --- a/hphp/hhbbc/analyze.h +++ b/hphp/hhbbc/analyze.h @@ -118,6 +118,11 @@ struct FuncAnalysisResult { hphp_fast_set> unfoldableFuncs; + /* + * Known types of local statics. + */ + CompactVector localStaticTypes; + /* * Bitset representing which parameters may affect the result of the * function, assuming it produces one. Note that VerifyParamType diff --git a/hphp/hhbbc/dce.cpp b/hphp/hhbbc/dce.cpp index 59a24bc96ce95..79cb3e9083d6a 100644 --- a/hphp/hhbbc/dce.cpp +++ b/hphp/hhbbc/dce.cpp @@ -645,6 +645,16 @@ bool setLocCouldHaveSideEffects(Env& env, LocalId loc, bool forExit = false) { } } + // If this local is bound to a static, its type will be TRef, but we + // may know the type of the static - but statics *are* live out of + // the function, so don't do this when forExit is set. + if (!forExit && + env.stateBefore.localStaticBindings.size() > loc && + env.stateBefore.localStaticBindings[loc] == LocalStaticBinding::Bound && + !setCouldHaveSideEffects(env.dceState.ainfo.localStaticTypes[loc])) { + return false; + } + return setCouldHaveSideEffects(locRaw(env, loc)); } @@ -1468,7 +1478,9 @@ void dce(Env& env, const bc::ColFromArray& op) { dceNewArrayLike(env, op); } void dce(Env& env, const bc::PopL& op) { auto const effects = setLocCouldHaveSideEffects(env, op.loc1); if (!isLocLive(env, op.loc1) && !effects) { - assert(!locRaw(env, op.loc1).couldBe(BRef)); + assert(!locRaw(env, op.loc1).couldBe(BRef) || + env.stateBefore.localStaticBindings[op.loc1] == + LocalStaticBinding::Bound); discard(env); env.dceState.actionMap[env.id] = DceAction::PopInputs; return; @@ -1490,7 +1502,9 @@ void dce(Env& env, const bc::InitThisLoc& op) { void dce(Env& env, const bc::SetL& op) { auto const effects = setLocCouldHaveSideEffects(env, op.loc1); if (!isLocLive(env, op.loc1) && !effects) { - assert(!locRaw(env, op.loc1).couldBe(BRef)); + assert(!locRaw(env, op.loc1).couldBe(BRef) || + env.stateBefore.localStaticBindings[op.loc1] == + LocalStaticBinding::Bound); return markDead(env); } stack_ops(env, [&] (UseInfo& ui) { @@ -1515,7 +1529,10 @@ void dce(Env& env, const bc::UnsetL& op) { // Unsetting a local bound to a static never has side effects // because the static itself has a reference to the value. - auto const effects = setLocCouldHaveSideEffects(env, op.loc1); + auto const effects = + setLocCouldHaveSideEffects(env, op.loc1) && + (env.stateBefore.localStaticBindings.size() <= op.loc1 || + env.stateBefore.localStaticBindings[op.loc1] != LocalStaticBinding::Bound); if (!isLocLive(env, op.loc1) && !effects) return markDead(env); if (effects) { @@ -1812,6 +1829,9 @@ void dce(Env& env, const bc::Shl& op) { dce_default(env, op); } void dce(Env& env, const bc::Shr& op) { dce_default(env, op); } void dce(Env& env, const bc::Silence& op) { dce_default(env, op); } void dce(Env& env, const bc::SSwitch& op) { dce_default(env, op); } +void dce(Env& env, const bc::StaticLocCheck& op) { dce_default(env, op); } +void dce(Env& env, const bc::StaticLocDef& op) { dce_default(env, op); } +void dce(Env& env, const bc::StaticLocInit& op) { dce_default(env, op); } void dce(Env& env, const bc::Sub& op) { dce_default(env, op); } void dce(Env& env, const bc::SubO& op) { dce_default(env, op); } void dce(Env& env, const bc::Switch& op) { dce_default(env, op); } diff --git a/hphp/hhbbc/debug.cpp b/hphp/hhbbc/debug.cpp index aef108b3df7aa..8d855d301d12b 100644 --- a/hphp/hhbbc/debug.cpp +++ b/hphp/hhbbc/debug.cpp @@ -142,6 +142,13 @@ void dump_func_state(std::ostream& out, auto const retTy = index.lookup_return_type_raw(f); out << name << " :: " << show(retTy) << '\n'; + + auto const localStatics = index.lookup_local_static_types(f); + for (auto i = size_t{0}; i < localStatics.size(); ++i) { + if (localStatics[i].subtypeOf(BBottom)) continue; + out << name << "::" << local_string(*f, i) + << " :: " << show(localStatics[i]) << '\n'; + } } void dump_index(fs::path dir, diff --git a/hphp/hhbbc/emit.cpp b/hphp/hhbbc/emit.cpp index 230881b4076d8..a6701c9381e4c 100644 --- a/hphp/hhbbc/emit.cpp +++ b/hphp/hhbbc/emit.cpp @@ -1005,6 +1005,10 @@ void emit_locals_and_params(FuncEmitter& fe, assert(fe.numLocals() == id); fe.setNumIterators(func.numIters); fe.setNumClsRefSlots(func.numClsRefSlots); + + for (auto& sv : func.staticLocals) { + fe.staticVars.push_back(Func::SVInfo {sv.name}); + } } struct EHRegion { diff --git a/hphp/hhbbc/func-util.cpp b/hphp/hhbbc/func-util.cpp index 35f29d39e2ae4..ddd9cbb3fc4c9 100644 --- a/hphp/hhbbc/func-util.cpp +++ b/hphp/hhbbc/func-util.cpp @@ -29,8 +29,9 @@ const StaticString s_86metadata("86metadata"); ////////////////////////////////////////////////////////////////////// uint32_t closure_num_use_vars(const php::Func* f) { - // Properties on the closure object are use vars. - return f->cls->properties.size(); + // Properties on the closure object are either use vars, or storage + // for static locals. The first N are the use vars. + return f->cls->properties.size() - f->staticLocals.size(); } bool is_pseudomain(const php::Func* f) { diff --git a/hphp/hhbbc/index.cpp b/hphp/hhbbc/index.cpp index 4da7a852a9632..88d3f3cdabe63 100644 --- a/hphp/hhbbc/index.cpp +++ b/hphp/hhbbc/index.cpp @@ -330,6 +330,11 @@ struct res::Func::FuncInfo { */ bool effectFree{false}; + /* + * Type info for local statics. + */ + CompactVector localStaticTypes; + /* * Bitset representing which parameters definitely don't affect the * result of the function, assuming it produces one. Note that @@ -5030,9 +5035,22 @@ Type Index::lookup_return_type_raw(const php::Func* f) const { Type Index::lookup_return_type_and_clear( const php::Func* f) const { auto it = func_info(*m_data, f); + + it->localStaticTypes.clear(); + return std::move(it->returnTy); } +CompactVector +Index::lookup_local_static_types(const php::Func* f) const { + auto it = func_info(*m_data, f); + if (it->func) { + assertx(it->func == f); + return it->localStaticTypes; + } + return {}; +} + bool Index::lookup_this_available(const php::Func* f) const { return (f->attrs & AttrRequiresThis) && !f->isClosureBody; } @@ -5419,6 +5437,37 @@ void Index::fixup_return_type(const php::Func* func, } } +void Index::refine_local_static_types( + const php::Func* func, + const CompactVector& localStaticTypes) { + + auto const finfo = create_func_info(*m_data, func); + if (localStaticTypes.empty()) { + finfo->localStaticTypes.clear(); + return; + } + + finfo->localStaticTypes.resize(localStaticTypes.size(), TTop); + for (auto i = size_t{0}; i < localStaticTypes.size(); i++) { + auto& indexTy = finfo->localStaticTypes[i]; + auto const& newTy = unctx(localStaticTypes[i]); + always_assert_flog( + newTy.moreRefined(indexTy), + "Index local static type invariant violated in {} {}{}.\n" + " Static Local {}: {} is not a subtype of {}\n", + func->unit->filename, + func->cls ? folly::to(func->cls->name->data(), "::") + : std::string{}, + func->name->data(), + local_string(*func, i), + show(newTy), + show(indexTy) + ); + if (!newTy.strictlyMoreRefined(indexTy)) continue; + indexTy = newTy; + } +} + void Index::init_return_type(const php::Func* func) { if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) { return; diff --git a/hphp/hhbbc/index.h b/hphp/hhbbc/index.h index 8280e80b36a69..0dbdb215df873 100644 --- a/hphp/hhbbc/index.h +++ b/hphp/hhbbc/index.h @@ -731,6 +731,9 @@ struct Index { lookup_closure_use_vars(const php::Func*, bool move = false) const; + CompactVector + lookup_local_static_types(const php::Func* f) const; + /* * Return the availability of $this on entry to the provided method. * If the Func provided is not a method of a class false is @@ -871,6 +874,12 @@ struct Index { void refine_constants(const FuncAnalysisResult& fa, DependencyContextSet& deps); + /* + * Refine the types of the local statics owned by the function. + */ + void refine_local_static_types(const php::Func* func, + const CompactVector& localStaticTypes); + /* * Refine the return type for a function, based on a round of * analysis. diff --git a/hphp/hhbbc/interp-internal.h b/hphp/hhbbc/interp-internal.h index 8630b8c10c2cd..898edbd58f633 100644 --- a/hphp/hhbbc/interp-internal.h +++ b/hphp/hhbbc/interp-internal.h @@ -214,6 +214,53 @@ void readAllLocals(ISS& env) { if (env.recordUsedParams) env.collect.usedParams.set(); } +void modifyLocalStatic(ISS& env, LocalId id, const Type& t) { + auto modifyOne = [&] (LocalId lid) { + if (is_volatile_local(env.ctx.func, lid)) return; + if (env.state.localStaticBindings.size() <= lid) return; + if (env.state.localStaticBindings[lid] == LocalStaticBinding::None) return; + if (t.subtypeOf(BUninit) && !t.subtypeOf(BBottom)) { + // Uninit means we are unbinding. + env.state.localStaticBindings[lid] = id == NoLocalId ? + LocalStaticBinding::None : LocalStaticBinding::Maybe; + return; + } + if (lid >= env.collect.localStaticTypes.size()) { + env.collect.localStaticTypes.resize(lid + 1, TBottom); + } + env.collect.localStaticTypes[lid] = t.subtypeOf(BCell) ? + union_of(std::move(env.collect.localStaticTypes[lid]), t) : + TGen; + }; + if (id != NoLocalId) { + return modifyOne(id); + } + for (LocalId i = 0; i < env.state.localStaticBindings.size(); i++) { + modifyOne(i); + } +} + +void maybeBindLocalStatic(ISS& env, LocalId id) { + if (is_volatile_local(env.ctx.func, id)) return; + if (env.state.localStaticBindings.size() <= id) return; + if (env.state.localStaticBindings[id] != LocalStaticBinding::None) return; + env.state.localStaticBindings[id] = LocalStaticBinding::Maybe; + return; +} + +void unbindLocalStatic(ISS& env, LocalId id) { + modifyLocalStatic(env, id, TUninit); +} + +void bindLocalStatic(ISS& env, LocalId id, const Type& t) { + if (is_volatile_local(env.ctx.func, id)) return; + if (env.state.localStaticBindings.size() <= id) { + env.state.localStaticBindings.resize(id + 1); + } + env.state.localStaticBindings[id] = LocalStaticBinding::Bound; + modifyLocalStatic(env, id, t); +} + void doRet(ISS& env, Type t, bool hasEffects) { IgnoreUsedParams _{env}; @@ -507,6 +554,17 @@ void fpiNotFoldable(ISS& env) { ////////////////////////////////////////////////////////////////////// // locals +void useLocalStatic(ISS& env, LocalId l) { + assert(env.collect.localStaticTypes.size() > l); + if (!env.flags.usedLocalStatics) { + env.flags.usedLocalStatics = + std::make_shared>(); + } + // Ignore the return value, since we only want the first type used, + // as that will be the narrowest. + env.flags.usedLocalStatics->emplace(l, env.collect.localStaticTypes[l]); +} + void mayReadLocal(ISS& env, uint32_t id) { if (id < env.flags.mayReadLocalSet.size()) { env.flags.mayReadLocalSet.set(id); @@ -731,19 +789,41 @@ void setLocRaw(ISS& env, LocalId l, Type t) { always_assert_flog(current == TGen, "volatile local was not TGen"); return; } + modifyLocalStatic(env, l, t); env.state.locals[l] = std::move(t); } +folly::Optional staticLocType(ISS& env, LocalId l, const Type& super) { + mayReadLocal(env, l); + if (env.state.localStaticBindings.size() > l && + env.state.localStaticBindings[l] == LocalStaticBinding::Bound) { + assert(env.collect.localStaticTypes.size() > l); + auto t = env.collect.localStaticTypes[l]; + if (t.subtypeOf(super)) { + useLocalStatic(env, l); + if (t.subtypeOf(BBottom)) t = TInitNull; + return std::move(t); + } + } + return folly::none; +} + // Read a local type in the sense of CGetL. (TUninits turn into // TInitNull, and potentially reffy types return the "inner" type, // which is always a subtype of InitCell.) Type locAsCell(ISS& env, LocalId l) { + if (auto s = staticLocType(env, l, TInitCell)) { + return std::move(*s); + } return to_cell(locRaw(env, l)); } // Read a local type, dereferencing refs, but without converting // potential TUninits to TInitNull. Type derefLoc(ISS& env, LocalId l) { + if (auto s = staticLocType(env, l, TCell)) { + return std::move(*s); + } auto v = locRaw(env, l); if (v.subtypeOf(BCell)) return v; return v.couldBe(BUninit) ? TCell : TInitCell; @@ -841,6 +921,7 @@ void setLoc(ISS& env, LocalId l, Type t, LocalId key = NoLocalId) { killLocEquiv(env, l); killIterEquivs(env, l, key); killThisLoc(env, l); + modifyLocalStatic(env, l, t); mayReadLocal(env, l); refineLocHelper(env, l, std::move(t)); } @@ -867,11 +948,13 @@ void loseNonRefLocalTypes(ISS& env) { killAllStkEquiv(env); killAllIterEquivs(env); killThisLoc(env, NoLocalId); + modifyLocalStatic(env, NoLocalId, TCell); } void killLocals(ISS& env) { FTRACE(2, " killLocals\n"); readUnknownLocals(env); + modifyLocalStatic(env, NoLocalId, TGen); for (auto& l : env.state.locals) l = TGen; killAllLocEquiv(env); killAllStkEquiv(env); diff --git a/hphp/hhbbc/interp-state.cpp b/hphp/hhbbc/interp-state.cpp index 754f80d0b3ead..514518af76deb 100644 --- a/hphp/hhbbc/interp-state.cpp +++ b/hphp/hhbbc/interp-state.cpp @@ -152,6 +152,7 @@ CollectedInfo::CollectedInfo(const Index& index, , opts{fa ? opts | CollectionOpts::Optimizing : opts} { if (fa) { + localStaticTypes = fa->localStaticTypes; unfoldableFuncs = fa->unfoldableFuncs; } } @@ -423,6 +424,25 @@ bool merge_impl(State& dst, const State& src, JoinOp join) { } } + auto const sz = std::max(dst.localStaticBindings.size(), + src.localStaticBindings.size()); + if (sz) { + CompactVector lsb; + for (auto i = size_t{0}; i < sz; i++) { + auto b1 = i < dst.localStaticBindings.size() ? + dst.localStaticBindings[i] : LocalStaticBinding::None; + auto b2 = i < src.localStaticBindings.size() ? + src.localStaticBindings[i] : LocalStaticBinding::None; + + if (b1 != LocalStaticBinding::None || b2 != LocalStaticBinding::None) { + lsb.resize(i + 1); + lsb[i] = b1 == b2 ? b1 : LocalStaticBinding::Maybe; + changed |= lsb[i] != b1; + } + } + dst.localStaticBindings = std::move(lsb); + } + return changed; } @@ -540,9 +560,25 @@ std::string state_string(const php::Func& f, const State& st, } for (auto i = size_t{0}; i < st.locals.size(); ++i) { - folly::format(&ret, "{: <8} :: {}\n", + auto staticLocal = [&] () -> std::string { + if (i >= st.localStaticBindings.size() || + st.localStaticBindings[i] == LocalStaticBinding::None) { + return ""; + } + + if (i >= collect.localStaticTypes.size()) { + return " (!!! unknown static !!!)"; + } + + return folly::sformat( + " ({}static: {})", + st.localStaticBindings[i] == LocalStaticBinding::Maybe ? "maybe-" : "", + show(collect.localStaticTypes[i])); + }; + folly::format(&ret, "{: <8} :: {}{}\n", local_string(f, i), - show(st.locals[i])); + show(st.locals[i]), + staticLocal()); } for (auto i = size_t{0}; i < st.iters.size(); ++i) { diff --git a/hphp/hhbbc/interp-state.h b/hphp/hhbbc/interp-state.h index c020866f85ae6..ad565ace1f1c2 100644 --- a/hphp/hhbbc/interp-state.h +++ b/hphp/hhbbc/interp-state.h @@ -219,6 +219,19 @@ struct StackElem { } }; +/* + * Used to track the state of the binding between locals, and their + * corresponding static (if any). + */ +enum class LocalStaticBinding { + // This local is not bound to a local static + None, + // This local might be bound to its local static + Maybe, + // This local is known to be bound to its local static + Bound +}; + /* * A program state at a position in a php::Block. * @@ -303,6 +316,11 @@ struct State { * compare types if they care. */ CompactVector equivLocals; + + /* + * LocalStaticBindings. Only allocated on demand. + */ + CompactVector localStaticBindings; }; /* @@ -419,6 +437,7 @@ struct CollectedInfo { CollectionOpts opts{CollectionOpts::TrackConstantArrays}; bool (*propagate_constants)(const Bytecode& bc, State& state, BytecodeVec& out) = nullptr; + CompactVector localStaticTypes; /* * See FuncAnalysisResult for details. */ diff --git a/hphp/hhbbc/interp.cpp b/hphp/hhbbc/interp.cpp index 7efe9fffb2432..641e908c47ade 100644 --- a/hphp/hhbbc/interp.cpp +++ b/hphp/hhbbc/interp.cpp @@ -1212,6 +1212,25 @@ void isTypeHelper(ISS& env, refineLocation(env, location, pre, jmp.target1, post); } +folly::Optional staticLocHelper(ISS& env, LocalId l, Type init) { + if (is_volatile_local(env.ctx.func, l)) return folly::none; + unbindLocalStatic(env, l); + setLocRaw(env, l, TRef); + bindLocalStatic(env, l, std::move(init)); + if (!env.ctx.func->isMemoizeWrapper && + !env.ctx.func->isClosureBody && + env.collect.localStaticTypes.size() > l) { + auto t = env.collect.localStaticTypes[l]; + if (auto v = tv(t)) { + useLocalStatic(env, l); + setLocRaw(env, l, t); + return v; + } + } + useLocalStatic(env, l); + return folly::none; +} + // If the current function is a memoize wrapper, return the inferred return type // of the function being wrapped along with if the wrapped function is effect // free. @@ -1272,6 +1291,49 @@ std::pair memoizeImplRetType(ISS& env) { return { retTy, effectFree }; } +// After a StaticLocCheck, we know the local is bound on the true path, +// and not changed on the false path. +template +void staticLocCheckJmpImpl(ISS& env, + const bc::StaticLocCheck& slc, + const JmpOp& jmp) { + auto const takenOnInit = jmp.op == Op::JmpNZ; + auto save = env.state; + + if (auto const v = staticLocHelper(env, slc.loc1, TBottom)) { + return impl(env, slc, jmp); + } + + if (env.collect.localStaticTypes.size() > slc.loc1 && + env.collect.localStaticTypes[slc.loc1].subtypeOf(BBottom)) { + env.state = std::move(save); + if (takenOnInit) { + jmp_nevertaken(env); + } else { + jmp_setdest(env, jmp.target1); + } + return; + } + + if (takenOnInit) { + env.propagate(jmp.target1, &env.state); + env.state = std::move(save); + } else { + env.propagate(jmp.target1, &save); + } +} + +} + +template +void group(ISS& env, const bc::StaticLocCheck& slc, const JmpOp& jmp) { + staticLocCheckJmpImpl(env, slc, jmp); +} + +template +void group(ISS& env, const bc::StaticLocCheck& slc, + const bc::Not&, const JmpOp& jmp) { + staticLocCheckJmpImpl(env, slc, invertJmp(jmp)); } template @@ -2856,6 +2918,12 @@ void in(ISS& env, const bc::IncDecS& op) { } void in(ISS& env, const bc::BindL& op) { + // If the op.loc1 was bound to a local static, its going to be + // unbound from it. If the thing its being bound /to/ is a local + // static, we've already marked it as modified via the VGetL, so + // there's nothing more to track. + // Unbind it before any updates. + modifyLocalStatic(env, op.loc1, TUninit); nothrow(env); auto t1 = popV(env); setLocRaw(env, op.loc1, t1); @@ -3961,6 +4029,40 @@ void in(ISS& env, const bc::FuncNumArgs& op) { push(env, TInt); } +void in(ISS& env, const bc::StaticLocDef& op) { + if (staticLocHelper(env, op.loc1, topC(env))) { + return reduce(env, bc::SetL { op.loc1 }, bc::PopC {}); + } + popC(env); +} + +void in(ISS& env, const bc::StaticLocCheck& op) { + auto const l = op.loc1; + if (!env.ctx.func->isMemoizeWrapper && + !env.ctx.func->isClosureBody && + env.collect.localStaticTypes.size() > l) { + auto t = env.collect.localStaticTypes[l]; + if (auto v = tv(t)) { + useLocalStatic(env, l); + setLocRaw(env, l, t); + return reduce(env, + gen_constant(*v), + bc::SetL { op.loc1 }, bc::PopC {}, + bc::True {}); + } + } + setLocRaw(env, l, TGen); + maybeBindLocalStatic(env, l); + push(env, TBool); +} + +void in(ISS& env, const bc::StaticLocInit& op) { + if (staticLocHelper(env, op.loc1, topC(env))) { + return reduce(env, bc::SetL { op.loc1 }, bc::PopC {}); + } + popC(env); +} + /* * Amongst other things, we use this to mark units non-persistent. */ @@ -4715,6 +4817,7 @@ void interpStep(ISS& env, Iterator& it, Iterator stop) { X(IsTypeStructC) X(IsTypeL) X(IsTypeC) + X(StaticLocCheck) X(Same) X(NSame) default: break; @@ -4815,6 +4918,11 @@ BlockId speculate(Interp& interp) { return NoBlockId; } + if (flags.usedLocalStatics) { + FTRACE(3, " Bailing from speculate because local statics were used\n"); + return NoBlockId; + } + assertx(!flags.returned); assertx(!interp.state.unreachable); @@ -4902,6 +5010,16 @@ RunFlags run(Interp& interp, PropagateFn propagate) { } } + if (flags.usedLocalStatics) { + if (!ret.usedLocalStatics) { + ret.usedLocalStatics = std::move(flags.usedLocalStatics); + } else { + for (auto& elm : *flags.usedLocalStatics) { + ret.usedLocalStatics->insert(std::move(elm)); + } + } + } + if (interp.state.unreachable) { FTRACE(2, " \n"); return ret; diff --git a/hphp/hhbbc/interp.h b/hphp/hhbbc/interp.h index c5b7b41f2f593..844d04c3fa081 100644 --- a/hphp/hhbbc/interp.h +++ b/hphp/hhbbc/interp.h @@ -63,6 +63,13 @@ struct RunFlags { * NoLocalId. */ LocalId retParam{NoLocalId}; + + /* + * Map from the local statics whose types were used by this block, + * to the type that was used. This is used to force re-analysis of + * the corresponding blocks when the type of the static changes. + */ + std::shared_ptr> usedLocalStatics; }; ////////////////////////////////////////////////////////////////////// @@ -148,6 +155,14 @@ struct StepFlags { * NoLocalId. */ LocalId retParam{NoLocalId}; + + /* + * Map from the local statics whose types were used by this + * instruction, to the type that was used. This is used to force + * re-analysis of the corresponding blocks when the type of the + * static changes. + */ + std::shared_ptr> usedLocalStatics; }; ////////////////////////////////////////////////////////////////////// diff --git a/hphp/hhbbc/parse.cpp b/hphp/hhbbc/parse.cpp index 412cd666c385a..e5c58e4ed50a8 100644 --- a/hphp/hhbbc/parse.cpp +++ b/hphp/hhbbc/parse.cpp @@ -1156,6 +1156,13 @@ void add_frame_variables(php::Func& func, const FuncEmitter& fe) { func.numIters = fe.numIterators(); func.numClsRefSlots = fe.numClsRefSlots(); + + func.staticLocals.reserve(fe.staticVars.size()); + for (auto& sv : fe.staticVars) { + func.staticLocals.push_back( + php::StaticLocalInfo { sv.name } + ); + } } std::unique_ptr parse_func(ParseUnitState& puState, @@ -1203,6 +1210,7 @@ std::unique_ptr parse_func(ParseUnitState& puState, ret->locals.resize(fe.params.size()); ret->numIters = 0; ret->numClsRefSlots = 0; + ret->staticLocals.clear(); ret->attrs |= AttrIsFoldable; auto const mainEntry = BlockId{0}; diff --git a/hphp/hhbbc/representation.h b/hphp/hhbbc/representation.h index fa13f904c2c75..fb50897e5cb99 100644 --- a/hphp/hhbbc/representation.h +++ b/hphp/hhbbc/representation.h @@ -262,6 +262,14 @@ struct Local { uint32_t killed : 1; }; +/* + * Static local information. For each static local, we need to keep + * the php code around for reflection. + */ +struct StaticLocalInfo { + LSString name; +}; + /* * Extra information for function with a HNI native implementation. */ @@ -338,6 +346,7 @@ struct Func : FuncBase { ClsRefSlotId numClsRefSlots; CompactVector params; CompactVector locals; + CompactVector staticLocals; /* * Which unit defined this function. If it is a method, the cls diff --git a/hphp/hhbbc/whole-program.cpp b/hphp/hhbbc/whole-program.cpp index bbea19b72b910..d2a32a38a1275 100644 --- a/hphp/hhbbc/whole-program.cpp +++ b/hphp/hhbbc/whole-program.cpp @@ -341,6 +341,7 @@ void analyze_iteratively(Index& index, php::Program& program, }; index.refine_return_info(fa, deps); index.refine_constants(fa, deps); + index.refine_local_static_types(fa.ctx.func, fa.localStaticTypes); if (options.AnalyzePublicStatics && mode == AnalyzeMode::NormalPass) { index.record_public_static_mutations( diff --git a/hphp/runtime/base/rds-util.cpp b/hphp/runtime/base/rds-util.cpp index 6a66f3f9cc72e..bdc26ed423615 100644 --- a/hphp/runtime/base/rds-util.cpp +++ b/hphp/runtime/base/rds-util.cpp @@ -22,6 +22,14 @@ namespace HPHP { namespace rds { ////////////////////////////////////////////////////////////////////// +Link +bindStaticLocal(const Func* func, const StringData* name) { + auto ret = bind( + StaticLocal { func->getFuncId(), name } + ); + return ret; +} + Link bindClassConstant(const StringData* clsName, const StringData* cnsName) { auto ret = bind( diff --git a/hphp/runtime/base/rds-util.h b/hphp/runtime/base/rds-util.h index ad8fcbd364741..c1285a5ba0add 100644 --- a/hphp/runtime/base/rds-util.h +++ b/hphp/runtime/base/rds-util.h @@ -36,6 +36,21 @@ namespace HPHP { namespace rds { * Utility functions for allocating some types of data from RDS. */ +/* + * Static locals. + * + * For normal functions, static locals are allocated as RefData's that + * live in RDS. Note that we don't put closure locals here because + * they are per-instance. + */ + +struct StaticLocalData { + RefData ref; + static size_t ref_offset() { return offsetof(StaticLocalData, ref); } +}; +Link +bindStaticLocal(const Func*, const StringData*); + /* * Allocate storage for the value of a class constant in RDS. */ diff --git a/hphp/runtime/base/rds.cpp b/hphp/runtime/base/rds.cpp index 6b42cd3958c4f..0824f6530509d 100644 --- a/hphp/runtime/base/rds.cpp +++ b/hphp/runtime/base/rds.cpp @@ -63,6 +63,7 @@ std::mutex s_allocMutex; ////////////////////////////////////////////////////////////////////// struct SymbolKind : boost::static_visitor { + std::string operator()(StaticLocal /*k*/) const { return "StaticLocal"; } std::string operator()(ClsConstant /*k*/) const { return "ClsConstant"; } std::string operator()(StaticMethod /*k*/) const { return "StaticMethod"; } std::string operator()(StaticMethodF /*k*/) const { return "StaticMethodF"; } @@ -76,6 +77,19 @@ struct SymbolKind : boost::static_visitor { }; struct SymbolRep : boost::static_visitor { + std::string operator()(StaticLocal k) const { + const Func* func = Func::fromFuncId(k.funcId); + const Class* cls = getOwningClassForFunc(func); + std::string name; + if (cls != func->cls()) { + name = cls->name()->toCppString() + "::" + + func->name()->toCppString(); + } else { + name = func->fullName()->toCppString(); + } + return name + "::" + k.name->toCppString(); + } + std::string operator()(ClsConstant k) const { return k.clsName->data() + std::string("::") + k.cnsName->data(); } @@ -125,6 +139,11 @@ struct SymbolEq : boost::static_visitor { bool >::type operator()(const T&, const U&) const { return false; } + bool operator()(StaticLocal k1, StaticLocal k2) const { + assertx(k1.name->isStatic() && k2.name->isStatic()); + return k1.funcId == k2.funcId && k1.name == k2.name; + } + bool operator()(ClsConstant k1, ClsConstant k2) const { assertx(k1.clsName->isStatic() && k1.cnsName->isStatic()); assertx(k2.clsName->isStatic() && k2.cnsName->isStatic()); @@ -172,6 +191,13 @@ struct SymbolEq : boost::static_visitor { }; struct SymbolHash : boost::static_visitor { + size_t operator()(StaticLocal k) const { + return folly::hash::hash_128_to_64( + std::hash()(k.funcId), + k.name->hash() + ); + } + size_t operator()(ClsConstant k) const { return folly::hash::hash_128_to_64( k.clsName->hash(), diff --git a/hphp/runtime/base/rds.h b/hphp/runtime/base/rds.h index e6de559dc849c..3f29a42a70ea0 100644 --- a/hphp/runtime/base/rds.h +++ b/hphp/runtime/base/rds.h @@ -185,6 +185,13 @@ extern __thread void* tl_base; * All StringData*'s below must be static strings. */ +/* + * Symbol for function static locals. These are RefData's allocated + * in RDS. + */ +struct StaticLocal { FuncId funcId; + LowStringPtr name; }; + /* * Class constant values are TypedValue's stored in RDS. */ @@ -231,7 +238,8 @@ struct LSBMemoCache { }; -using Symbol = boost::variant< ClsConstant +using Symbol = boost::variant< StaticLocal + , ClsConstant , StaticMethod , StaticMethodF , Profile @@ -624,7 +632,7 @@ void uninitHandle(Handle handle); /* * Used to record information about the rds handle h in the * perf-data-pid.map (if enabled). - * The type indicates the type of entry (eg ClsConstant), and the + * The type indicates the type of entry (eg StaticLocal), and the * msg identifies this particular entry (eg function-name:local-name) */ void recordRds(Handle h, size_t size, diff --git a/hphp/runtime/base/ref-data.h b/hphp/runtime/base/ref-data.h index 53d3f4c046d26..172a4e6d5f738 100644 --- a/hphp/runtime/base/ref-data.h +++ b/hphp/runtime/base/ref-data.h @@ -30,9 +30,26 @@ struct Variant; * A Variant or TypedValue can be KindOfRef and point to a RefData, * but the value held here must not be KindOfRef. * - * Note that a RefData should never contain KindOfUninit. + * RefData's are also used to implement static locals, but in this + * case the RefData itself is allocated in RDS rather than on + * the heap. Note that generally speaking a RefData should never + * contain KindOfUninit, *except* uninitialized RefDatas for this + * RDS case. */ struct RefData final : Countable, type_scan::MarkScannableCollectable { + /* + * Some RefData's (static locals) are allocated in RDS, and + * live until the end of the request. In this case, we start with a + * reference count to keep it alive. + * + * Note that the JIT accesses RDS RefDatas directly---if you need to + * change how initialization works it keep that up to date. + */ + void initInRDS() { + // count=OneReference + initHeader_16(HeaderKind::Ref, OneReference, 0); + } + /* * Create a RefData, allocated in the request local heap. */ @@ -69,6 +86,11 @@ struct RefData final : Countable, type_scan::MarkScannableCollectable { return &m_cell; } + /* + * This can never return a pointer to non-Cell when valid, but can + * be used to obtain a pointer for writing, after initInRDS() when + * m_cell still is uninitialized. + */ Cell* cell() { assertx(kindIsValid()); return &m_cell; diff --git a/hphp/runtime/base/runtime-option.cpp b/hphp/runtime/base/runtime-option.cpp index f34d3d01a3aac..2813efebf8552 100644 --- a/hphp/runtime/base/runtime-option.cpp +++ b/hphp/runtime/base/runtime-option.cpp @@ -812,6 +812,7 @@ bool RuntimeOption::DisableReservedVariables = true; uint64_t RuntimeOption::DisableConstant = 0; bool RuntimeOption::DisableNontoplevelDeclarations = false; bool RuntimeOption::DisableStaticClosures = false; +bool RuntimeOption::DisableStaticLocalVariables = false; #ifdef HHVM_DYNAMIC_EXTENSION_DIR std::string RuntimeOption::ExtensionDir = HHVM_DYNAMIC_EXTENSION_DIR; @@ -1601,6 +1602,9 @@ void RuntimeOption::Load( Config::Bind(DisableStaticClosures, ini, config, "Hack.Lang.Phpism.DisableStaticClosures", DisableStaticClosures); + Config::Bind(DisableStaticLocalVariables, ini, config, + "Hack.Lang.Phpism.DisableStaticLocalVariables", + DisableStaticLocalVariables); Config::Bind(DisableConstant, ini, config, "Hack.Lang.Phpism.DisableConstant", DisableConstant); diff --git a/hphp/runtime/base/runtime-option.h b/hphp/runtime/base/runtime-option.h index 9cb25a6d49abf..e049aa9633ab6 100644 --- a/hphp/runtime/base/runtime-option.h +++ b/hphp/runtime/base/runtime-option.h @@ -579,6 +579,9 @@ struct RuntimeOption { // Disables static closures // true => error, false => default behaviour static bool DisableStaticClosures; + // Disables static local variables + // true => error, false => default behaviour + static bool DisableStaticLocalVariables; // Disables the setting of reserved variable php_errorsmg // true => error, false => php_errormsg can be set diff --git a/hphp/runtime/base/unit-cache.cpp b/hphp/runtime/base/unit-cache.cpp index 38442ce214287..74c7e85d4815c 100644 --- a/hphp/runtime/base/unit-cache.cpp +++ b/hphp/runtime/base/unit-cache.cpp @@ -802,6 +802,7 @@ std::string mangleUnitMd5(const std::string& fileMd5, const RepoOptions& opts) { + (RuntimeOption::DisallowExecutionOperator ? '1' : '0') + (RuntimeOption::DisableNontoplevelDeclarations ? '1' : '0') + (RuntimeOption::DisableStaticClosures ? '1' : '0') + + (RuntimeOption::DisableStaticLocalVariables ? '1' : '0') + (RuntimeOption::EvalRxIsEnabled ? '1' : '0') + (RuntimeOption::EvalIsVecNotices ? '1' : '0') + opts.cacheKeyRaw() diff --git a/hphp/runtime/ext/objprof/ext_heapgraph.cpp b/hphp/runtime/ext/objprof/ext_heapgraph.cpp index 399a12488a765..ad908919cfb65 100644 --- a/hphp/runtime/ext/objprof/ext_heapgraph.cpp +++ b/hphp/runtime/ext/objprof/ext_heapgraph.cpp @@ -99,10 +99,11 @@ const StaticString // Extra information about a HeapGraph::Node. union CapturedNode { - CapturedNode() {} + CapturedNode() : static_local() {} CapturedNode(const CapturedNode& other) { memcpy(this, &other, sizeof other); } + rds::StaticLocal static_local; // only for rds::StaticLocalData rds::SPropCache sprop_cache; // only for HPHP::SPropCache struct { HeaderKind kind; @@ -332,6 +333,11 @@ void heapgraphCallback(Array fields, Array fields2, const Variant& callback) { vm_call_user_func(callback, params); } +static bool isStaticLocal(const HeapGraph::Node& node) { + return node.tyindex == type_scan::getIndexForScan() && + type_scan::hasNonConservative(); +} + static bool isStaticProp(const HeapGraph::Node& node) { return node.tyindex == type_scan::getIndexForScan() && type_scan::hasNonConservative(); @@ -372,6 +378,17 @@ Array createPhpNode(HeapGraphContextPtr hgptr, int index) { if (auto cls = cnode.heap_object.cls) { node_arr.set(s_class, make_tv(cls->name())); } + } else if (isStaticLocal(node)) { + node_arr.set(s_local, + make_tv(cnode.static_local.name)); + auto func_id = cnode.static_local.funcId; + if (func_id != InvalidFuncId) { + auto func = Func::fromFuncId(cnode.static_local.funcId); + node_arr.set(s_func, make_tv(func->name())); + if (auto cls = func->cls()) { + node_arr.set(s_class, make_tv(cls->name())); + } + } } else if (isStaticProp(node)) { if (auto cls = cnode.sprop_cache.cls) { auto& sprop = cls->staticProperties()[cnode.sprop_cache.slot]; @@ -452,6 +469,14 @@ Resource HHVM_FUNCTION(heapgraph_create, void) { auto obj = innerObj(node.h); cnode.heap_object.kind = node.h->kind(); cnode.heap_object.cls = obj ? obj->getVMClass() : nullptr; + } else if (isStaticLocal(node)) { + rds::Handle handle = rds::ptrToHandle(node.ptr); + auto sym = rds::reverseLink(handle); + if (sym) { + cnode.static_local = boost::get(sym.value()); + } else { + cnode.static_local = {InvalidFuncId, nullptr}; + } } else if (isStaticProp(node)) { rds::Handle handle = rds::ptrToHandle(node.ptr); auto sym = rds::reverseLink(handle); diff --git a/hphp/runtime/ext/objprof/ext_heapgraph.php b/hphp/runtime/ext/objprof/ext_heapgraph.php index f0273ebf15465..951eeeff7101c 100644 --- a/hphp/runtime/ext/objprof/ext_heapgraph.php +++ b/hphp/runtime/ext/objprof/ext_heapgraph.php @@ -141,6 +141,11 @@ function heapgraph_dfs_edges( * if the node is an object: * class PHP classname of the object * + * if the node is a static local (type == HPHP::rds::StaticLocalData) + * func PHP function name + * local name of the static local variable + * class (optional) a method's class, or closure's context class + * * if the node is a static property (type == HPHP::StaticPropData) * class PHP classname owning the static property * prop name of the static property diff --git a/hphp/runtime/ext/reflection/ext_reflection.cpp b/hphp/runtime/ext/reflection/ext_reflection.cpp index e283163afb0d2..25c3e6ad0e78c 100644 --- a/hphp/runtime/ext/reflection/ext_reflection.cpp +++ b/hphp/runtime/ext/reflection/ext_reflection.cpp @@ -648,6 +648,32 @@ static Variant HHVM_METHOD(ReflectionFunctionAbstract, getDocComment) { } } +ALWAYS_INLINE +static Array get_function_static_variables(const Func* func) { + auto const& staticVars = func->staticVars(); + + auto size = staticVars.size(); + DArrayInit ai(size); + + for (size_t i = 0; i < staticVars.size(); ++i) { + const Func::SVInfo &sv = staticVars[i]; + auto const staticLocalData = rds::bindStaticLocal(func, sv.name); + // FIXME: this should not require variant hops + ai.setUnknownKey( + VarNR(sv.name), + staticLocalData.isInit() + ? tvAsCVarRef(staticLocalData.get()->ref.cell()) + : uninit_variant + ); + } + return ai.toArray(); +} + +static Array HHVM_METHOD(ReflectionFunctionAbstract, getStaticVariables) { + auto const func = ReflectionFuncHandle::GetFuncFor(this_); + return get_function_static_variables(func); +} + static String HHVM_METHOD(ReflectionFunctionAbstract, getName) { auto const func = ReflectionFuncHandle::GetFuncFor(this_); auto ret = const_cast(func->name()); @@ -1060,6 +1086,31 @@ static Variant HHVM_METHOD(ReflectionFunction, getClosureThisObject, return init_null_variant; } +// helper for getStaticVariables +static Array HHVM_METHOD(ReflectionFunction, getClosureUseVariables, + const Object& closure) { + auto const cls = get_cls(closure); + assertx(cls); + MixedArrayInit ai(cls->numDeclProperties()); + auto propVal = closure->propVec(); + for (auto const& prop : cls->declProperties()) { + // Closure static locals are represented as special instance properties + // with a mangled name. + if (prop.name->data()[0] == '8') { + static const char prefix[] = "86static_"; + assertx(0 == strncmp(prop.name->data(), prefix, sizeof prefix - 1)); + String strippedName(prop.name->data() + sizeof prefix - 1, + prop.name->size() - sizeof prefix + 1, + CopyString); + ai.setUnknownKey(VarNR(strippedName), tvAsCVarRef(propVal)); + } else { + ai.setWithRef(StrNR(prop.name), *propVal); + } + propVal++; + } + return ai.toArray(); +} + ///////////////////////////////////////////////////////////////////////////// // class ReflectionClass @@ -2072,6 +2123,7 @@ struct ReflectionExtension final : Extension { HHVM_ME(ReflectionFunctionAbstract, getStartLine); HHVM_ME(ReflectionFunctionAbstract, getEndLine); HHVM_ME(ReflectionFunctionAbstract, getDocComment); + HHVM_ME(ReflectionFunctionAbstract, getStaticVariables); HHVM_ME(ReflectionFunctionAbstract, getReturnTypeHint); HHVM_ME(ReflectionFunctionAbstract, getNumberOfParameters); HHVM_ME(ReflectionFunctionAbstract, getParamInfo); @@ -2095,6 +2147,7 @@ struct ReflectionExtension final : Extension { HHVM_ME(ReflectionFunction, __initName); HHVM_ME(ReflectionFunction, __initClosure); + HHVM_ME(ReflectionFunction, getClosureUseVariables); HHVM_ME(ReflectionFunction, getClosureScopeClassname); HHVM_ME(ReflectionFunction, getClosureThisObject); @@ -2251,6 +2304,9 @@ static void set_debugger_reflection_function_info(Array& ret, // parameters ret.set(s_params, get_function_param_info(func)); + // static variables + ret.set(s_static_variables, get_function_static_variables(func)); + // user attributes ret.set(s_attributes, get_function_user_attributes(func)); diff --git a/hphp/runtime/ext/reflection/ext_reflection_hni.php b/hphp/runtime/ext/reflection/ext_reflection_hni.php index 75fcc7108fd65..b0db81aca0fb3 100644 --- a/hphp/runtime/ext/reflection/ext_reflection_hni.php +++ b/hphp/runtime/ext/reflection/ext_reflection_hni.php @@ -219,6 +219,19 @@ public function getEndLine(): mixed; <<__Native, __Rx, __MaybeMutable>> public function getDocComment(): mixed; + /** + * ( excerpt from + * http://php.net/manual/en/reflectionfunctionabstract.getstaticvariables.php + * ) + * + * Get the static variables. Warning: This function is currently not + * documented; only its argument list is available. + * + * @return array An array of static variables. + */ + <<__Native>> + public function getStaticVariables(): darray; + /** * ( excerpt from * http://php.net/manual/en/reflectionfunctionabstract.returnsreference.php @@ -605,6 +618,29 @@ public static function export($name, $ret=false) { print $str; } + /** + * ( excerpt from + * http://php.net/manual/en/reflectionfunctionabstract.getstaticvariables.php + * ) + * + * Get the static variables. Warning: This function is currently not + * documented; only its argument list is available. + * + * @return mixed An array of static variables. + */ + public function getStaticVariables() { + $static_vars = parent::getStaticVariables(); + return !$this->closure + ? $static_vars + : array_merge( // XXX: which should win in key collision case? + $static_vars, + $this->getClosureUseVariables($this->closure), + ); + } + + <<__Native, __Rx, __MaybeMutable>> + private function getClosureUseVariables(object $closure): array; + /** * ( excerpt from http://php.net/manual/en/reflectionfunction.invoke.php ) * diff --git a/hphp/runtime/ext/std/ext_std_closure.cpp b/hphp/runtime/ext/std/ext_std_closure.cpp index 92808a5176efd..81188012f85ee 100644 --- a/hphp/runtime/ext/std/ext_std_closure.cpp +++ b/hphp/runtime/ext/std/ext_std_closure.cpp @@ -33,7 +33,24 @@ const StaticString s_varprefix("$"), s_parameter("parameter"), s_required(""), - s_optional(""); + s_optional(""), + s_staticPrefix("86static_"); + +Slot lookupStaticSlotFromClosure(const Class* cls, const StringData* name) { + auto str = String::attach( + StringData::Make(s_staticPrefix.slice(), name->slice()) + ); + auto const slot = cls->lookupDeclProp(str.get()); + assertx(slot != kInvalidSlot); + return slot; +} + +TypedValue* lookupStaticTvFromClosure(ObjectData* closure, + const StringData* name) { + assertx(closure->instanceof(c_Closure::classof())); + auto const slot = lookupStaticSlotFromClosure(closure->getVMClass(), name); + return c_Closure::fromObject(closure)->getStaticVar(slot); +} static Array HHVM_METHOD(Closure, __debugInfo) { auto closure = c_Closure::fromObject(this_); @@ -106,9 +123,11 @@ void c_Closure::init(int numArgs, ActRec* ar, TypedValue* sp) { } /* - * Copy the use vars to instance variables. + * Copy the use vars to instance variables, and initialize any + * instance properties that are for static locals to KindOfUninit. */ - assertx(cls->numDeclProperties() == numArgs); + auto const numDeclProperties = cls->numDeclProperties(); + assertx(numDeclProperties - numArgs == getInvokeFunc()->numStaticLocals()); if (debug) { // Closure properties shouldn't have type-hints nor should they be LateInit. @@ -121,10 +140,14 @@ void c_Closure::init(int numArgs, ActRec* ar, TypedValue* sp) { auto beforeCurUseVar = sp + numArgs; auto curProperty = getUseVars(); int i = 0; + assertx(numArgs <= numDeclProperties); for (; i < numArgs; i++) { // teleport the references in here so we don't incref tvCopy(*--beforeCurUseVar, *curProperty++); } + for (; i < numDeclProperties; ++i) { + tvWriteUninit(*curProperty++); + } } static Variant HHVM_METHOD(Closure, bindto, diff --git a/hphp/runtime/ext/std/ext_std_closure.h b/hphp/runtime/ext/std/ext_std_closure.h index b4a2c4cd4e08f..bddde61c137f8 100644 --- a/hphp/runtime/ext/std/ext_std_closure.h +++ b/hphp/runtime/ext/std/ext_std_closure.h @@ -101,7 +101,7 @@ struct c_Closure final : ObjectData { Class* getScope() { return getInvokeFunc()->cls(); } /* - * Use variables. + * Use and static local variables. * * Returns obj->propVecForWrite() * but with runtime generalized checks replaced with assertions @@ -117,7 +117,8 @@ struct c_Closure final : ObjectData { } int32_t getNumUseVars() const { - return getVMClass()->numDeclProperties(); + return getVMClass()->numDeclProperties() - + getInvokeFunc()->numStaticLocals(); } /* @@ -166,6 +167,10 @@ struct c_Closure final : ObjectData { static void setAllocators(Class* cls); }; +TypedValue* lookupStaticTvFromClosure(ObjectData* closure, + const StringData* name); +Slot lookupStaticSlotFromClosure(const Class* cls, const StringData* name); + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/vm/as.cpp b/hphp/runtime/vm/as.cpp index 9cfd96abe32d1..b5a4751ad5952 100644 --- a/hphp/runtime/vm/as.cpp +++ b/hphp/runtime/vm/as.cpp @@ -1950,6 +1950,28 @@ void parse_srcloc(AsmState& as, int /*nestLevel*/) { as.srcLoc = Location::Range(line0, char0, line1, char1); } +/* + * directive-static : '$' local_name = long-string-literal ';' + * ; + * + * Record that the function contains a static named local_name along with an + * associated initializer. + */ +void parse_static(AsmState& as) { + Func::SVInfo svInfo; + std::string name; + String init; + + as.in.expectWs('$'); + if (!as.in.readword(name)) { + as.error("Statics must be named"); + } + svInfo.name = makeStaticString(name); + as.fe->staticVars.push_back(svInfo); + + as.in.expectWs(';'); +} + /* * directive-doccomment : long-string-literal ';' * ; @@ -2141,6 +2163,7 @@ void parse_function_body(AsmState& as, int nestLevel /* = 0 */) { if (word == ".try_catch") { parse_catch(as, nestLevel); continue; } if (word == ".try") { parse_try_catch(as, nestLevel); continue; } if (word == ".srcloc") { parse_srcloc(as, nestLevel); continue; } + if (word == ".static") { parse_static(as); continue; } if (word == ".doc") { parse_func_doccomment(as); continue; } as.error("unrecognized directive `" + word + "' in function"); } diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 7f1aeba6b0e46..b7d4571f2ed48 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -5978,6 +5978,102 @@ OPTBLD_INLINE void iopFuncNumArgs() { } } +static inline TypedValue* lookupClosureStatic(const StringData* name, + const ActRec* fp) { + auto const func = fp->m_func; + + assertx(func->isClosureBody()); + assertx(!func->hasVariadicCaptureParam()); + auto const obj = frame_local(fp, func->numParams())->m_data.pobj; + + return lookupStaticTvFromClosure(obj, name); +} + +OPTBLD_INLINE void iopStaticLocCheck(local_var loc, const StringData* var) { + auto const func = vmfp()->m_func; + + auto ref = [&] () -> RefData* { + if (UNLIKELY(func->isClosureBody())) { + auto const val = lookupClosureStatic(var, vmfp()); + if (val->m_type == KindOfUninit) { + return nullptr; + } + assertx(isRefType(val->m_type)); + return val->m_data.pref; + } + + auto const staticLocalData = rds::bindStaticLocal(func, var); + if (!staticLocalData.isInit()) { + return nullptr; + } + + return &staticLocalData->ref; + }(); + + if (!ref) return vmStack().pushBool(false); + + auto const tmpTV = make_tv(ref); + tvBind(tmpTV, *loc.ptr); + vmStack().pushBool(true); +} + +OPTBLD_INLINE void iopStaticLocDef(local_var loc, const StringData* var) { + auto const func = vmfp()->m_func; + auto const initVal = vmStack().topC(); + + auto ref = [&] () -> RefData* { + if (UNLIKELY(func->isClosureBody())) { + auto const val = lookupClosureStatic(var, vmfp()); + assertx(val->m_type == KindOfUninit); + cellCopy(*initVal, *val); + tvBox(*val); + return val->m_data.pref; + } + + auto const staticLocalData = rds::bindStaticLocal(func, var); + if (LIKELY(!staticLocalData.isInit())) { + staticLocalData->ref.initInRDS(); + staticLocalData.markInit(); + cellCopy(*initVal, *staticLocalData->ref.cell()); + } else { + cellMove(*initVal, *staticLocalData->ref.cell()); + } + return &staticLocalData->ref; + }(); + + auto const tmpTV = make_tv(ref); + tvBind(tmpTV, *loc.ptr); + vmStack().discard(); +} + +OPTBLD_INLINE void iopStaticLocInit(local_var loc, const StringData* var) { + auto const func = vmfp()->m_func; + auto const initVal = vmStack().topC(); + + auto ref = [&] () -> RefData* { + if (UNLIKELY(func->isClosureBody())) { + auto const val = lookupClosureStatic(var, vmfp()); + if (val->m_type == KindOfUninit) { + cellCopy(*initVal, *val); + tvBox(*val); + } + return val->m_data.pref; + } + + auto const staticLocalData = rds::bindStaticLocal(func, var); + if (!staticLocalData.isInit()) { + staticLocalData->ref.initInRDS(); + staticLocalData.markInit(); + cellCopy(*initVal, *staticLocalData->ref.cell()); + } + return &staticLocalData->ref; + }(); + + auto const tmpTV = make_tv(ref); + tvBind(tmpTV, *loc.ptr); + vmStack().discard(); +} + OPTBLD_INLINE void iopCatch() { auto vm = &*g_context; assertx(vm->m_faults.size() > 0); diff --git a/hphp/runtime/vm/class.cpp b/hphp/runtime/vm/class.cpp index 8ed4e166bb33a..2eaf8ccbcc272 100644 --- a/hphp/runtime/vm/class.cpp +++ b/hphp/runtime/vm/class.cpp @@ -67,6 +67,34 @@ const StaticString s___Reified("__Reified"); Mutex g_classesMutex; +/////////////////////////////////////////////////////////////////////////////// + +/* + * We clone methods with static locals into derived classes, but the clone + * still points to the class the method was defined in (because it needs to + * have the right context class). For data profiling, we need to find the + * actual class that a Func belongs to so we put such Funcs into this map. + */ +typedef tbb::concurrent_hash_map FuncIdToClassMap; +static FuncIdToClassMap* s_funcIdToClassMap; + +const Class* getOwningClassForFunc(const Func* f) { + // We only populate s_funcIdToClassMap when the following conditions + // are true. + assertx(RuntimeOption::EvalPerfDataMap || + RuntimeOption::EvalJitSerdesMode == JitSerdesMode::Serialize || + RuntimeOption::EvalJitSerdesMode == JitSerdesMode::SerializeAndExit); + + if (s_funcIdToClassMap) { + FuncIdToClassMap::const_accessor acc; + if (s_funcIdToClassMap->find(acc, f->getFuncId())) { + return acc->second; + } + } + return f->cls(); +} + + /////////////////////////////////////////////////////////////////////////////// // Class::PropInitVec. @@ -518,7 +546,8 @@ void Class::releaseRefs() { for (auto i = 0; i < num; i++) { Func* meth = getMethod(i); if (meth /* releaseRefs can be called more than once */ && - meth->cls() != this) { + meth->cls() != this && + ((meth->attrs() & AttrPrivate) || !meth->hasStaticLocals())) { setMethod(i, nullptr); okToReleaseParent = false; } @@ -2014,6 +2043,7 @@ void Class::methodOverrideCheck(const Func* parentMethod, const Func* method) { } void Class::setMethods() { + std::vector parentMethodsWithStaticLocals; MethodMapBuilder builder; ITRACE(5, "----------\nsetMethods() for {}:\n", this->name()->data()); @@ -2023,6 +2053,14 @@ void Class::setMethods() { Func* f = m_parent->getMethod(i); assertx(f); ITRACE(5, " - adding parent method {}\n", f->name()->data()); + if (!(f->attrs() & AttrPrivate) && f->hasStaticLocals()) { + // When copying down an entry for a non-private method that has + // static locals, we want to make a copy of the Func so that it + // gets a distinct set of static locals variables. We defer making + // a copy of the parent method until the end because it might get + // overridden below. + parentMethodsWithStaticLocals.push_back(i); + } assertx(builder.size() == i); builder.add(f->name(), f); } @@ -2095,6 +2133,38 @@ void Class::setMethods() { } auto const traitsEndIdx = builder.size(); + // Make copies of Funcs inherited from the parent class that have + // static locals + std::vector::const_iterator it; + for (it = parentMethodsWithStaticLocals.begin(); + it != parentMethodsWithStaticLocals.end(); ++it) { + Func*& f = builder[*it]; + if (f->cls() != this) { + // Don't update f's m_cls. We're cloning it so that we get a + // distinct set of static locals and a separate translation, not + // a different context class. + f = f->clone(f->cls()); + f->setNewFuncId(); + if (RuntimeOption::EvalPerfDataMap || + RuntimeOption::EvalJitSerdesMode == JitSerdesMode::Serialize || + RuntimeOption::EvalJitSerdesMode == JitSerdesMode::SerializeAndExit) { + if (!s_funcIdToClassMap) { + Lock l(g_classesMutex); + if (!s_funcIdToClassMap) { + s_funcIdToClassMap = new FuncIdToClassMap; + } + } + FuncIdToClassMap::accessor acc; + if (!s_funcIdToClassMap->insert( + acc, FuncIdToClassMap::value_type(f->getFuncId(), this))) { + // we only just allocated this id, which is supposedly + // process unique + assertx(false); + } + } + } + } + if (m_extra) { m_extra.raw()->m_traitsBeginIdx = traitsBeginIdx; m_extra.raw()->m_traitsEndIdx = traitsEndIdx; diff --git a/hphp/runtime/vm/class.h b/hphp/runtime/vm/class.h index 753a8b4c38fba..7ad93719fb4af 100644 --- a/hphp/runtime/vm/class.h +++ b/hphp/runtime/vm/class.h @@ -1671,6 +1671,14 @@ bool classHasPersistentRDS(const Class* cls); */ bool classMayHaveMagicPropMethods(const Class* cls); +/* + * Return the class that "owns" f. This will normally be f->cls(), but for + * Funcs with static locals, f may have been cloned into a derived class. + * + * @requires: RuntimeOption::EvalPerfDataMap + */ +const Class* getOwningClassForFunc(const Func* f); + /* * Convert a class pointer where a string is needed in some context. A warning * will be raised when compiler option Eval.RaiseClassConversionWarning is true. diff --git a/hphp/runtime/vm/disas.cpp b/hphp/runtime/vm/disas.cpp index d655989468234..29df17f863a7d 100644 --- a/hphp/runtime/vm/disas.cpp +++ b/hphp/runtime/vm/disas.cpp @@ -442,6 +442,9 @@ void print_func_directives(Output& out, const FuncInfo& finfo) { } out.fmtln(".declvars {};", folly::join(" ", locals)); } + for (auto& info : func->staticVars()) { + out.fmtln(".static ${};", info.name); + } } void print_srcloc(Output& out, SourceLoc loc) { @@ -882,6 +885,8 @@ void print_unit(Output& out, const Unit* unit) { * conjunction with as.cpp): * * - .line/.srcpos directives + * + * - Static locals. */ std::string disassemble(const Unit* unit) { diff --git a/hphp/runtime/vm/func-emitter.cpp b/hphp/runtime/vm/func-emitter.cpp index 94df9f37f04ce..974eb73c6ac24 100644 --- a/hphp/runtime/vm/func-emitter.cpp +++ b/hphp/runtime/vm/func-emitter.cpp @@ -237,6 +237,7 @@ Func* FuncEmitter::create(Unit& unit, PreClass* preClass /* = NULL */) const { f->shared()->m_numLocals = m_numLocals; f->shared()->m_numIterators = m_numIterators; f->m_maxStackCells = maxStackCells; + f->shared()->m_staticVars = staticVars; f->shared()->m_ehtab = ehtab; f->shared()->m_fpitab = fpitab; f->shared()->m_isClosureBody = isClosureBody; @@ -588,6 +589,7 @@ void FuncEmitter::serdeMetaData(SerDe& sd) { (params) (localNames) + (staticVars) (ehtab) (fpitab, [&](const FPIEnt& prev, FPIEnt cur) -> FPIEnt { diff --git a/hphp/runtime/vm/func-emitter.h b/hphp/runtime/vm/func-emitter.h index ad856f1e5cc1f..2b8c674e103a4 100644 --- a/hphp/runtime/vm/func-emitter.h +++ b/hphp/runtime/vm/func-emitter.h @@ -269,6 +269,7 @@ struct FuncEmitter { Attr attrs; ParamInfoVec params; + SVInfoVec staticVars; int maxStackCells; MaybeDataType hniReturnType; diff --git a/hphp/runtime/vm/func-inl.h b/hphp/runtime/vm/func-inl.h index 9512ec224e375..1ecb9800ce9a0 100644 --- a/hphp/runtime/vm/func-inl.h +++ b/hphp/runtime/vm/func-inl.h @@ -383,6 +383,21 @@ inline void Func::setGenerated(bool isGenerated) { shared()->m_isGenerated = isGenerated; } +/////////////////////////////////////////////////////////////////////////////// +// Static locals. + +inline const Func::SVInfoVec& Func::staticVars() const { + return shared()->m_staticVars; +} + +inline bool Func::hasStaticLocals() const { + return !shared()->m_staticVars.empty(); +} + +inline int Func::numStaticLocals() const { + return shared()->m_staticVars.size(); +} + /////////////////////////////////////////////////////////////////////////////// // Definition context. diff --git a/hphp/runtime/vm/func.cpp b/hphp/runtime/vm/func.cpp index 9dc382616a94f..a7fef857a431b 100644 --- a/hphp/runtime/vm/func.cpp +++ b/hphp/runtime/vm/func.cpp @@ -493,7 +493,9 @@ bool Func::isImmutableFrom(const Class* cls) const { if (!RuntimeOption::RepoAuthoritative) return false; assertx(cls && cls->lookupMethod(name()) == this); if (attrs() & AttrNoOverride) { - return true; + // Even if the func isn't overridden, we clone it into + // any derived classes if it has static locals + if (!hasStaticLocals()) return true; } if (cls->preClass()->attrs() & AttrNoOverride) { return true; @@ -1083,6 +1085,7 @@ void logFunc(const Func* func, StructuredLogEntry& ent) { if (func->isClosureBody()) attrSet.emplace("closure_body"); if (func->isPairGenerator()) attrSet.emplace("pair_generator"); if (func->hasVariadicCaptureParam()) attrSet.emplace("variadic_param"); + if (func->hasStaticLocals()) attrSet.emplace("has_statics"); if (func->isHot()) attrSet.emplace("hot"); if (func->attrs() & AttrMayUseVV) attrSet.emplace("may_use_vv"); if (func->attrs() & AttrRequiresThis) attrSet.emplace("must_have_this"); @@ -1098,6 +1101,7 @@ void logFunc(const Func* func, StructuredLogEntry& ent) { ent.setInt("num_iterators", func->numIterators()); ent.setInt("frame_cells", func->numSlotsInFrame()); ent.setInt("max_stack_cells", func->maxStackCells()); + ent.setInt("num_static_locals", func->numStaticLocals()); } /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/vm/func.h b/hphp/runtime/vm/func.h index 69cbc2a927595..33731f18f6d19 100644 --- a/hphp/runtime/vm/func.h +++ b/hphp/runtime/vm/func.h @@ -219,7 +219,9 @@ struct Func final { * PreClasses. * * We also clone methods from traits when we transclude the trait in its user - * Classes in repo mode. + * Classes in repo mode. Finally, we clone inherited methods that define + * static locals in order to instantiate new static locals for the child + * class's copy of the method. */ Func* clone(Class* cls, const StringData* name = nullptr) const; @@ -621,6 +623,26 @@ struct Func final { void setHasForeignThis(bool); + ///////////////////////////////////////////////////////////////////////////// + // Static locals. [const] + + /* + * Const reference to the static variable info table. + * + * SVInfo objects pulled from the table will also be const. + */ + const SVInfoVec& staticVars() const; + + /* + * Whether the function has any static locals. + */ + bool hasStaticLocals() const; + + /* + * Number of static locals declared in the function. + */ + int numStaticLocals() const; + ///////////////////////////////////////////////////////////////////////////// // Definition context. [const] @@ -1228,6 +1250,7 @@ struct Func final { uint64_t* m_refBitPtr; ParamInfoVec m_params; NamedLocalsMap m_localNames; + SVInfoVec m_staticVars; EHEntVec m_ehtab; FPIEntVec m_fpitab; diff --git a/hphp/runtime/vm/hhbc.h b/hphp/runtime/vm/hhbc.h index c798423b5006d..0d039f07b86c4 100644 --- a/hphp/runtime/vm/hhbc.h +++ b/hphp/runtime/vm/hhbc.h @@ -684,6 +684,9 @@ constexpr uint32_t kMaxConcatN = 4; O(CheckThis, NA, NOV, NOV, NF) \ O(InitThisLoc, ONE(LA), NOV, NOV, NF) \ O(FuncNumArgs, NA, NOV, ONE(CV), NF) \ + O(StaticLocCheck, TWO(LA,SA), NOV, ONE(CV), NF) \ + O(StaticLocDef, TWO(LA,SA), ONE(CV), NOV, NF) \ + O(StaticLocInit, TWO(LA,SA), ONE(CV), NOV, NF) \ O(Catch, NA, NOV, ONE(CV), NF) \ O(ChainFaults, NA, TWO(CV,CV), ONE(CV), NF) \ O(OODeclExists, ONE(OA(OODeclExistsOp)), \ diff --git a/hphp/runtime/vm/jit/dce.cpp b/hphp/runtime/vm/jit/dce.cpp index f45a11edb1ed2..e32a0794b115a 100644 --- a/hphp/runtime/vm/jit/dce.cpp +++ b/hphp/runtime/vm/jit/dce.cpp @@ -199,6 +199,7 @@ bool canDCE(IRInstruction* inst) { case LdVecElem: case LdPackedElem: case LdPackedArrayDataElemAddr: + case LdClosureStaticLoc: case NewInstanceRaw: case NewArray: case NewMixedArray: @@ -271,6 +272,7 @@ bool canDCE(IRInstruction* inst) { case GetTimeNs: case Select: case LdARCtx: + case LdStaticLoc: case LdARNumArgsAndFlags: case LdARReifiedGenerics: case KillARReifiedGenerics: @@ -548,6 +550,7 @@ bool canDCE(IRInstruction* inst) { case RaiseStrToClassNotice: case CheckClsReifiedGenericMismatch: case CheckFunReifiedGenericMismatch: + case InitStaticLoc: case PrintStr: case PrintInt: case PrintBool: @@ -723,6 +726,7 @@ bool canDCE(IRInstruction* inst) { case SetOpCell: case SetOpCellVerify: case ConjureUse: + case CheckStaticLoc: case LdClsMethodFCacheFunc: case LdClsMethodCacheFunc: case LdReifiedGeneric: diff --git a/hphp/runtime/vm/jit/extra-data.h b/hphp/runtime/vm/jit/extra-data.h index 7cf525d54f00d..5d370212d98c9 100644 --- a/hphp/runtime/vm/jit/extra-data.h +++ b/hphp/runtime/vm/jit/extra-data.h @@ -850,6 +850,31 @@ struct ProfileSubClsCnsData : IRExtraData { rds::Handle handle; }; +/* + * The name of a static local in a function. + */ +struct StaticLocName : IRExtraData { + StaticLocName(const Func* func, const StringData* name) + : func(func) + , name(name) + {} + + std::string show() const { + return folly::to( + func->fullName()->data(), "$", name->data() + ); + } + + bool equals(const StaticLocName& o) const { + return func == o.func && name == o.name; + } + size_t hash() const { + return hash_int64_pair((intptr_t)func, (intptr_t)name); + } + const Func* func; + const StringData* name; +}; + struct FuncNameData : IRExtraData { explicit FuncNameData(const StringData* name) : name(name) @@ -1641,6 +1666,10 @@ X(LdClsMethodCacheCls, ClsMethodData); X(LdClsMethodFCacheFunc, ClsMethodData); X(LookupClsMethodFCache, ClsMethodData); X(LdIfaceMethod, IfaceMethodData); +X(LdClosureStaticLoc, StaticLocName); +X(LdStaticLoc, StaticLocName); +X(CheckStaticLoc, StaticLocName); +X(InitStaticLoc, StaticLocName); X(LdClsCns, ClsCnsName); X(InitClsCns, ClsCnsName); X(LdSubClsCns, LdSubClsCnsData); diff --git a/hphp/runtime/vm/jit/gvn.cpp b/hphp/runtime/vm/jit/gvn.cpp index 792d13b2d6451..0205840414acf 100644 --- a/hphp/runtime/vm/jit/gvn.cpp +++ b/hphp/runtime/vm/jit/gvn.cpp @@ -302,6 +302,7 @@ bool supportsGVN(const IRInstruction* inst) { case LdContActRec: case LdAFWHActRec: case LdPackedArrayDataElemAddr: + case LdStaticLoc: case OrdStr: case ChrInt: case CheckRange: diff --git a/hphp/runtime/vm/jit/ir-opcode.cpp b/hphp/runtime/vm/jit/ir-opcode.cpp index 83d25b74d8722..0dc35008b081b 100644 --- a/hphp/runtime/vm/jit/ir-opcode.cpp +++ b/hphp/runtime/vm/jit/ir-opcode.cpp @@ -613,6 +613,7 @@ bool opcodeMayRaise(Opcode opc) { case CheckRDSInitialized: case CheckRefInner: case CheckRefs: + case CheckStaticLoc: case CheckStk: case CheckSubClsCns: case CheckSurpriseFlags: @@ -769,6 +770,7 @@ bool opcodeMayRaise(Opcode opc) { case InitObjMemoSlots: case InitPackedLayoutArray: case InitPackedLayoutArrayLoop: + case InitStaticLoc: case InitThrowableFileAndLine: case InlineReturn: case InlineReturnNoFrame: @@ -823,6 +825,7 @@ bool opcodeMayRaise(Opcode opc) { case LdCctx: case LdClosure: case LdClosureCtx: + case LdClosureStaticLoc: case LdClsCachedSafe: case LdClsCctx: case LdClsCns: @@ -874,6 +877,7 @@ bool opcodeMayRaise(Opcode opc) { case LdRef: case LdRetVal: case LdSSwitchDestFast: + case LdStaticLoc: case LdStk: case LdStkAddr: case LdStrLen: diff --git a/hphp/runtime/vm/jit/irgen-create.cpp b/hphp/runtime/vm/jit/irgen-create.cpp index 4e2f37d0c31a7..5a37f62e575de 100644 --- a/hphp/runtime/vm/jit/irgen-create.cpp +++ b/hphp/runtime/vm/jit/irgen-create.cpp @@ -379,7 +379,22 @@ void emitCreateCl(IRGS& env, uint32_t numParams, uint32_t clsIx) { ); } - assertx(cls->numDeclProperties() == numParams); + assertx(cls->numDeclProperties() == func->numStaticLocals() + numParams); + + // Closure static variables are per instance, and need to start + // uninitialized. After numParams use vars, the remaining instance + // properties hold any static locals. + for (int32_t numDeclProperties = cls->numDeclProperties(); + propId < numDeclProperties; + ++propId) { + gen( + env, + StClosureArg, + ByteOffsetData { safe_cast(cls->declPropOffset(propId)) }, + closure, + cns(env, TUninit) + ); + } push(env, closure); } @@ -620,6 +635,195 @@ void emitColFromArray(IRGS& env, CollectionType type) { push(env, gen(env, NewColFromArray, NewColData{type}, arr)); } +void emitStaticLocInit(IRGS& env, int32_t locId, const StringData* name) { + auto const func = curFunc(env); + if (func->isPseudoMain()) PUNT(StaticLocInit); + + auto const value = popC(env); + + // Closures and generators from closures don't satisfy the "one static per + // source location" rule that the inline fastpath requires + auto const box = [&]{ + if (func->isClosureBody()) { + assertx(func->isClosureBody()); + assertx(!func->hasVariadicCaptureParam()); + auto const obj = gen( + env, LdLoc, TObj, LocalId(func->numParams()), fp(env)); + + auto const theStatic = gen(env, + LdClosureStaticLoc, + StaticLocName { func, name }, + obj); + ifThen( + env, + [&] (Block* taken) { + gen(env, CheckTypeMem, TBoxedCell, taken, theStatic); + }, + [&] { + hint(env, Block::Hint::Unlikely); + gen(env, StMem, theStatic, value); + gen(env, BoxPtr, theStatic); + } + ); + return gen(env, LdMem, TBoxedCell, theStatic); + } + + ifThen( + env, + [&] (Block* taken) { + gen( + env, + CheckStaticLoc, + StaticLocName { func, name }, + taken + ); + }, + [&] { + hint(env, Block::Hint::Unlikely); + gen( + env, + InitStaticLoc, + StaticLocName { func, name }, + value + ); + } + ); + return gen(env, LdStaticLoc, StaticLocName { func, name }); + }(); + + gen(env, IncRef, box); + auto const oldValue = ldLoc(env, locId, nullptr, DataTypeSpecific); + stLocRaw(env, locId, fp(env), box); + decRef(env, oldValue); + // We don't need to decref value---it's a bytecode invariant that + // our Cell was not ref-counted. +} + +void emitStaticLocCheck(IRGS& env, int32_t locId, const StringData* name) { + auto const func = curFunc(env); + if (func->isPseudoMain()) PUNT(StaticLocCheck); + + auto bindLocal = [&] (SSATmp* box) { + gen(env, IncRef, box); + auto const oldValue = ldLoc(env, locId, nullptr, DataTypeGeneric); + stLocRaw(env, locId, fp(env), box); + decRef(env, oldValue); + return cns(env, true); + }; + + auto const inited = [&] { + if (func->isClosureBody()) { + auto const obj = gen( + env, LdLoc, TObj, LocalId(func->numParams()), fp(env)); + auto const theStatic = gen(env, + LdClosureStaticLoc, + StaticLocName { func, name }, + obj); + return cond( + env, + [&] (Block* taken) { + gen(env, CheckTypeMem, TBoxedCell, taken, theStatic); + }, + [&] { + return bindLocal(gen(env, LdMem, TInitCell, theStatic)); + }, + [&] { + hint(env, Block::Hint::Unlikely); + return cns(env, false); + } + ); + } + + return cond( + env, + [&] (Block* taken) { + gen( + env, + CheckStaticLoc, + StaticLocName { func, name }, + taken + ); + }, + [&] { + // Next: the static local is already initialized + return bindLocal(gen(env, LdStaticLoc, StaticLocName { func, name })); + }, + [&] { // Taken: need to initialize the static local + return cns(env, false); + } + ); + }(); + + push(env, inited); +} + +void emitStaticLocDef(IRGS& env, int32_t locId, const StringData* name) { + auto const func = curFunc(env); + if (func->isPseudoMain()) PUNT(StaticLocDef); + + auto const value = popC(env); + + auto const box = [&] { + if (func->isClosureBody()) { + auto const obj = gen( + env, LdLoc, TObj, LocalId(func->numParams()), fp(env)); + auto const theStatic = gen(env, + LdClosureStaticLoc, + StaticLocName { func, name }, + obj); + gen(env, StMem, theStatic, value); + auto const boxedStatic = gen(env, BoxPtr, theStatic); + return gen(env, LdMem, TBoxedInitCell, boxedStatic); + } + + auto init = [&] { + gen( + env, + InitStaticLoc, + StaticLocName { func, name }, + value + ); + }; + + if (func->isMemoizeWrapper() && !func->numParams()) { + ifThenElse( + env, + [&] (Block* taken) { + gen( + env, + CheckStaticLoc, + StaticLocName { func, name }, + taken + ); + }, + [&] { + hint(env, Block::Hint::Unlikely); + auto oldBox = gen(env, LdStaticLoc, StaticLocName { func, name }); + auto oldVal = gen(env, LdRef, TInitCell, oldBox); + init(); + decRef(env, oldVal); + }, + [&] { + init(); + } + ); + } else { + init(); + } + + return gen( + env, + LdStaticLoc, + StaticLocName { func, name } + ); + }(); + + gen(env, IncRef, box); + auto const oldValue = ldLoc(env, locId, nullptr, DataTypeGeneric); + stLocRaw(env, locId, fp(env), box); + decRef(env, oldValue); +} + void emitCheckReifiedGenericMismatch(IRGS& env) { auto const cls = curClass(env); if (!cls) { diff --git a/hphp/runtime/vm/jit/irgen-func-prologue.cpp b/hphp/runtime/vm/jit/irgen-func-prologue.cpp index 9bc6c1892bc43..ad8d177112a35 100644 --- a/hphp/runtime/vm/jit/irgen-func-prologue.cpp +++ b/hphp/runtime/vm/jit/irgen-func-prologue.cpp @@ -252,8 +252,9 @@ void init_use_vars(IRGS& env, const Func* func, SSATmp* closure) { assertx(func->isClosureBody()); - // Closure object properties are the use vars. - auto const nuse = cls->numDeclProperties(); + // Closure object properties are the use vars followed by the static locals + // (which are per-instance). + auto const nuse = cls->numDeclProperties() - func->numStaticLocals(); ptrdiff_t use_var_off = sizeof(ObjectData); for (auto i = 0; i < nuse; ++i, use_var_off += sizeof(Cell)) { @@ -289,7 +290,8 @@ void init_locals(IRGS& env, const Func* func) { auto num_inited = func->numParams(); if (func->isClosureBody()) { - auto const nuse = func->implCls()->numDeclProperties(); + auto const nuse = func->implCls()->numDeclProperties() - + func->numStaticLocals(); num_inited += 1 + nuse; } diff --git a/hphp/runtime/vm/jit/irgen-inlining.cpp b/hphp/runtime/vm/jit/irgen-inlining.cpp index 8bce162144b20..5c0320a93eedc 100644 --- a/hphp/runtime/vm/jit/irgen-inlining.cpp +++ b/hphp/runtime/vm/jit/irgen-inlining.cpp @@ -636,6 +636,28 @@ void suspendFromInlined(IRGS& env, SSATmp* waitHandle) { ////////////////////////////////////////////////////////////////////// +void inlSingletonSLoc(IRGS& env, const Func* func, PC op) { + assertx(peek_op(op) == Op::StaticLocInit); + + TransFlags trflags; + trflags.noinlineSingleton = true; + + auto exit = makeExit(env, trflags); + auto const name = func->unit()->lookupLitstrId(getImmPtr(op, 1)->u_SA); + + // Side exit if the static local is uninitialized. + gen(env, CheckStaticLoc, StaticLocName { func, name }, exit); + auto const box = gen(env, LdStaticLoc, StaticLocName { func, name }); + + // Side exit if the static local is null. + auto const value = gen(env, LdRef, TInitCell, box); + auto const isnull = gen(env, IsType, TInitNull, value); + gen(env, JmpNZero, exit, isnull); + + // Return the singleton. + pushIncRef(env, value); +} + void inlSingletonSProp(IRGS& env, const Func* func, PC clsOp, diff --git a/hphp/runtime/vm/jit/irgen-interpone.cpp b/hphp/runtime/vm/jit/irgen-interpone.cpp index e095f18d0f92b..d9145ff3c3145 100644 --- a/hphp/runtime/vm/jit/irgen-interpone.cpp +++ b/hphp/runtime/vm/jit/irgen-interpone.cpp @@ -234,6 +234,15 @@ interpOutputLocals(IRGS& env, break; } + case OpStaticLocInit: + case OpStaticLocDef: + setImmLocType(0, TBoxedInitCell); + break; + + case OpStaticLocCheck: + setImmLocType(0, TGen); + break; + case OpInitThisLoc: setImmLocType(0, TCell); break; diff --git a/hphp/runtime/vm/jit/irgen.h b/hphp/runtime/vm/jit/irgen.h index 6ca700bd8e037..f8f061ed31555 100644 --- a/hphp/runtime/vm/jit/irgen.h +++ b/hphp/runtime/vm/jit/irgen.h @@ -274,13 +274,15 @@ bool conjureEndInlining(IRGS& env, bool builtin); /* - * We do a special-case optimization to partially inline 'singleton' accessor - * functions (functions that just return a static property if it's not null). + * We do two special-case optimizations to partially inline 'singleton' + * accessor functions (functions that just return a static local or static + * property if it's not null). * * This is exposed publically because the region translator drives inlining * decisions. */ void inlSingletonSProp(IRGS&, const Func*, PC clsOp, PC propOp); +void inlSingletonSLoc(IRGS&, const Func*, PC op); /* * In PGO mode, we use profiling to try to determine the most likely target diff --git a/hphp/runtime/vm/jit/irlower-closure.cpp b/hphp/runtime/vm/jit/irlower-closure.cpp index 075694ad11b69..795a3bd68bc28 100644 --- a/hphp/runtime/vm/jit/irlower-closure.cpp +++ b/hphp/runtime/vm/jit/irlower-closure.cpp @@ -50,6 +50,19 @@ void cgLdClosureCtx(IRLS& env, const IRInstruction* inst) { vmain(env) << load{obj[c_Closure::ctxOffset()], ctx}; } +void cgLdClosureStaticLoc(IRLS& env, const IRInstruction* inst) { + auto const extra = inst->extra(); + auto const func = extra->func; + auto const cls = func->implCls(); + auto const slot = lookupStaticSlotFromClosure(cls, extra->name); + auto const offset = cls->declPropOffset(slot); + + auto const dst = dstLoc(env, inst, 0).reg(); + auto const obj = srcLoc(env, inst, 0).reg(); + auto& v = vmain(env); + v << lea{obj[offset], dst}; +} + void cgStClosureCtx(IRLS& env, const IRInstruction* inst) { auto const obj = srcLoc(env, inst, 0).reg(); auto& v = vmain(env); diff --git a/hphp/runtime/vm/jit/irlower-static-loc.cpp b/hphp/runtime/vm/jit/irlower-static-loc.cpp new file mode 100644 index 0000000000000..0746037624ef2 --- /dev/null +++ b/hphp/runtime/vm/jit/irlower-static-loc.cpp @@ -0,0 +1,85 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "hphp/runtime/vm/jit/irlower-internal.h" + +#include "hphp/runtime/base/datatype.h" +#include "hphp/runtime/base/header-kind.h" +#include "hphp/runtime/base/rds.h" +#include "hphp/runtime/base/ref-data.h" + +#include "hphp/runtime/vm/jit/types.h" +#include "hphp/runtime/vm/jit/abi.h" +#include "hphp/runtime/vm/jit/code-gen-cf.h" +#include "hphp/runtime/vm/jit/code-gen-helpers.h" +#include "hphp/runtime/vm/jit/extra-data.h" +#include "hphp/runtime/vm/jit/ir-instruction.h" +#include "hphp/runtime/vm/jit/ir-opcode.h" +#include "hphp/runtime/vm/jit/ssa-tmp.h" +#include "hphp/runtime/vm/jit/type.h" +#include "hphp/runtime/vm/jit/vasm-gen.h" +#include "hphp/runtime/vm/jit/vasm-instr.h" +#include "hphp/runtime/vm/jit/vasm-reg.h" +#include "hphp/runtime/vm/runtime.h" + +#include "hphp/util/asm-x64.h" +#include "hphp/util/trace.h" + +namespace HPHP { namespace jit { namespace irlower { + +TRACE_SET_MOD(irlower); + +/////////////////////////////////////////////////////////////////////////////// + +void cgCheckStaticLoc(IRLS& env, const IRInstruction* inst) { + auto const extra = inst->extra(); + auto const link = rds::bindStaticLocal(extra->func, extra->name); + auto& v = vmain(env); + + auto const sf = checkRDSHandleInitialized(v, link.handle()); + fwdJcc(v, env, CC_NE, sf, inst->taken()); +} + +void cgLdStaticLoc(IRLS& env, const IRInstruction* inst) { + auto const extra = inst->extra(); + auto const link = rds::bindStaticLocal(extra->func, extra->name); + assertx(link.isNormal()); + auto const dst = dstLoc(env, inst, 0).reg(); + auto& v = vmain(env); + + v << lea{rvmtl()[link.handle() + rds::StaticLocalData::ref_offset()], dst}; +} + +void cgInitStaticLoc(IRLS& env, const IRInstruction* inst) { + auto const extra = inst->extra(); + auto const link = rds::bindStaticLocal(extra->func, extra->name); + assertx(link.isNormal()); + auto& v = vmain(env); + + // Initialize the RefData by storing the new value into it's TypedValue and + // incrementing the RefData reference count (which will set it to 1). + auto mem = rvmtl()[link.handle() + rds::StaticLocalData::ref_offset()]; + storeTV(v, mem + RefData::cellOffset(), srcLoc(env, inst, 0), inst->src(0)); + emitStoreRefCount(v, OneReference, mem); + v << storebi{uint8_t(HeaderKind::Ref), mem + (int)HeaderKindOffset}; + markRDSHandleInitialized(v, link.handle()); + + static_assert(sizeof(HeaderKind) == 1, ""); +} + +/////////////////////////////////////////////////////////////////////////////// + +}}} diff --git a/hphp/runtime/vm/jit/memory-effects.cpp b/hphp/runtime/vm/jit/memory-effects.cpp index b91173c42fc5f..77e5031505280 100644 --- a/hphp/runtime/vm/jit/memory-effects.cpp +++ b/hphp/runtime/vm/jit/memory-effects.cpp @@ -1061,6 +1061,9 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { // in AliasClass yet. return PureStore { AUnknown, inst.src(1) }; + case LdClosureStaticLoc: + return may_load_store(AFrameAny, AFrameAny); + ////////////////////////////////////////////////////////////////////// // Instructions that manipulate class-ref slots @@ -1763,6 +1766,8 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case LdMIPropStateAddr: case LdMIStateAddr: case LdPairBase: + case CheckStaticLoc: + case LdStaticLoc: case LdClsCns: case LdSubClsCns: case LdTypeCns: @@ -1958,6 +1963,7 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { return may_load_store(AEmpty, AEmpty); // Some that touch memory we might care about later, but currently don't: + case InitStaticLoc: case ColIsEmpty: case ColIsNEmpty: case ConvCellToBool: diff --git a/hphp/runtime/vm/jit/phi.cpp b/hphp/runtime/vm/jit/phi.cpp index 25a3c0aff52ec..ec85d57b06995 100644 --- a/hphp/runtime/vm/jit/phi.cpp +++ b/hphp/runtime/vm/jit/phi.cpp @@ -65,7 +65,7 @@ SinkableInst findSinkablePhiSrc( is_lval = true; } if (!val->inst()->is(LdLocAddr, LdStkAddr, LdMIStateAddr, - LdMIPropStateAddr)) { + LdMIPropStateAddr, LdStaticLoc)) { return {}; } assertx(val->inst()->numSrcs() <= 1); diff --git a/hphp/runtime/vm/jit/prof-data-serialize.cpp b/hphp/runtime/vm/jit/prof-data-serialize.cpp index 56a0cd18b17a1..df85fe6956fe9 100644 --- a/hphp/runtime/vm/jit/prof-data-serialize.cpp +++ b/hphp/runtime/vm/jit/prof-data-serialize.cpp @@ -1219,7 +1219,7 @@ void write_func(ProfDataSerializer& ser, const Func* func) { if (func->name() == s_86pinit.get()) return k86pinitSlot; if (func->name() == s_86sinit.get()) return k86sinitSlot; if (func->name() == s_86linit.get()) return k86linitSlot; - cls = func->cls(); + cls = getOwningClassForFunc(func); assertx(cls->getMethod(slot) == func); } return ~slot; diff --git a/hphp/runtime/vm/jit/refcount-opts.cpp b/hphp/runtime/vm/jit/refcount-opts.cpp index 21b8f45183ca2..505797037f488 100644 --- a/hphp/runtime/vm/jit/refcount-opts.cpp +++ b/hphp/runtime/vm/jit/refcount-opts.cpp @@ -1355,11 +1355,24 @@ bool irrelevant_inst(const IRInstruction& inst) { ////////////////////////////////////////////////////////////////////// +struct LdStaticLocHashEqual { + size_t operator()(const IRInstruction* inst) const { + return inst->extra()->hash(); + } + bool operator()(const IRInstruction* i1, const IRInstruction* i2) const { + return i1->extra()->equals(*i2->extra()); + } +}; + void find_alias_sets(Env& env) { FTRACE(2, "find_alias_sets --------------------------------------\n"); auto frame_to_ctx = sparse_idptr_map(env.unit.numTmps()); + jit::fast_set ldStaticLocs; + auto add = [&] (SSATmp* tmp) { if (!tmp->type().maybe(TCounted)) return; @@ -1391,6 +1404,11 @@ void find_alias_sets(Env& env) { } auto canon = canonical(tmp); + if (canon->inst()->is(LdStaticLoc)) { + auto const res = ldStaticLocs.insert(canon->inst()); + if (!res.second) canon = (*res.first)->dst(); + } + if (env.asetMap[canon] != -1) { id = env.asetMap[canon]; } else { diff --git a/hphp/runtime/vm/jit/translate-region.cpp b/hphp/runtime/vm/jit/translate-region.cpp index a27455c657dc1..7b80116c9edfb 100644 --- a/hphp/runtime/vm/jit/translate-region.cpp +++ b/hphp/runtime/vm/jit/translate-region.cpp @@ -328,9 +328,56 @@ bool tryTranslateSingletonInline(irgen::IRGS& irgs, return false; } - // Check for the static property pattern. + // First, check for the static local singleton pattern... + + // Lambda to check if CGetL and StaticLocInit refer to the same local. + auto has_same_local = [] (PC pc, const Captures& captures) { + if (captures.size() == 0) return false; + + auto cgetl = pc; + auto sli = captures[0]; + + assertx(peek_op(cgetl) == Op::CGetL); + assertx(peek_op(sli) == Op::StaticLocInit); + + return (getImm(sli, 0).u_IVA == getImm(cgetl, 0).u_IVA); + }; + + auto cgetl = Atom(Op::CGetL).onlyif(has_same_local); auto retc = Atom(Op::RetC); + // Look for a static local singleton pattern. + auto result = BCPattern { + Atom(Op::Null), + Atom(Op::StaticLocInit).capture(), + Atom(Op::IsTypeL), + Atom::alt( + Atom(Op::JmpZ).taken({cgetl, retc}), + Atom::seq(Atom(Op::JmpNZ), cgetl, retc) + ) + }.ignore( + {Op::AssertRATL, Op::AssertRATStk} + ).matchAnchored(funcd); + + if (result.found()) { + try { + irgen::prepareForNextHHBC(irgs, nullptr, ninst.source, false); + irgen::inlSingletonSLoc( + irgs, + funcd, + result.getCapture(0) + ); + } catch (const FailedIRGen& e) { + return false; + } + TRACE(1, "[singleton-sloc] %s <- %s\n", + funcd->fullName()->data(), + fcall.func()->fullName()->data()); + return true; + } + + // Not found; check for the static property pattern. + // Factory for String atoms that are required to match another captured // String opcode. auto same_string_as = [&] (int i) { @@ -354,7 +401,7 @@ bool tryTranslateSingletonInline(irgen::IRGS& irgs, auto cgets = Atom(Op::CGetS); // Look for a class static singleton pattern. - auto result = BCPattern { + result = BCPattern { Atom(Op::String).capture(), Atom(Op::String).capture(), Atom(Op::ClsRefGetC), diff --git a/hphp/runtime/vm/jit/translator.cpp b/hphp/runtime/vm/jit/translator.cpp index c8f72d8184698..62ada3c315621 100644 --- a/hphp/runtime/vm/jit/translator.cpp +++ b/hphp/runtime/vm/jit/translator.cpp @@ -345,6 +345,12 @@ static const struct { { OpInitThisLoc, {None, Local, OutUnknown }}, { OpFuncNumArgs, {None, Stack1, OutInt64 }}, + { OpStaticLocCheck, + {None, Stack1|Local, OutBoolean }}, + { OpStaticLocDef, + {Stack1, Local, OutVUnknown }}, + { OpStaticLocInit, + {Stack1, Local, OutVUnknown }}, { OpCatch, {None, Stack1, OutObject }}, { OpChainFaults, {StackTop2, Stack1, OutObject }}, { OpVerifyParamType, @@ -999,6 +1005,9 @@ bool dontGuardAnyInputs(const NormalizedInstruction& ni) { case Op::Shl: case Op::Shr: case Op::Silence: + case Op::StaticLocDef: + case Op::StaticLocCheck: + case Op::StaticLocInit: case Op::String: case Op::This: case Op::Throw: diff --git a/hphp/runtime/vm/verifier/check-func.cpp b/hphp/runtime/vm/verifier/check-func.cpp index 216217101d551..2e7e2559b90a4 100644 --- a/hphp/runtime/vm/verifier/check-func.cpp +++ b/hphp/runtime/vm/verifier/check-func.cpp @@ -1313,7 +1313,8 @@ bool FuncChecker::checkOp(State* cur, PC pc, Op op, Block* b) { } auto const numBound = getImm(pc, 0).u_IVA; auto const invoke = preCls->lookupMethod(s_invoke.get()); - if (invoke && numBound != preCls->numProperties()) { + if (invoke && + numBound != preCls->numProperties() - invoke->staticVars.size()) { ferror("CreateCl bound Closure {} with {} params instead of {}\n", preCls->name(), numBound, preCls->numProperties()); return false; @@ -2036,6 +2037,14 @@ bool FuncChecker::checkRxOp(State* cur, PC pc, Op op) { ferror("statics are forbidden in Rx functions: {}\n", opcodeToName(op)); return RuntimeOption::EvalRxVerifyBody < 2; + // unsafe: static locals + case Op::StaticLocCheck: + case Op::StaticLocDef: + case Op::StaticLocInit: + ferror("static locals are forbidden in Rx functions: {}\n", + opcodeToName(op)); + return RuntimeOption::EvalRxVerifyBody < 2; + // unsafe: defines and includes case Op::DefCls: case Op::DefClsNop: diff --git a/hphp/runtime/vm/verifier/fuzzer/fuzzer.ml b/hphp/runtime/vm/verifier/fuzzer/fuzzer.ml index 46f3a59508089..b3aa32ed82a66 100644 --- a/hphp/runtime/vm/verifier/fuzzer/fuzzer.ml +++ b/hphp/runtime/vm/verifier/fuzzer/fuzzer.ml @@ -423,6 +423,9 @@ let mut_imms (is : IS.t) : IS.t = match s with | BareThis b -> BareThis (mutate_bare b) | InitThisLoc id -> InitThisLoc (mutate_local_id id !mag) + | StaticLocCheck (id, str) -> StaticLocCheck (mutate_local_id id !mag, str) + | StaticLocInit (id, str) -> StaticLocInit (mutate_local_id id !mag, str) + | StaticLocDef (id, str) -> StaticLocDef (mutate_local_id id !mag, str) | OODeclExists k -> OODeclExists (mutate_kind k) | VerifyParamType p -> VerifyParamType (mutate_param_id p !mag) | VerifyOutType p -> VerifyOutType (mutate_param_id p !mag) @@ -633,6 +636,7 @@ let mutate_metadata (input : HP.t) = (param |> Hhas_param.type_info |> option_lift mutate_type_info) (param |> Hhas_param.default_value) in let mutate_body_data (body : Hhas_body.t) : Hhas_body.t = + let mutate_static_init s = s in Hhas_body.make (body |> Hhas_body.instrs) (body |> Hhas_body.decl_vars) @@ -642,6 +646,7 @@ let mutate_metadata (input : HP.t) = (body |> Hhas_body.is_memoize_wrapper_lsb |> mutate_bool) (body |> Hhas_body.params |> delete_map mutate_param) (body |> Hhas_body.return_type |> option_lift mutate_type_info) + (body |> Hhas_body.static_inits |> delete_map mutate_static_init) (body |> Hhas_body.doc_comment) (body |> Hhas_body.env) in let mutate_class_data (ids : Hhbc_id.Class.t list) (cls : Hhas_class.t) = diff --git a/hphp/runtime/vm/verifier/fuzzer/instr_utils.ml b/hphp/runtime/vm/verifier/fuzzer/instr_utils.ml index e2587fb81274b..b0f99400ae6e8 100644 --- a/hphp/runtime/vm/verifier/fuzzer/instr_utils.ml +++ b/hphp/runtime/vm/verifier/fuzzer/instr_utils.ml @@ -83,6 +83,7 @@ let stk_data : instruct -> stack_sig = function | IContFlow Switch _ | IContFlow SSwitch _ | IContFlow RetC + | IMisc StaticLocDef _ | IContFlow Throw | IGet ClsRefGetC _ | IGet ClsRefGetTS _ @@ -93,6 +94,7 @@ let stk_data : instruct -> stack_sig = function | ICall FPushObjMethodD _ | IIterator IterInit _ | IIterator IterInitK _ + | IMisc StaticLocInit _ | IMisc CheckReifiedGenericMismatch | IBasic PopC -> ["C"], [] | IBasic PopU -> ["U"], [] @@ -135,6 +137,7 @@ let stk_data : instruct -> stack_sig = function | IMutator CheckProp _ | IMisc This | IMisc BareThis _ + | IMisc StaticLocCheck _ | IMisc Catch | IMisc GetMemoKeyL _ | IGenerator CreateCont diff --git a/hphp/runtime/vm/verifier/fuzzer/random_utils.ml b/hphp/runtime/vm/verifier/fuzzer/random_utils.ml index 894ca8d6085be..39781839ceefc 100644 --- a/hphp/runtime/vm/verifier/fuzzer/random_utils.ml +++ b/hphp/runtime/vm/verifier/fuzzer/random_utils.ml @@ -236,6 +236,9 @@ let all_instrs (fn : IS.t) : lazy_instruct list = (fun () -> IMisc (BareThis (random_bare_op()))); (fun () -> IMisc CheckThis); (fun () -> IMisc (InitThisLoc (random_local ()))); + (fun () -> IMisc (StaticLocCheck (random_local (), ""))); + (fun () -> IMisc (StaticLocDef (random_local (), ""))); + (fun () -> IMisc (StaticLocInit (random_local (), ""))); (fun () -> IMisc Catch); (fun () -> IMisc (OODeclExists (random_class_kind ()))); (fun () -> IMisc (VerifyParamType (random_param_id ()))); diff --git a/hphp/test/quick/closure_static_vars.php b/hphp/test/quick/closure_static_vars.php new file mode 100644 index 0000000000000..2e1ec231dd0d0 --- /dev/null +++ b/hphp/test/quick/closure_static_vars.php @@ -0,0 +1,71 @@ +baz = function() { + static $a; + $a += 4; + print "A::baz $a\n"; + $priv = self::$priv; + $priv(); + }; + } +} + +class B extends A { +} + +$a = new A(); +$b = new B(); +$abaz = $a->baz; +$bbaz = $b->baz; + +$foo(); +$bar(); +$abaz(); +$bbaz(); +$foo(); +$bar(); +$abaz(); +$bbaz(); +$init_twice(0); +$init_twice(0); +$init_twice(0); +$init_twice(1); +$init_twice(1); +$init_twice(1); diff --git a/hphp/test/quick/closure_static_vars.php.expect b/hphp/test/quick/closure_static_vars.php.expect new file mode 100644 index 0000000000000..6c32ee8ff8d67 --- /dev/null +++ b/hphp/test/quick/closure_static_vars.php.expect @@ -0,0 +1,18 @@ +foo 1 +bar 2 +A::baz 4 +A::priv 4 +A::baz 4 +A::priv 8 +foo 2 +bar 4 +A::baz 8 +A::priv 12 +A::baz 8 +A::priv 16 +init0 46 +init0 45 +init0 44 +init1 45 +init1 46 +init1 47 diff --git a/hphp/test/quick/function-static-init.php b/hphp/test/quick/function-static-init.php new file mode 100644 index 0000000000000..1516ffe279ef7 --- /dev/null +++ b/hphp/test/quick/function-static-init.php @@ -0,0 +1,32 @@ + + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(4) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) +} +array(5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) +} +NULL diff --git a/hphp/test/quick/inherit-static-local.php b/hphp/test/quick/inherit-static-local.php new file mode 100644 index 0000000000000..3809f4ad88cff --- /dev/null +++ b/hphp/test/quick/inherit-static-local.php @@ -0,0 +1,23 @@ +propStr = $pn; + } + + final public function getPropStr() { + return $this->propStr; + } + + private static $reproStaticarr = array(); + + final public function repro($user_id) { + $key = $this->getPropStr() . (string)$this->propInt; + if (!(isset(self::$reproStaticarr[$key]))) { + self::$reproStaticarr[$key] = array(); + } + } +} + +$rd = new Repro("proj"); + +foreach (range(1,1000) as $i) { + $rd->repro(0); +} + +var_dump($rd); diff --git a/hphp/test/quick/pgo1.php.expect b/hphp/test/quick/pgo1.php.expect new file mode 100644 index 0000000000000..a6c05917be18b --- /dev/null +++ b/hphp/test/quick/pgo1.php.expect @@ -0,0 +1,6 @@ +object(Repro) (2) { + ["propStr":"Repro":private]=> + string(4) "proj" + ["propInt":"Repro":private]=> + int(5) +} diff --git a/hphp/test/quick/reflection_static_vars.php b/hphp/test/quick/reflection_static_vars.php new file mode 100644 index 0000000000000..0fb6a4cafed8e --- /dev/null +++ b/hphp/test/quick/reflection_static_vars.php @@ -0,0 +1,69 @@ + array(5)); + $b++; + echo "Bar::foo():\n"; + echo "a = ";var_dump($a); + echo "b = ";var_dump($b); + echo "c = ";var_dump($c); + echo "d = ";var_dump($d); + } +} + +function foo() { + static $a; + static $b = 3; + static $c = Bar::X; + static $d = array('4' => array(5)); + $b++; + echo "foo():\n"; + echo "a = ";var_dump($a); + echo "b = ";var_dump($b); + echo "c = ";var_dump($c); + echo "d = ";var_dump($d); +} + +$foo = function() { + static $a; + static $b = 3; + static $c = Bar::X; + static $d = array('4' => array(5)); + $b++; + echo "\$foo():\n"; + echo "a = ";var_dump($a); + echo "b = ";var_dump($b); + echo "c = ";var_dump($c); + echo "d = ";var_dump($d); +}; + +echo "---- ReflectionFunction ----\n"; +$rf = new ReflectionFunction('foo'); +echo "ReflectionFunction(1):"; var_dump($rf->getStaticVariables()); +foo(); +echo "ReflectionFunction(2):"; var_dump($rf->getStaticVariables()); +foo(); +$rf = new ReflectionFunction('foo'); +echo "ReflectionFunction(3):"; var_dump($rf->getStaticVariables()); + +echo "---- ReflectionMethod ----\n"; +$rf = new ReflectionMethod('Bar', 'foo'); +echo "ReflectionMethod(1):"; var_dump($rf->getStaticVariables()); +Bar::foo(); +echo "ReflectionMethod(2):"; var_dump($rf->getStaticVariables()); +Bar::foo(); +$rf = new ReflectionFunction('foo'); +echo "ReflectionMethod(3):"; var_dump($rf->getStaticVariables()); + +echo "---- ReflectionClosure ----\n"; +$rf = new ReflectionFunction($foo); +echo "ReflectionFunction(1-closure):"; var_dump($rf->getStaticVariables()); +$foo(); +echo "ReflectionFunction(2-closure):"; var_dump($rf->getStaticVariables()); +$foo(); +$rf = new ReflectionFunction($foo); +echo "ReflectionFunction(3-closure):"; var_dump($rf->getStaticVariables()); diff --git a/hphp/test/quick/reflection_static_vars.php.expect b/hphp/test/quick/reflection_static_vars.php.expect new file mode 100644 index 0000000000000..f2f9c2264c3bd --- /dev/null +++ b/hphp/test/quick/reflection_static_vars.php.expect @@ -0,0 +1,195 @@ +---- ReflectionFunction ---- +ReflectionFunction(1):array(4) { + ["a"]=> + NULL + ["b"]=> + NULL + ["c"]=> + NULL + ["d"]=> + NULL +} +foo(): +a = NULL +b = int(4) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionFunction(2):array(4) { + ["a"]=> + NULL + ["b"]=> + int(4) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} +foo(): +a = NULL +b = int(5) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionFunction(3):array(4) { + ["a"]=> + NULL + ["b"]=> + int(5) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} +---- ReflectionMethod ---- +ReflectionMethod(1):array(4) { + ["a"]=> + NULL + ["b"]=> + NULL + ["c"]=> + NULL + ["d"]=> + NULL +} +Bar::foo(): +a = NULL +b = int(4) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionMethod(2):array(4) { + ["a"]=> + NULL + ["b"]=> + int(4) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} +Bar::foo(): +a = NULL +b = int(5) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionMethod(3):array(4) { + ["a"]=> + NULL + ["b"]=> + int(5) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} +---- ReflectionClosure ---- +ReflectionFunction(1-closure):array(4) { + ["a"]=> + NULL + ["b"]=> + NULL + ["c"]=> + NULL + ["d"]=> + NULL +} +$foo(): +a = NULL +b = int(4) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionFunction(2-closure):array(4) { + ["a"]=> + NULL + ["b"]=> + int(4) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} +$foo(): +a = NULL +b = int(5) +c = string(1) "x" +d = array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } +} +ReflectionFunction(3-closure):array(4) { + ["a"]=> + NULL + ["b"]=> + int(5) + ["c"]=> + string(1) "x" + ["d"]=> + array(1) { + [4]=> + array(1) { + [0]=> + int(5) + } + } +} diff --git a/hphp/test/quick/reflection_static_vars.php.norepo b/hphp/test/quick/reflection_static_vars.php.norepo new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/hphp/test/quick/reflection_static_vars.php.opts b/hphp/test/quick/reflection_static_vars.php.opts new file mode 100644 index 0000000000000..bd481754d84ea --- /dev/null +++ b/hphp/test/quick/reflection_static_vars.php.opts @@ -0,0 +1,2 @@ +-vEval.JitEnableRenameFunction=true +-vEval.EnablePHP=1 diff --git a/hphp/test/quick/static-in-vector.php b/hphp/test/quick/static-in-vector.php new file mode 100644 index 0000000000000..b77be5340bb60 --- /dev/null +++ b/hphp/test/quick/static-in-vector.php @@ -0,0 +1,10 @@ +priv(); + } +} + +class B extends A { +} + +$a = new A(); +$b = new B(); + +foo(); +bar(); +$a->baz(); +$b->baz(); +foo(); +bar(); +$a->baz(); +$b->baz(); +init_twice(0); +init_twice(0); +init_twice(0); +init_twice(1); +init_twice(1); +init_twice(1); diff --git a/hphp/test/quick/static_vars.php.expect b/hphp/test/quick/static_vars.php.expect new file mode 100644 index 0000000000000..6c32ee8ff8d67 --- /dev/null +++ b/hphp/test/quick/static_vars.php.expect @@ -0,0 +1,18 @@ +foo 1 +bar 2 +A::baz 4 +A::priv 4 +A::baz 4 +A::priv 8 +foo 2 +bar 4 +A::baz 8 +A::priv 12 +A::baz 8 +A::priv 16 +init0 46 +init0 45 +init0 44 +init1 45 +init1 46 +init1 47 diff --git a/hphp/test/slow/compilation/closure_static.php b/hphp/test/slow/compilation/closure_static.php index 9035e39d52aaa..a037772fb0254 100644 --- a/hphp/test/slow/compilation/closure_static.php +++ b/hphp/test/slow/compilation/closure_static.php @@ -2,10 +2,10 @@ <<__EntryPoint>> function main_closure_static() { - $a = Map{}; - $foo = function () use($a) { - if (!$a) $a['xxxxxxxxxxxxx'] = str_repeat('x', 2048); - echo "hello\n"; - }; - $foo(); +$foo = function () { + static $a = Map {}; + if (!$a) $a['xxxxxxxxxxxxx'] = str_repeat('x', 2048); + echo "hello\n"; +}; +$foo(); } diff --git a/hphp/test/slow/compilation/infinite-static-local.php b/hphp/test/slow/compilation/infinite-static-local.php new file mode 100644 index 0000000000000..8361b38e641bf --- /dev/null +++ b/hphp/test/slow/compilation/infinite-static-local.php @@ -0,0 +1,13 @@ +> +function main_infinite_static_local() { +var_dump(main()); +} diff --git a/hphp/test/slow/compilation/infinite-static-local.php.expect b/hphp/test/slow/compilation/infinite-static-local.php.expect new file mode 100644 index 0000000000000..3b466ade33d61 --- /dev/null +++ b/hphp/test/slow/compilation/infinite-static-local.php.expect @@ -0,0 +1,16 @@ +vec(3) { + vec(0) { + } + vec(1) { + vec(0) { + } + } + vec(2) { + vec(0) { + } + vec(1) { + vec(0) { + } + } + } +} diff --git a/hphp/test/slow/dv_array/ext_reflection/reflection_func.php b/hphp/test/slow/dv_array/ext_reflection/reflection_func.php index 339367f16b1b8..23a65b6e7e885 100644 --- a/hphp/test/slow/dv_array/ext_reflection/reflection_func.php +++ b/hphp/test/slow/dv_array/ext_reflection/reflection_func.php @@ -2,6 +2,7 @@ <> function foo($a, $b = null, $c = null): void { + static $foo = 3; var_dump($a); } @@ -21,12 +22,14 @@ public function a($a): void { <<__EntryPoint>> function main() { $foo_fn = new ReflectionFunction('foo'); + var_dump($foo_fn->getStaticVariables()); var_dump($foo_fn->getAttributes()); var_dump($foo_fn->getAttributesRecursive()); var_dump($foo_fn->getParameters()); var_dump($foo_fn->getReturnType()); $foo_fn->invoke(varray[1]); $a_meth = new ReflectionMethod(Baz::class, 'a'); + var_dump($a_meth->getStaticVariables()); var_dump($a_meth->getAttributes()); var_dump($a_meth->getAttributesRecursive()); var_dump($a_meth->getParameters()); diff --git a/hphp/test/slow/dv_array/ext_reflection/reflection_func.php.expectf b/hphp/test/slow/dv_array/ext_reflection/reflection_func.php.expectf index 40e7f50b66152..c7cdc7bce1f62 100644 --- a/hphp/test/slow/dv_array/ext_reflection/reflection_func.php.expectf +++ b/hphp/test/slow/dv_array/ext_reflection/reflection_func.php.expectf @@ -1,3 +1,7 @@ +array(1) { + ["foo"]=> + NULL +} array(2) { ["A"]=> array(2) { @@ -11,7 +15,7 @@ array(2) { } } -Deprecated: ReflectionFunctionAbstract::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 25 +Deprecated: ReflectionFunctionAbstract::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 27 array(2) { ["A"]=> array(2) { @@ -163,15 +167,17 @@ array(1) { [0]=> int(1) } +array(0) { +} array(1) { ["Bing"]=> array(0) { } } -Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 31 +Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 34 -Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 31 +Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 34 array(2) { ["Bing"]=> array(0) { diff --git a/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php b/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php index 339367f16b1b8..23a65b6e7e885 100644 --- a/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php +++ b/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php @@ -2,6 +2,7 @@ <> function foo($a, $b = null, $c = null): void { + static $foo = 3; var_dump($a); } @@ -21,12 +22,14 @@ public function a($a): void { <<__EntryPoint>> function main() { $foo_fn = new ReflectionFunction('foo'); + var_dump($foo_fn->getStaticVariables()); var_dump($foo_fn->getAttributes()); var_dump($foo_fn->getAttributesRecursive()); var_dump($foo_fn->getParameters()); var_dump($foo_fn->getReturnType()); $foo_fn->invoke(varray[1]); $a_meth = new ReflectionMethod(Baz::class, 'a'); + var_dump($a_meth->getStaticVariables()); var_dump($a_meth->getAttributes()); var_dump($a_meth->getAttributesRecursive()); var_dump($a_meth->getParameters()); diff --git a/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php.expectf b/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php.expectf index e6d92df7e5f6f..c01f1ea650e19 100644 --- a/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php.expectf +++ b/hphp/test/slow/dv_array_hack_arr/ext_reflection/reflection_func.php.expectf @@ -1,3 +1,7 @@ +dict(1) { + ["foo"]=> + NULL +} dict(2) { ["A"]=> vec(2) { @@ -9,7 +13,7 @@ dict(2) { } } -Deprecated: ReflectionFunctionAbstract::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 25 +Deprecated: ReflectionFunctionAbstract::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 27 dict(2) { ["A"]=> vec(2) { @@ -155,15 +159,17 @@ object(ReflectionType) (1) { vec(1) { int(1) } +dict(0) { +} dict(1) { ["Bing"]=> vec(0) { } } -Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 31 +Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 34 -Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 31 +Deprecated: ReflectionMethod::getAttributesRecursive: This function is being removed as it has been broken for some time in %s on line 34 dict(2) { ["Bing"]=> vec(0) { diff --git a/hphp/test/slow/inlining/singleton.php b/hphp/test/slow/inlining/singleton.php index b80968c3c34ac..acb3b4a83cad9 100644 --- a/hphp/test/slow/inlining/singleton.php +++ b/hphp/test/slow/inlining/singleton.php @@ -34,10 +34,10 @@ public static function getNSame() { } /** - * Toplevel functions with singleton statics. + * Toplevel functions with static local singletons. * * The *_loop functions are the same as the normal versions; they're duplicated - * only to get different statics. + * only to get fresh the static locals. */ function check_null_same() { if (CheckNullSameStatics::$instance === null) { diff --git a/hphp/test/slow/lang/reflection.php b/hphp/test/slow/lang/reflection.php index 98fc968b68ab0..418b8e0b5d0f9 100644 --- a/hphp/test/slow/lang/reflection.php +++ b/hphp/test/slow/lang/reflection.php @@ -7,15 +7,15 @@ #=============================================================================== # ReflectionFunction. -class State { static $staticX = 4; } - /** * This is f's doc comment. */ function f($a, &$b, $c=null, $d=array(1, 2, SOME_CONSTANT)) { + static $staticX = 4; + static $staticY; print "In f()\n"; - State::$staticX++; - $x = State::$staticX; + $staticX++; + $x = $staticX; return $x; } @@ -53,9 +53,9 @@ function f($a, &$b, $c=null, $d=array(1, 2, SOME_CONSTANT)) { var_dump($rf->getParameters()); print "\n"; - - - +print "--- getStaticVariables(\"f\") ---\n"; +var_dump($rf->getStaticVariables()); +print "\n"; print "--- isInternal(\"f\") ---\n"; var_dump($rf->isInternal()); @@ -77,10 +77,10 @@ function f($a, &$b, $c=null, $d=array(1, 2, SOME_CONSTANT)) { var_dump($rf->invokeArgs(array("a", &$b, "c"))); print "\n"; - - - - +print "--- getStaticVariables(\"f\") ---\n"; +$rf = new ReflectionFunction("f"); +var_dump($rf->getStaticVariables()); +print "\n"; /** * This is g's doc comment. diff --git a/hphp/test/slow/lang/reflection.php.expectf b/hphp/test/slow/lang/reflection.php.expectf index b50ac6df67d2e..83ce438ebf71c 100644 --- a/hphp/test/slow/lang/reflection.php.expectf +++ b/hphp/test/slow/lang/reflection.php.expectf @@ -5,7 +5,7 @@ string(35) "/** */" --- getStartLine("f") --- -int(15) +int(13) --- getEndLine("f") --- int(20) @@ -195,6 +195,14 @@ array(4) { } } +--- getStaticVariables("f") --- +array(2) { + ["staticX"]=> + NULL + ["staticY"]=> + NULL +} + --- isInternal("f") --- bool(false) @@ -206,7 +214,7 @@ string(%d) "/** * This is f's doc comment. */ Function [ function f ] { - @@ %s/test/slow/lang/reflection.php 15 - 20 + @@ %s/test/slow/lang/reflection.php 13 - 20 - Parameters [4] { Parameter #0 [ $a ] @@ -227,6 +235,14 @@ int(5) In f() int(6) +--- getStaticVariables("f") --- +array(2) { + ["staticX"]=> + int(6) + ["staticY"]=> + NULL +} + --- invoke("g") --- In g(a, b, some string) NULL diff --git a/hphp/test/slow/memoize/namespacebar.php.inc b/hphp/test/slow/memoize/namespacebar.php.inc index 2db083c17818e..8c221c8e5e324 100644 --- a/hphp/test/slow/memoize/namespacebar.php.inc +++ b/hphp/test/slow/memoize/namespacebar.php.inc @@ -4,9 +4,9 @@ namespace Bar { trait TN { use \Foo\TN; - static $j = 30; <<__Memoize>>public function test($a = 0) { - return $a + self::$j++; + static $i = 30; + return $a + $i++; } } } diff --git a/hphp/test/slow/memoize/namespacefoo.php.inc b/hphp/test/slow/memoize/namespacefoo.php.inc index 7a63fb947b6db..ad65e8785edc2 100644 --- a/hphp/test/slow/memoize/namespacefoo.php.inc +++ b/hphp/test/slow/memoize/namespacefoo.php.inc @@ -3,7 +3,6 @@ namespace Foo { trait TN { - static $i = 1000; - <<__Memoize>>public function test() {return self::$i++;} + <<__Memoize>>public function test() {static $i = 1000; return $i++;} } } diff --git a/hphp/test/slow/phpism/disable_static_local_variables.php b/hphp/test/slow/phpism/disable_static_local_variables.php new file mode 100644 index 0000000000000..f0cc4e53823dc --- /dev/null +++ b/hphp/test/slow/phpism/disable_static_local_variables.php @@ -0,0 +1,9 @@ +> +function main_closure_get_static_vars() { +$a = 4; +$b = 'i am a string'; + +$lam = function ($c) use ($a, $b) { + return $a + $c; +}; + +$refl = new ReflectionFunction('fn'); +var_dump($refl->getStaticVariables()); + +$refl = new ReflectionFunction($lam); +var_dump($refl->getStaticVariables()); + +$refl = new ReflectionMethod('C', 'meth'); +var_dump($refl->getStaticVariables()); +} diff --git a/hphp/test/slow/reflection/closure_get_static_vars.php.expect b/hphp/test/slow/reflection/closure_get_static_vars.php.expect new file mode 100644 index 0000000000000..bca9823ff952d --- /dev/null +++ b/hphp/test/slow/reflection/closure_get_static_vars.php.expect @@ -0,0 +1,10 @@ +array(0) { +} +array(2) { + ["a"]=> + int(4) + ["b"]=> + string(13) "i am a string" +} +array(0) { +} diff --git a/hphp/test/slow/reflection/closure_static_locals.php b/hphp/test/slow/reflection/closure_static_locals.php new file mode 100644 index 0000000000000..ce39720138a81 --- /dev/null +++ b/hphp/test/slow/reflection/closure_static_locals.php @@ -0,0 +1,28 @@ +getStaticVariables()); + + $fn = function () { static $something = 2; }; + $fn(); + $x = new ReflectionFunction($fn); + var_dump($x->getStaticVariables()); // 'something' is 2; we've done + // the StaticLocInit + + // Just a use var + $y = 2; + $x = new ReflectionFunction($x ==> $x + $y); + var_dump($x->getStaticVariables()); +} + +<<__EntryPoint>> +function main_closure_static_locals() { +x(); +} diff --git a/hphp/test/slow/reflection/closure_static_locals.php.expect b/hphp/test/slow/reflection/closure_static_locals.php.expect new file mode 100644 index 0000000000000..b7a885f1a6749 --- /dev/null +++ b/hphp/test/slow/reflection/closure_static_locals.php.expect @@ -0,0 +1,12 @@ +array(1) { + ["something"]=> + NULL +} +array(1) { + ["something"]=> + int(2) +} +array(1) { + ["y"]=> + int(2) +} \ No newline at end of file diff --git a/hphp/test/slow/reflection/closure_use_vars.php b/hphp/test/slow/reflection/closure_use_vars.php new file mode 100644 index 0000000000000..c4c73439bbade --- /dev/null +++ b/hphp/test/slow/reflection/closure_use_vars.php @@ -0,0 +1,20 @@ +> +function main_closure_use_vars() { +$refVariable = false; +$refName = 'refVariable'; +$callback = function() use (&$refVariable) { var_dump($refVariable); }; +$function = new \ReflectionFunction($callback); +$staticVariables = $function->getStaticVariables(); +var_dump($staticVariables); +var_dump(array_key_exists($refName, $staticVariables)); +var_dump($refVariable); +$staticVariables[$refName] = true; +var_dump($refVariable); +$callback(); +$refVariable = 'foo'; +var_dump($staticVariables[$refName]); +$callback(); +} diff --git a/hphp/test/slow/reflection/closure_use_vars.php.expect b/hphp/test/slow/reflection/closure_use_vars.php.expect new file mode 100644 index 0000000000000..3b260e38d5021 --- /dev/null +++ b/hphp/test/slow/reflection/closure_use_vars.php.expect @@ -0,0 +1,10 @@ +array(1) { + ["refVariable"]=> + &bool(false) +} +bool(true) +bool(false) +bool(true) +bool(true) +string(3) "foo" +string(3) "foo" diff --git a/hphp/test/slow/reflection/lambda_uninit_capture.php b/hphp/test/slow/reflection/lambda_uninit_capture.php index a9c2c0e12158b..6ec81fa3adfce 100644 --- a/hphp/test/slow/reflection/lambda_uninit_capture.php +++ b/hphp/test/slow/reflection/lambda_uninit_capture.php @@ -6,6 +6,7 @@ function test() { $x = 2; $r = new ReflectionFunction($foo); var_dump($r->isClosure()); // true + var_dump($r->getStaticVariables()); // x, y $foo(); // x is not defined } diff --git a/hphp/test/slow/reflection/lambda_uninit_capture.php.expectf b/hphp/test/slow/reflection/lambda_uninit_capture.php.expectf index 72dccefcc2ac8..16bb5c94da6a6 100644 --- a/hphp/test/slow/reflection/lambda_uninit_capture.php.expectf +++ b/hphp/test/slow/reflection/lambda_uninit_capture.php.expectf @@ -1,3 +1,9 @@ bool(true) +array(2) { + ["y"]=> + int(1) + ["x"]=> + NULL +} Notice: Undefined variable: x in %s on line 5 diff --git a/hphp/test/slow/reflection_classes/1354.php b/hphp/test/slow/reflection_classes/1354.php index e87eef72e2a87..18982c479ae49 100644 --- a/hphp/test/slow/reflection_classes/1354.php +++ b/hphp/test/slow/reflection_classes/1354.php @@ -13,6 +13,7 @@ function method1($param1) { } } function func1(cls1 $p1, &$p2, $p3='def') { + static $a=1; var_dump($p1); } function func2($a) { @@ -25,6 +26,9 @@ function dump_func($func) { var_dump($func->isInternal()); var_dump($func->isUserDefined()); var_dump($func->isClosure()); + $vars = $func->getStaticVariables(); + var_dump(count($vars)); + var_dump(array_key_exists('a', $vars)); var_dump($func->getNumberOfParameters()); var_dump($func->getNumberOfRequiredParameters()); foreach ($func->getParameters() as $name => $param) { diff --git a/hphp/test/slow/reflection_classes/1354.php.expect b/hphp/test/slow/reflection_classes/1354.php.expect index bd3c611c53777..19aca99191d4a 100644 --- a/hphp/test/slow/reflection_classes/1354.php.expect +++ b/hphp/test/slow/reflection_classes/1354.php.expect @@ -2,6 +2,8 @@ string(5) "func1" bool(false) bool(true) bool(false) +int(1) +bool(true) int(3) int(2) int(0) @@ -47,6 +49,8 @@ string(7) "method1" bool(false) bool(true) bool(false) +int(0) +bool(false) int(1) int(1) int(0) @@ -102,6 +106,8 @@ string(7) "method1" bool(false) bool(true) bool(false) +int(0) +bool(false) int(1) int(1) int(0) @@ -135,6 +141,8 @@ string(7) "method1" bool(false) bool(true) bool(false) +int(0) +bool(false) int(1) int(1) int(0) @@ -190,6 +198,8 @@ string(7) "method1" bool(false) bool(true) bool(false) +int(0) +bool(false) int(1) int(1) int(0) diff --git a/hphp/test/slow/rx/body/static-locals.php b/hphp/test/slow/rx/body/static-locals.php new file mode 100644 index 0000000000000..b1b976520e821 --- /dev/null +++ b/hphp/test/slow/rx/body/static-locals.php @@ -0,0 +1,14 @@ +> +function test() { + static $x = 1; // StaticLocInit + $x++; // prevent dce from eating $x + static $y = Vector{1}; // StaticLocCheck, StaticLocDef +} + +<<__EntryPoint>> +function main() { + test(); + echo "Done\n"; +} diff --git a/hphp/test/slow/rx/body/static-locals.php.expectf b/hphp/test/slow/rx/body/static-locals.php.expectf new file mode 100644 index 0000000000000..02cf2792ae8c1 --- /dev/null +++ b/hphp/test/slow/rx/body/static-locals.php.expectf @@ -0,0 +1,7 @@ +Verification Error (unit %s/test/slow/rx/body/static-locals.php func test): static locals are forbidden in Rx functions: StaticLocInit + +Verification Error (unit %s/test/slow/rx/body/static-locals.php func test): static locals are forbidden in Rx functions: StaticLocCheck + +Verification Error (unit %s/test/slow/rx/body/static-locals.php func test): static locals are forbidden in Rx functions: StaticLocDef + +Done diff --git a/hphp/test/slow/spec/tests/basic_concepts/storage_duration.php b/hphp/test/slow/spec/tests/basic_concepts/storage_duration.php index 2b6a75506ef4b..b757d7d331293 100644 --- a/hphp/test/slow/spec/tests/basic_concepts/storage_duration.php +++ b/hphp/test/slow/spec/tests/basic_concepts/storage_duration.php @@ -43,11 +43,11 @@ public function __toString() echo "---------------- after \$av1 init -------------------\n"; -class State { static $sv1 = TRUE; static $sv2 = 0; static $sv3 = NULL; } +static $sv1 = TRUE; echo "---------------- after \$sv1 decl -------------------\n"; -State::$sv1 = new Point(0, 2); +$sv1 = new Point(0, 2); echo "---------------- after \$sv1 init -------------------\n"; @@ -59,9 +59,11 @@ function doit($p1) echo "---------------- after \$av2 init -------------------\n"; + static $sv2 = 0; + echo "---------------- after \$sv2 decl -------------------\n"; - State::$sv2 = new Point(1, 2); + $sv2 = new Point(1, 2); echo "---------------- after \$sv2 init -------------------\n"; @@ -73,9 +75,11 @@ function doit($p1) echo "---------------- after \$av3 init -------------------\n"; + static $sv3 = NULL; + echo "---------------- after \$sv3 decl -------------------\n"; - State::$sv3 = new Point(2, 2); + $sv3 = new Point(2, 2); echo "---------------- after \$sv3 init -------------------\n"; // ... diff --git a/hphp/test/slow/spec/tests/expressions/primary_expressions/intrinsics_unset.php b/hphp/test/slow/spec/tests/expressions/primary_expressions/intrinsics_unset.php index 51b5ef3fd2952..9c4192b9b3318 100644 --- a/hphp/test/slow/spec/tests/expressions/primary_expressions/intrinsics_unset.php +++ b/hphp/test/slow/spec/tests/expressions/primary_expressions/intrinsics_unset.php @@ -128,14 +128,15 @@ function g3($p1, &$p2) echo "---------- unsetting inside a function (static) ------------\n"; -class State { static $count = 0; } function g4() { - ++State::$count; - echo "count = ".State::$count."\n"; + static $count = 0; + ++$count; + echo "count = $count\n"; - var_dump(isset(State::$count)); - var_dump(false); + var_dump(isset($count)); + unset($count); // unsets local "version" in current scope + var_dump(isset($count)); } g4(); diff --git a/hphp/test/slow/spec/tests/variables/unsetting_variables.php b/hphp/test/slow/spec/tests/variables/unsetting_variables.php index 4abcfb712120f..80492c774463b 100644 --- a/hphp/test/slow/spec/tests/variables/unsetting_variables.php +++ b/hphp/test/slow/spec/tests/variables/unsetting_variables.php @@ -27,6 +27,14 @@ echo "---\n"; //*/ +static $sVar1 = 11; +echo "Top1: \$sVar1 = " . $sVar1 . "\n"; +//unset($sVar1); // unsets this variable +//echo "Top1: \$sVar1 = " . $sVar1 . "\n"; + +$tmp =& $sVar1; +echo "Top1: \$tmp = " . $tmp . "\n"; + $gVar1 = 21; echo "Top1: \$gVar1 = " . $gVar1 . "\n"; //unset($gVar1); // unsets this variable @@ -54,6 +62,10 @@ function f($p) global $sVar1; $sVar1 = -1; // hmm; is setting the top-level static echo "f:2 \$sVar1 = " . $sVar1 . "\n"; + static $sVar1 = 12; + echo "f:2 \$sVar1 = " . $sVar1 . "\n"; + ++$sVar1; +// unset($sVar1); // removes this alias; doesn't unset the inner static itself global $gVar1; echo "f:2 \$gVar1 = " . $gVar1 . "\n"; @@ -85,7 +97,22 @@ function f($p) echo "---\n"; //*/ echo "Top2: \$sVar1 = " . $sVar1 . "\n"; +echo "Top2: \$tmp = " . $tmp . "\n"; echo "Top2: \$gVar1 = " . $gVar1 . "\n"; echo "-----------------------------------------\n"; + +function f2($p) +{ + static $s2 = 100; + + if ($p) + { +// global $s2; // with this in, var_dump($s2) is NULL, becuse there is no such global + var_dump($s2); + var_dump(isset($s2)); + } +} + +f2(TRUE); diff --git a/hphp/test/slow/spec/tests/variables/unsetting_variables.php.expectf b/hphp/test/slow/spec/tests/variables/unsetting_variables.php.expectf index f66fcf683e295..3adb22722b358 100644 --- a/hphp/test/slow/spec/tests/variables/unsetting_variables.php.expectf +++ b/hphp/test/slow/spec/tests/variables/unsetting_variables.php.expectf @@ -4,6 +4,8 @@ bool(true) Top1: CON1 = 1 Top1: CON2 = 2 --- +Top1: $sVar1 = 11 +Top1: $tmp = 11 Top1: $gVar1 = 21 --- Inside f, block-level 1 f:1 CON1 = 1 @@ -15,11 +17,12 @@ f:2 CON2 = 2 f:2 CON3 = 3 f:2 CON4 = 4 f:2 $sVar1 = -1 +f:2 $sVar1 = 12 f:2 $gVar1 = 21 f:2 $gVar1 = 25 --- Back at f:1 f:1 CON4 = 4 -f:1 $sVar1 = -1 +f:1 $sVar1 = 13 f:1 $gVar1 = 25 --- Back at top-level --- Inside f, block-level 1 @@ -32,11 +35,12 @@ f:2 CON2 = 2 f:2 CON3 = 3 f:2 CON4 = 4 f:2 $sVar1 = -1 +f:2 $sVar1 = 13 f:2 $gVar1 = 25 f:2 $gVar1 = 25 --- Back at f:1 f:1 CON4 = 4 -f:1 $sVar1 = -1 +f:1 $sVar1 = 14 f:1 $gVar1 = 25 --- Back at top-level Top2: CON1 = 1 @@ -45,5 +49,8 @@ Top2: CON3 = 3 Top2: CON4 = 4 --- Top2: $sVar1 = -1 +Top2: $tmp = -1 Top2: $gVar1 = 25 ----------------------------------------- +int(100) +bool(true) diff --git a/hphp/test/slow/spec/tests/variables/variable_kinds.php b/hphp/test/slow/spec/tests/variables/variable_kinds.php index 9465309d9150a..cc0db4dafad89 100644 --- a/hphp/test/slow/spec/tests/variables/variable_kinds.php +++ b/hphp/test/slow/spec/tests/variables/variable_kinds.php @@ -26,6 +26,29 @@ function doit($p1) // assigned the value TRUE when called echo "---------------- Array elements -------------------\n"; +echo "---------------- Function statics -------------------\n"; + +function f() +{ + $lv = 1; + static $fs = 1; + static $fs2; + var_dump($fs2); // show default value is NULL + + echo "\$lv = $lv, \$fs = $fs\n"; + ++$lv; + ++$fs; + if (TRUE) + { + static $fs3 = 99; + echo "\$fs3 = $fs3\n"; + ++$fs3; + } +} + +for ($i = 1; $i <= 3; ++$i) + f(); + echo "---------------- recursive function example -------------------\n"; function factorial($i) diff --git a/hphp/test/slow/spec/tests/variables/variable_kinds.php.expectf b/hphp/test/slow/spec/tests/variables/variable_kinds.php.expectf index 466c34a709cd3..50313437dc60e 100644 --- a/hphp/test/slow/spec/tests/variables/variable_kinds.php.expectf +++ b/hphp/test/slow/spec/tests/variables/variable_kinds.php.expectf @@ -1,5 +1,15 @@ ---------------- Local variables ------------------- ---------------- Array elements ------------------- +---------------- Function statics ------------------- +NULL +$lv = 1, $fs = 1 +$fs3 = 99 +NULL +$lv = 1, $fs = 2 +$fs3 = 100 +NULL +$lv = 1, $fs = 3 +$fs3 = 101 ---------------- recursive function example ------------------- $result = 3628800 ---------------- Global Constants ------------------- diff --git a/hphp/test/slow/static_statement/1394.php b/hphp/test/slow/static_statement/1394.php new file mode 100644 index 0000000000000..12d20195002f0 --- /dev/null +++ b/hphp/test/slow/static_statement/1394.php @@ -0,0 +1,22 @@ +> +function main_1394() { +test_unset_static(); +test_unset_static(); +test_unset_static(); +} diff --git a/hphp/test/slow/static_statement/1394.php.expectf b/hphp/test/slow/static_statement/1394.php.expectf new file mode 100644 index 0000000000000..a0a4770bbb5b4 --- /dev/null +++ b/hphp/test/slow/static_statement/1394.php.expectf @@ -0,0 +1,27 @@ +value of static_var before unset: 1 +bool(true) +bool(false) + +Notice: Undefined variable: static_var in %s/test/slow/static_statement/1394.php on line 10 +value of static_var after unset: +bool(false) +bool(true) +value of static_var after new assignment: 20 +value of static_var before unset: 2 +bool(true) +bool(false) + +Notice: Undefined variable: static_var in %s/test/slow/static_statement/1394.php on line 10 +value of static_var after unset: +bool(false) +bool(true) +value of static_var after new assignment: 20 +value of static_var before unset: 3 +bool(true) +bool(false) + +Notice: Undefined variable: static_var in %s/test/slow/static_statement/1394.php on line 10 +value of static_var after unset: +bool(false) +bool(true) +value of static_var after new assignment: 20 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1395.php b/hphp/test/slow/static_statement/1395.php new file mode 100644 index 0000000000000..728a5ba640a06 --- /dev/null +++ b/hphp/test/slow/static_statement/1395.php @@ -0,0 +1,14 @@ +> +function main_1395() { +test(); +} diff --git a/hphp/test/slow/static_statement/1395.php.expect b/hphp/test/slow/static_statement/1395.php.expect new file mode 100644 index 0000000000000..b74e882ae378d --- /dev/null +++ b/hphp/test/slow/static_statement/1395.php.expect @@ -0,0 +1 @@ +31 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1396.php b/hphp/test/slow/static_statement/1396.php new file mode 100644 index 0000000000000..0fca00386d28b --- /dev/null +++ b/hphp/test/slow/static_statement/1396.php @@ -0,0 +1,16 @@ +> +function main_1396() { +A::test(); +} diff --git a/hphp/test/slow/static_statement/1396.php.expect b/hphp/test/slow/static_statement/1396.php.expect new file mode 100644 index 0000000000000..b74e882ae378d --- /dev/null +++ b/hphp/test/slow/static_statement/1396.php.expect @@ -0,0 +1 @@ +31 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1397.php b/hphp/test/slow/static_statement/1397.php new file mode 100644 index 0000000000000..ad410230069dd --- /dev/null +++ b/hphp/test/slow/static_statement/1397.php @@ -0,0 +1,12 @@ +> +function main_1397() { +$static_var = 1; + echo $static_var . "\n"; + static $static_var; + echo $static_var . "\n"; + $static_var ++; + echo $static_var . "\n"; +} diff --git a/hphp/test/slow/static_statement/1397.php.expect b/hphp/test/slow/static_statement/1397.php.expect new file mode 100644 index 0000000000000..a594e1495c11e --- /dev/null +++ b/hphp/test/slow/static_statement/1397.php.expect @@ -0,0 +1,3 @@ +1 + +1 diff --git a/hphp/test/slow/static_statement/1398.php b/hphp/test/slow/static_statement/1398.php new file mode 100644 index 0000000000000..07bb1a246911c --- /dev/null +++ b/hphp/test/slow/static_statement/1398.php @@ -0,0 +1,7 @@ +> +function main_1400() { +static $static_var = 1; +echo $static_var . "\n"; +test(); +test(); +} diff --git a/hphp/test/slow/static_statement/1400.php.expect b/hphp/test/slow/static_statement/1400.php.expect new file mode 100644 index 0000000000000..112f72dfc3d37 --- /dev/null +++ b/hphp/test/slow/static_statement/1400.php.expect @@ -0,0 +1,7 @@ +1 +-1 +2 +3 +3 +2 +3 diff --git a/hphp/test/slow/static_statement/1401.php b/hphp/test/slow/static_statement/1401.php new file mode 100644 index 0000000000000..684f03843385a --- /dev/null +++ b/hphp/test/slow/static_statement/1401.php @@ -0,0 +1,10 @@ +> +function main_1401() { +static $static_var; +echo $static_var . "\n"; +$static_var = 1; +echo $static_var . "\n"; +} diff --git a/hphp/test/slow/static_statement/1401.php.expect b/hphp/test/slow/static_statement/1401.php.expect new file mode 100644 index 0000000000000..a464d9da742a6 --- /dev/null +++ b/hphp/test/slow/static_statement/1401.php.expect @@ -0,0 +1,2 @@ + +1 diff --git a/hphp/test/slow/static_statement/1402.php b/hphp/test/slow/static_statement/1402.php new file mode 100644 index 0000000000000..da66f9c37dd4c --- /dev/null +++ b/hphp/test/slow/static_statement/1402.php @@ -0,0 +1,15 @@ +> +function main_1402() { +test(); +} diff --git a/hphp/test/slow/static_statement/1402.php.expectf b/hphp/test/slow/static_statement/1402.php.expectf new file mode 100644 index 0000000000000..3acbfb8c51b26 --- /dev/null +++ b/hphp/test/slow/static_statement/1402.php.expectf @@ -0,0 +1,4 @@ + +Notice: Undefined variable: static_var in %s/test/slow/static_statement/1402.php on line 7 +NULL +int(4) \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1403.php b/hphp/test/slow/static_statement/1403.php new file mode 100644 index 0000000000000..824f588ac6380 --- /dev/null +++ b/hphp/test/slow/static_statement/1403.php @@ -0,0 +1,8 @@ +> +function main_1406() { +echo test(); +} diff --git a/hphp/test/slow/static_statement/1406.php.expect b/hphp/test/slow/static_statement/1406.php.expect new file mode 100644 index 0000000000000..56a6051ca2b02 --- /dev/null +++ b/hphp/test/slow/static_statement/1406.php.expect @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1407.php b/hphp/test/slow/static_statement/1407.php new file mode 100644 index 0000000000000..99831e7704f4e --- /dev/null +++ b/hphp/test/slow/static_statement/1407.php @@ -0,0 +1,121 @@ +> +function main_1407() { +static $a = 1, $b = 2; +static $c = 1; +static $d = 1; +static $e = 1; +static $f = 1; +static $g = 1; +static $h = 1; +static $i = 1; +static $i = 2; +if (false) { + static $a = 2; + static $b = 3; + static $c = 2; + static $g; + static $i; + $e = 2; +} + else { + static $d = 2; + static $h; + $f = 2; +} +echo $a; +echo $b; +echo $c; +echo $d; +echo $e; +echo $f; +echo $g; +echo $h; +echo $i; +f(); +echo foo::$a; +echo foo::$b; +$v = new foo; +$v->bar(); +} diff --git a/hphp/test/slow/static_statement/1407.php.expect b/hphp/test/slow/static_statement/1407.php.expect new file mode 100644 index 0000000000000..ed16d7af7f98e --- /dev/null +++ b/hphp/test/slow/static_statement/1407.php.expect @@ -0,0 +1 @@ +23221223221212121111232212 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1408.php b/hphp/test/slow/static_statement/1408.php new file mode 100644 index 0000000000000..ffdd2924903c0 --- /dev/null +++ b/hphp/test/slow/static_statement/1408.php @@ -0,0 +1,17 @@ +> +function main_1408() { +foo(); +echo a::$b; +} diff --git a/hphp/test/slow/static_statement/1408.php.expectf b/hphp/test/slow/static_statement/1408.php.expectf new file mode 100644 index 0000000000000..b4de394767536 --- /dev/null +++ b/hphp/test/slow/static_statement/1408.php.expectf @@ -0,0 +1 @@ +11 diff --git a/hphp/test/slow/static_statement/1409.php b/hphp/test/slow/static_statement/1409.php new file mode 100644 index 0000000000000..93a219c512104 --- /dev/null +++ b/hphp/test/slow/static_statement/1409.php @@ -0,0 +1,45 @@ +q; + echo $foo; + } + function y() { + static $foo = 20; + $foo++; + echo $foo; + } + static function sf() { + static $foo = 0; + $foo++; + echo $foo; + } +} +class d extends c { + public $q = 30; +} + +<<__EntryPoint>> +function main_1409() { +$x = new c(); +$x->x(); +$x->y(); +$x->y(); +$x->y(); +$x->y(); +$x = new d(); +$x->x(); +$x->y(); +$x->y(); +$x->y(); +c::sf(); +c::sf(); +c::sf(); +d::sf(); +d::sf(); +d::sf(); +} diff --git a/hphp/test/slow/static_statement/1409.php.expect b/hphp/test/slow/static_statement/1409.php.expect new file mode 100644 index 0000000000000..340c5fe916d95 --- /dev/null +++ b/hphp/test/slow/static_statement/1409.php.expect @@ -0,0 +1 @@ +202122232430212223123123 \ No newline at end of file diff --git a/hphp/test/slow/static_statement/1410.php b/hphp/test/slow/static_statement/1410.php new file mode 100644 index 0000000000000..6425118d52632 --- /dev/null +++ b/hphp/test/slow/static_statement/1410.php @@ -0,0 +1,26 @@ +foo(); + } +} +class B extends A { +} +class C extends A { +} + +<<__EntryPoint>> +function main_1410() { +$a = new A; +$b = new B; +$c = new C; +$a->run(); +$b->run(); +$c->run(); +} diff --git a/hphp/test/slow/static_statement/1410.php.expect b/hphp/test/slow/static_statement/1410.php.expect new file mode 100644 index 0000000000000..c31bf4b98663e --- /dev/null +++ b/hphp/test/slow/static_statement/1410.php.expect @@ -0,0 +1,6 @@ +string(1) "A" +NULL +string(1) "B" +int(1) +string(1) "C" +int(1) diff --git a/hphp/test/slow/static_prop_with_non_const_expr.php b/hphp/test/slow/static_statement/static_prop_with_non_const_expr.php similarity index 100% rename from hphp/test/slow/static_prop_with_non_const_expr.php rename to hphp/test/slow/static_statement/static_prop_with_non_const_expr.php diff --git a/hphp/test/slow/static_prop_with_non_const_expr.php.expectf b/hphp/test/slow/static_statement/static_prop_with_non_const_expr.php.expectf similarity index 100% rename from hphp/test/slow/static_prop_with_non_const_expr.php.expectf rename to hphp/test/slow/static_statement/static_prop_with_non_const_expr.php.expectf diff --git a/hphp/test/slow/static_statement/static_with_non_const_expr.php b/hphp/test/slow/static_statement/static_with_non_const_expr.php new file mode 100644 index 0000000000000..10e21d11adc36 --- /dev/null +++ b/hphp/test/slow/static_statement/static_with_non_const_expr.php @@ -0,0 +1,12 @@ +> +function main_static_with_non_const_expr() { +f(); +} diff --git a/hphp/test/slow/static_statement/static_with_non_const_expr.php.expectf b/hphp/test/slow/static_statement/static_with_non_const_expr.php.expectf new file mode 100644 index 0000000000000..e07d11cf9f6b1 --- /dev/null +++ b/hphp/test/slow/static_statement/static_with_non_const_expr.php.expectf @@ -0,0 +1,2 @@ +Fatal error: %s in %s on line 5 + diff --git a/hphp/test/slow/traits/2059.php b/hphp/test/slow/traits/2059.php new file mode 100644 index 0000000000000..e86158970c441 --- /dev/null +++ b/hphp/test/slow/traits/2059.php @@ -0,0 +1,58 @@ +> +function main_2059() { +var_dump(T::test()); +var_dump(T::test1()); +var_dump(T::test2()); +var_dump(call_user_func("T::test")); +var_dump(call_user_func("T::test1")); +var_dump(call_user_func("T::test2")); +$obj = new T; +var_dump($obj->test()); +var_dump($obj->test1()); +var_dump($obj->test2()); +var_dump(call_user_func(array($obj, "test"))); +var_dump(call_user_func(array($obj, "test1"))); +var_dump(call_user_func(array($obj, "test2"))); +var_dump(T::test8(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(T::test81(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(T::test82(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func("T::test8", 1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func("T::test81", 1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func("T::test82", 1, 2, 3, 4, 5, 6, 7, 8)); +$obj = new T; +var_dump($obj->test8(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump($obj->test81(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump($obj->test82(1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func(array($obj, "test8"), 1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func(array($obj, "test81"), 1, 2, 3, 4, 5, 6, 7, 8)); +var_dump(call_user_func(array($obj, "test82"), 1, 2, 3, 4, 5, 6, 7, 8)); +} diff --git a/hphp/test/slow/traits/2059.php.expect b/hphp/test/slow/traits/2059.php.expect new file mode 100644 index 0000000000000..12d984fd4a3b3 --- /dev/null +++ b/hphp/test/slow/traits/2059.php.expect @@ -0,0 +1,24 @@ +int(1) +int(1) +int(1) +int(2) +int(2) +int(2) +int(3) +int(3) +int(3) +int(4) +int(4) +int(4) +int(1) +int(1) +int(1) +int(2) +int(2) +int(2) +int(3) +int(3) +int(3) +int(4) +int(4) +int(4) diff --git a/hphp/test/slow/yield/2172.php b/hphp/test/slow/yield/2172.php new file mode 100644 index 0000000000000..7baaa5b0e6fce --- /dev/null +++ b/hphp/test/slow/yield/2172.php @@ -0,0 +1,24 @@ +> function main_g10() { -$it = new It; -$foo = function() use($it) { - $it->x += 10; - yield $it->x; - $it->x += 100; - yield $it->x; - $it->x += 1000; - yield $it->x; +$foo = function() { + static $x = 1; + $x += 10; + yield $x; + $x += 100; + yield $x; + $x += 1000; + yield $x; }; $x = $foo(); $x->rewind(); diff --git a/hphp/test/slow/yield/clone/g7.php b/hphp/test/slow/yield/clone/g7.php index d3d10750e8f4c..40ac586de6311 100644 --- a/hphp/test/slow/yield/clone/g7.php +++ b/hphp/test/slow/yield/clone/g7.php @@ -1,12 +1,12 @@ > diff --git a/hphp/test/slow/yield/clone/g8.php b/hphp/test/slow/yield/clone/g8.php index fd2dc4822a7d6..e88a6b215561b 100644 --- a/hphp/test/slow/yield/clone/g8.php +++ b/hphp/test/slow/yield/clone/g8.php @@ -1,12 +1,12 @@ > diff --git a/hphp/test/slow/yield/clone/g9.php b/hphp/test/slow/yield/clone/g9.php index 044bd9330876b..c07fc7a934b43 100644 --- a/hphp/test/slow/yield/clone/g9.php +++ b/hphp/test/slow/yield/clone/g9.php @@ -1,16 +1,15 @@ > function main_g9() { -$it = new It; -$foo = function() use($it) { - $it->x += 10; - yield $it->x; - $it->x += 100; - yield $it->x; - $it->x += 1000; - yield $it->x; +$foo = function() { + static $x = 1; + $x += 10; + yield $x; + $x += 100; + yield $x; + $x += 1000; + yield $x; }; $x = $foo(); $y1 = clone $x; diff --git a/hphp/test/zend/good/Zend/tests/035.php b/hphp/test/zend/good/Zend/tests/035.php new file mode 100644 index 0000000000000..b2832ed928bd9 --- /dev/null +++ b/hphp/test/zend/good/Zend/tests/035.php @@ -0,0 +1,11 @@ + diff --git a/hphp/test/zend/good/Zend/tests/035.php.expectf b/hphp/test/zend/good/Zend/tests/035.php.expectf new file mode 100644 index 0000000000000..62c5fad9a235c --- /dev/null +++ b/hphp/test/zend/good/Zend/tests/035.php.expectf @@ -0,0 +1,3 @@ +int(-1) +int(-1) +int(-1) \ No newline at end of file diff --git a/hphp/test/zend/good/Zend/tests/035.php.norepo b/hphp/test/zend/good/Zend/tests/035.php.norepo new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/hphp/test/zend/good/Zend/tests/bug54039.php b/hphp/test/zend/good/Zend/tests/bug54039.php index 130baf13f5e8a..4b8c37b82287d 100644 --- a/hphp/test/zend/good/Zend/tests/bug54039.php +++ b/hphp/test/zend/good/Zend/tests/bug54039.php @@ -1,7 +1,7 @@ n++; - $a = $it->n.':'.$a; +$x = function ($x) use ($a) { + static $n = 0; + $n++; + $a = $n.':'.$a; echo $x.':'.$a."\n"; }; -$it = new It; -$y = function ($x) use (&$a, $it) { - $it->n++; - $a = $it->n.':'.$a; +$y = function ($x) use (&$a) { + static $n = 0; + $n++; + $a = $n.':'.$a; echo $x.':'.$a."\n"; }; $x(1); diff --git a/hphp/test/zend/good/Zend/tests/method_static_var.php b/hphp/test/zend/good/Zend/tests/method_static_var.php index c4432fe3fc3b2..6b09b4d55bf54 100644 --- a/hphp/test/zend/good/Zend/tests/method_static_var.php +++ b/hphp/test/zend/good/Zend/tests/method_static_var.php @@ -3,9 +3,9 @@ class Foo { public function __construct() { eval("class Bar extends Foo {}"); } - <<__LSB>> static $i = 0; public static function test() { - var_dump(++static::$i); + static $i = 0; + var_dump(++$i); } } @@ -13,8 +13,8 @@ public static function test() { new Foo; foo::test(); -/** +/** * function_add_ref() makes a clone of static variables for inherited functions, so $i in Bar::test gets initial value 1 - */ + */ Bar::test(); Bar::test(); diff --git a/hphp/test/zend/good/Zend/tests/static_variable.php b/hphp/test/zend/good/Zend/tests/static_variable.php index 1a8d25f3ed519..a1ac9e1666b38 100644 --- a/hphp/test/zend/good/Zend/tests/static_variable.php +++ b/hphp/test/zend/good/Zend/tests/static_variable.php @@ -2,9 +2,9 @@ const bar = 2, baz = bar + 1; function foo() { - $a = 1 + 1; - $b = [bar => 1 + 1, baz * 2 => 1 << 2]; - $c = [1 => bar, 3 => baz]; + static $a = 1 + 1; + static $b = [bar => 1 + 1, baz * 2 => 1 << 2]; + static $c = [1 => bar, 3 => baz]; var_dump($a, $b, $c); } diff --git a/hphp/test/zend/good/ext/mysqli/tests/connect.inc b/hphp/test/zend/good/ext/mysqli/tests/connect.inc index 6257d56556e78..b761aebe75ba5 100644 --- a/hphp/test/zend/good/ext/mysqli/tests/connect.inc +++ b/hphp/test/zend/good/ext/mysqli/tests/connect.inc @@ -138,11 +138,11 @@ function handle_catchable_fatal($errno, $error, $file, $line) { $errcodes = array(); - $constants = get_defined_constants(); - foreach ($constants as $name => $value) { - if (substr($name, 0, 2) == "E_") - $errcodes[$value] = $name; - } + $constants = get_defined_constants(); + foreach ($constants as $name => $value) { + if (substr($name, 0, 2) == "E_") + $errcodes[$value] = $name; + } printf("[%s] %s in %s on line %s\n", (isset($errcodes[$errno])) ? $errcodes[$errno] : $errno, $error, $file, $line); diff --git a/hphp/test/zend/good/ext/pdo/tests/pdo_test.inc b/hphp/test/zend/good/ext/pdo/tests/pdo_test.inc index 2dbaa809715b0..443c8dd8222eb 100644 --- a/hphp/test/zend/good/ext/pdo/tests/pdo_test.inc +++ b/hphp/test/zend/good/ext/pdo/tests/pdo_test.inc @@ -40,7 +40,7 @@ class PDOTest { // clean up any crufty test tables we might have left behind // on a previous run - $test_tables = array( + static $test_tables = array( 'test', 'test2', 'classtypes' diff --git a/hphp/test/zend/good/tests/lang/bug26869.php b/hphp/test/zend/good/tests/lang/bug26869.php index 2b26b9e6fbed7..e71dc6a71d20e 100644 --- a/hphp/test/zend/good/tests/lang/bug26869.php +++ b/hphp/test/zend/good/tests/lang/bug26869.php @@ -1,6 +1,6 @@ 1); + static $a=array(A => 1); var_dump($a); var_dump(isset($a[A])); ?> diff --git a/hphp/test/zend/good/tests/lang/static_basic_001.php b/hphp/test/zend/good/tests/lang/static_basic_001.php index 27ec3b380355e..981c8eb9006f3 100644 --- a/hphp/test/zend/good/tests/lang/static_basic_001.php +++ b/hphp/test/zend/good/tests/lang/static_basic_001.php @@ -1,18 +1,13 @@ diff --git a/hphp/test/zend/good/tests/lang/static_basic_002.php b/hphp/test/zend/good/tests/lang/static_basic_002.php new file mode 100644 index 0000000000000..8831e9e25f47b --- /dev/null +++ b/hphp/test/zend/good/tests/lang/static_basic_002.php @@ -0,0 +1,21 @@ + diff --git a/hphp/test/zend/good/tests/lang/static_basic_002.php.expectf b/hphp/test/zend/good/tests/lang/static_basic_002.php.expectf new file mode 100644 index 0000000000000..46d8571217e84 --- /dev/null +++ b/hphp/test/zend/good/tests/lang/static_basic_002.php.expectf @@ -0,0 +1,3 @@ +int(5) +int(11) +int(14) \ No newline at end of file