PageRenderTime 74ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

/lang_php/analyze/checker/check_variables_php.ml

http://github.com/facebook/pfff
OCaml | 961 lines | 481 code | 98 blank | 382 comment | 11 complexity | 776f8c583cdcbec389011e68d3ce6d2f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, LGPL-2.0, Apache-2.0
  1. (* Yoann Padioleau
  2. *
  3. * Copyright (C) 2010, 2012 Facebook
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public License
  7. * version 2.1 as published by the Free Software Foundation, with the
  8. * special exception on linking described in file license.txt.
  9. *
  10. * This library is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
  13. * license.txt for more details.
  14. *)
  15. open Common
  16. open Ast_php_simple
  17. module A = Ast_php_simple
  18. module E = Error_php
  19. module S = Scope_code
  20. module Ent = Entity_code
  21. module PI = Parse_info
  22. (*****************************************************************************)
  23. (* Prelude *)
  24. (*****************************************************************************)
  25. (*
  26. * This module helps find stupid PHP mistakes related to variables. See
  27. * tests/php/scheck/variables.php for examples of bugs currently
  28. * detected by this checker. This module not only checks but also annotates
  29. * the AST with scoping information as a side effect. This is useful
  30. * in codemap to display differently references to parameters, local vars,
  31. * global vars, etc.
  32. *
  33. * This file mostly deals with scoping issues. Scoping is different
  34. * from typing! Those are two orthogonal programming language concepts.
  35. * old: This file is concerned with variables, that is Ast_php.dname
  36. * entities, so for completness C-s for dname in ast_php.ml and
  37. * see if all uses of it are covered. Other files are more concerned
  38. * about checks related to entities, that is Ast_php.name.
  39. *
  40. * The errors detected here are mostly:
  41. * - UseOfUndefinedVariable
  42. * - UnusedVariable
  43. * Some similar checks are done by JSlint.
  44. *
  45. * Some issues:
  46. * - detecting variable-related mistakes is made slightly more complicated
  47. * by PHP because of the lack of declaration in the language;
  48. * the first assignment "declares" the variable (on the other side
  49. * the PHP language forces people to explicitly declared
  50. * the use of global variables (via the 'global' statement) which
  51. * makes certain things easier).
  52. *
  53. * - variables passed by reference can look like UseOfUndefinedVariable
  54. * bugs but they are not. One way to fix it is to do a global analysis that
  55. * remembers what are all the functions taking arguments by reference
  56. * and whitelist them here. But it has a cost. One can optimize a little
  57. * this by using an entity_finder computed semi lazily a la cmf multi
  58. * level approach (recursive a la cpp, flib-map, git-grep, git head).
  59. * Another way is to force programmers to actually declare such variables
  60. * before those kinds of function calls (this is what Evan advocated).
  61. *
  62. * - people abuse assignements in function calls to emulate "keyword arguments"
  63. * as in 'foo($color = "red", $width = 10)'. Such assignments looks
  64. * like UnusedVariable but they are not. One can fix that by detecting
  65. * such code patterns.
  66. *
  67. * - functions like extract(), param_get(), param_post()
  68. * or variable variables like $$x introduce some false positives.
  69. * Regarding the param_get/param_post(), one way to fix it is to just
  70. * not analyse toplevel code. Another solution is to hardcode a few
  71. * analysis that recognizes the arguments of those functions. Finally
  72. * for the extract() and $$x one can just bailout of such code or
  73. * as Evan did remember the first line where such code happen and
  74. * don't do any analysis pass this point.
  75. *
  76. * - any analysis will probably flag lots of warnings on an existing PHP
  77. * codebase. Some programmers may find legitimate certain things,
  78. * for instance having variables declared in a foreach to escape its
  79. * foreach scope. This would then hinder the whole analysis because
  80. * people would just not run the analysis. You need the approval of
  81. * the PHP developers on such analysis first and get them ok to change
  82. * their coding styles rules. One way to fix this problem is to have
  83. * a strict mode where only certain checks are enabled. A good
  84. * alternative is also to rank errors. A final trick is to report
  85. * only new errors.
  86. *
  87. * Here are some extra notes by Evan in his own variable linter:
  88. *
  89. * "These things declare variables in a function":
  90. * - DONE Explicit parameters
  91. * - DONE Assignment (pad: variable mentionned for the first time)
  92. * - DONE Assignment via list()
  93. * - DONE Static, Global
  94. * - DONE foreach()
  95. * - DONE catch
  96. * - DONE Builtins ($this)
  97. * - DONE Lexical vars, in php 5.3 lambda expressions
  98. * pad: forgot pass by reference variables
  99. *
  100. * "These things make lexical scope unknowable":
  101. * - DONE Use of extract()
  102. * - DONE Assignment or Global with variable variables ($$x)
  103. * (pad: so just bailout on such code)
  104. * pad: forgot eval()? but eval can introduce new variables in scope?
  105. *
  106. * These things don't count as "using" a variable:
  107. * - DONE isset() (pad: this should be forbidden, it's a bad way to program)
  108. * - DONE empty()
  109. * - DONE Static class variables (pad: check done in check_classes instead)
  110. *
  111. * Here are a few additional checks and features of this checker:
  112. * - when the strict_scope flag is set, check_variables will
  113. * emulate a block-scoped language as in JSLint and flags
  114. * variables used outside their "block".
  115. * - when passed the find_entity hook, check_variables will
  116. * know about functions taking parameters by refs, which removes
  117. * some false positives
  118. *
  119. * history:
  120. * - sgrimm had the simple idea of just counting the number of occurences
  121. * of a variable in a program, at the token level. If only 1, then
  122. * probably a typo. But sometimes variable names are mentionned in
  123. * interface signatures in which case they occur only once. So you need
  124. * some basic analysis; the token level is not enough. You may not
  125. * need the CFG but at least you need the AST to differentiate the
  126. * different kinds of unused variables.
  127. * - Some of the scoping logic was previously in another file, scoping_php.ml
  128. * but we were kind of duplicating the logic that is in scoping_php.ml.
  129. * PHP has bad scoping rule allowing variables declared through a foreach
  130. * to be used outside the foreach, which is probably wrong.
  131. * Unfortunately, knowing from scoping_php.ml just the status of a
  132. * variable (local, global, or param) is not good enough to find bugs
  133. * related to those weird scoping rules. So I've put all variable scope
  134. * related stuff in this file and removed the duplication in scoping_php.ml.
  135. * - I was using ast_php.ml and a visitor approach but then I rewrote it
  136. * to use ast_php_simple and an "env" approach because the code was
  137. * getting ugly and was containing false positives that were hard to fix.
  138. * As a side effect of the refactoring, some bugs disappeared (nested
  139. * assigns in if, list() not at the toplevel of an expression, undefined
  140. * access to an array), and code regarding lexical variables became
  141. * more clear because localized in one place.
  142. *
  143. * TODO OTHER:
  144. * - factorize code for the shared_ref thing and create_new_local_if_necessary
  145. * - the old checker was handling correctly globals? was it looking up
  146. * in the top scope? add some unit tests.
  147. * - put back strict block scope
  148. * - factorize in add_var() that adds in env.vars and
  149. * update env.scoped_vars_used too. Removed refs to scope_ref in this file
  150. *)
  151. (*****************************************************************************)
  152. (* Types, constants *)
  153. (*****************************************************************************)
  154. type env = {
  155. (* The ref in the tuple is to remember the number of uses of the variable,
  156. * for the UnusedVariable check.
  157. * The ref for the Map.t is to avoid threading the env, because
  158. * any stmt/expression can introduce new variables.
  159. *
  160. * todo? use a list of Map.t to represent nested scopes?
  161. * (globals, methods/functions, nested blocks)? when in strict/block mode?
  162. *)
  163. vars: (string, (Ast_php.tok * Scope_code.scope * int ref)) Map_poly.t ref;
  164. (* to remember how to annotate Var in ast_php.ml *)
  165. scope_vars_used: (Ast_php.tok, Scope_code.scope) Hashtbl.t;
  166. (* todo: have a globals:? *)
  167. (* we need to access the definitions of functions/methods to know
  168. * if an argument was passed by reference, in which case what looks
  169. * like a UseOfUndefinedVariable is actually not (but it would be
  170. * better for them to fix the code to introduce/declare this variable
  171. * before ...).
  172. *)
  173. db: Entity_php.entity_finder option;
  174. (* when analyze $this->method_call(), we need to know the enclosing class *)
  175. in_class: ident option;
  176. (* for better error message when the variable was inside a long lambda *)
  177. in_long_lambda: bool;
  178. (* when the body of a function contains eval/extract/... we bailout
  179. * because we don't want to report false positives
  180. *)
  181. bailout: bool ref;
  182. }
  183. (*****************************************************************************)
  184. (* Helpers *)
  185. (*****************************************************************************)
  186. let unused_ok s =
  187. s =~ "\\$_.*" ||
  188. s =~ "\\$ignore.*" ||
  189. List.mem s ["$unused";"$dummy";"$guard"] ||
  190. (if !E.strict
  191. then false
  192. else
  193. List.mem s [
  194. "$res"; "$retval"; "$success"; "$is_error"; "$rs"; "$ret";
  195. "$e"; "$ex"; "$exn"; (* exception *)
  196. ]
  197. )
  198. let lookup_opt s vars =
  199. Common2.optionise (fun () -> Map_poly.find s vars)
  200. let s_tok_of_ident name =
  201. A.str_of_ident name, A.tok_of_ident name
  202. (* to help debug *)
  203. let str_of_any any =
  204. let v = Meta_ast_php_simple.vof_any any in
  205. Ocaml.string_of_v v
  206. (*****************************************************************************)
  207. (* Vars passed by ref *)
  208. (*****************************************************************************)
  209. (*
  210. * Detecting variables passed by reference is complicated in PHP because
  211. * one does not have to use &$var at the call site (one can though). This is
  212. * ugly. So to detect variables passed by reference, we need to look at
  213. * the definition of the function/method called, hence the need for a
  214. * entity_finder in env.db.
  215. *
  216. * note that it currently returns an Ast_php.func_def, not
  217. * an Ast_php_simple.func_def because the database currently
  218. * stores concrete ASTs, not simple ASTs.
  219. *)
  220. let funcdef_of_call_or_new_opt env e =
  221. match env.db with
  222. | None -> None
  223. | Some find_entity ->
  224. (match e with
  225. | Id [name] ->
  226. (* simple function call *)
  227. let (s, tok) = s_tok_of_ident name in
  228. (match find_entity (Ent.Function, s) with
  229. | [Ast_php.FunctionE def] -> Some def
  230. (* normally those errors should be triggered in
  231. * check_functions_php.ml, but right now this file uses
  232. * ast_php.ml and not ast_php_simple.ml, so there are some
  233. * differences in the logic so we double check things here.
  234. *)
  235. | [] ->
  236. E.fatal tok (E.UndefinedEntity (Ent.Function, s));
  237. None
  238. | _x::_y::_xs ->
  239. E.fatal tok (E.MultiDefinedEntity (Ent.Function, s, ("","")));
  240. None
  241. | _ -> raise Impossible
  242. )
  243. (* dynamic function call *)
  244. | Var _ -> None
  245. (* static method call *)
  246. | Class_get (Id name1, Id name2) ->
  247. (* todo: name1 can be self/parent in traits, or static: *)
  248. let aclass = A.str_of_name name1 in
  249. let amethod = A.str_of_name name2 in
  250. (try
  251. Some (Class_php.lookup_method ~case_insensitive:true
  252. (aclass, amethod) find_entity)
  253. (* could not find the method, this is bad, but
  254. * it's not our business here; this error will
  255. * be reported anyway in check_classes_php.ml anyway
  256. *)
  257. with
  258. | Not_found | Multi_found
  259. | Class_php.Use__Call|Class_php.UndefinedClassWhileLookup _ ->
  260. None
  261. )
  262. (* simple object call. If 'this->...()' then we can use lookup_method.
  263. * Being complete and handling any method calls like $o->...()
  264. * requires to know what is the type of $o which is quite
  265. * complicated ... so let's skip that for now.
  266. *
  267. * todo: special case also id(new ...)-> ?
  268. *)
  269. | Obj_get (This _, Id [name2]) ->
  270. (match env.in_class with
  271. | Some name1 ->
  272. let aclass = A.str_of_ident name1 in
  273. let amethod = A.str_of_ident name2 in
  274. (try
  275. Some (Class_php.lookup_method ~case_insensitive:true
  276. (aclass, amethod) find_entity)
  277. with
  278. | Not_found | Multi_found
  279. | Class_php.Use__Call|Class_php.UndefinedClassWhileLookup _ ->
  280. None
  281. )
  282. (* wtf? use of $this outside a class? *)
  283. | None -> None
  284. )
  285. (* dynamic call, not much we can do ... *)
  286. | _ -> None
  287. )
  288. (*****************************************************************************)
  289. (* Checks *)
  290. (*****************************************************************************)
  291. let check_defined env name ~incr_count =
  292. let (s, tok) = s_tok_of_ident name in
  293. match lookup_opt s !(env.vars) with
  294. | None ->
  295. (* todo? could still issue an error but with the information that
  296. * there was an extract/eval/... around?
  297. *)
  298. if !(env.bailout)
  299. then ()
  300. else
  301. let err =
  302. if env.in_long_lambda
  303. then E.UseOfUndefinedVariableInLambda s
  304. else
  305. let allvars =
  306. !(env.vars) +> Map_poly.to_list +> List.map fst in
  307. let suggest = Suggest_fix_php.suggest s allvars in
  308. E.UseOfUndefinedVariable (s, suggest)
  309. in
  310. E.fatal (A.tok_of_ident name) err
  311. | Some (_tok, scope, access_count) ->
  312. Hashtbl.add env.scope_vars_used tok scope;
  313. if incr_count then incr access_count
  314. let check_used env vars =
  315. vars +> Map_poly.iter (fun s (tok, scope, aref) ->
  316. if !aref = 0
  317. then
  318. if unused_ok s
  319. then ()
  320. else
  321. (* if you use compact(), some variables may look unused but
  322. * they can actually be used. See variables_fp.php.
  323. *)
  324. if !(env.bailout)
  325. then ()
  326. else E.fatal tok (E.UnusedVariable (s, scope))
  327. )
  328. let create_new_local_if_necessary ~incr_count env name =
  329. let (s, tok) = s_tok_of_ident name in
  330. match lookup_opt s !(env.vars) with
  331. (* new local variable implicit declaration.
  332. * todo: add in which nested scope? I would argue to add it
  333. * only in the current nested scope. If someone wants to use a
  334. * var outside the block, he should have initialized the var
  335. * in the outer context. Jslint does the same.
  336. *)
  337. | None ->
  338. env.vars := Map_poly.add s (tok, S.Local, ref 0) !(env.vars);
  339. Hashtbl.add env.scope_vars_used tok S.Local;
  340. | Some (_tok, scope, access_count) ->
  341. Hashtbl.add env.scope_vars_used tok scope;
  342. if incr_count then incr access_count
  343. (*****************************************************************************)
  344. (* Main entry point *)
  345. (*****************************************************************************)
  346. (* For each introduced variable (parameter, foreach variable, exception, etc),
  347. * we add the binding in the environment with a counter, a la checkModule.
  348. * We then check at use-time if something was declared before. We then
  349. * finally check when we exit a scope that all variables were actually used.
  350. *)
  351. let rec program env prog =
  352. List.iter (stmt env) prog;
  353. (* we must check if people used the variables declared at the toplevel
  354. * context or via the param_post/param_get calls.
  355. * todo: check env.globals instead?
  356. *)
  357. check_used env !(env.vars)
  358. (* ---------------------------------------------------------------------- *)
  359. (* Functions/Methods *)
  360. (* ---------------------------------------------------------------------- *)
  361. and func_def env def =
  362. (* should not contain variables anyway, but does not hurt to check *)
  363. def.f_params +> List.iter (fun p -> Common.opt (expr env) p.p_default);
  364. let access_cnt =
  365. match def.f_kind with
  366. | Function | AnonLambda | ShortLambda -> 0
  367. (* Don't report UnusedParameter for parameters of methods;
  368. * people sometimes override a method and don't use all
  369. * the parameters, hence the 1 value below.
  370. *
  371. * less: we we don't want parameters just in method interface
  372. * to not be counted as unused Parameter
  373. * less: one day we will have an @override annotation in which
  374. * case we can reconsider the above design decision.
  375. *)
  376. | Method -> 1
  377. in
  378. let oldvars = !(env.vars) in
  379. let enclosing_vars =
  380. match def.f_kind with
  381. (* for ShortLambda enclosing variables can be accessed *)
  382. | ShortLambda -> Map_poly.to_list oldvars
  383. (* fresh new scope *)
  384. | _ ->
  385. (Env_php.globals_builtins +> List.map (fun s ->
  386. "$" ^ s, (Ast_php.fakeInfo s, S.Global, ref 1)
  387. )) @
  388. (* $this is now implicitly passed in use() for closures *)
  389. (try ["$this", Map_poly.find "$this" oldvars]
  390. with Not_found -> []
  391. )
  392. in
  393. let env = { env with
  394. vars = ref ((
  395. (def.f_params +> List.map (fun p ->
  396. let (s, tok) = s_tok_of_ident p.p_name in
  397. s, (tok, S.Param, ref access_cnt)
  398. )) @
  399. enclosing_vars
  400. ) +> Map_poly.of_list);
  401. (* reinitialize bailout for each function/method *)
  402. bailout = ref false;
  403. }
  404. in
  405. def.l_uses +> List.iter (fun (_is_ref, name) ->
  406. let (s, tok) = s_tok_of_ident name in
  407. check_defined ~incr_count:true { env with vars = ref oldvars} name;
  408. (* don't reuse same access count reference; the variable has to be used
  409. * again in this new scope.
  410. *)
  411. env.vars := Map_poly.add s (tok, S.Closed, ref 0) !(env.vars);
  412. );
  413. (* We put 1 as 'use_count' below because we are not interested
  414. * in error message related to $this. It's ok to not use $this
  415. * in a method.
  416. *)
  417. if def.f_kind = Method && not (A.is_static def.m_modifiers)
  418. then begin
  419. let tok = (Ast_php.fakeInfo "$this") in
  420. env.vars := Map_poly.add "$this" (tok, S.Class, ref 1) !(env.vars);
  421. end;
  422. List.iter (stmt env) def.f_body;
  423. let newvars =
  424. enclosing_vars +> List.fold_left (fun acc (x, _) -> Map_poly.remove x acc)
  425. !(env.vars)
  426. in
  427. check_used env newvars
  428. (* ---------------------------------------------------------------------- *)
  429. (* Stmt *)
  430. (* ---------------------------------------------------------------------- *)
  431. and stmt env = function
  432. | FuncDef def -> func_def env def
  433. | ClassDef def -> class_def env def
  434. | ConstantDef def -> constant_def env def
  435. | TypeDef def -> typedef_def env def
  436. | NamespaceDef (qu, _) | NamespaceUse (qu, _) ->
  437. raise (Ast_php.TodoNamespace (A.tok_of_name qu))
  438. | Expr e -> expr env e
  439. (* todo: block scope checking when in strict mode? *)
  440. | Block xs -> stmtl env xs
  441. | If (e, st1, st2) ->
  442. expr env e;
  443. stmtl env [st1;st2]
  444. | Switch (e, xs) ->
  445. expr env e;
  446. casel env xs
  447. | While (e, xs) ->
  448. expr env e;
  449. stmtl env xs
  450. | Do (xs, e) ->
  451. stmtl env xs;
  452. expr env e
  453. | For (es1, es2, es3, xs) ->
  454. exprl env (es1 @ es2 @ es3);
  455. stmtl env xs
  456. | Foreach (e1, pattern, xs) ->
  457. expr env e1;
  458. foreach_pattern env pattern;
  459. stmtl env xs
  460. | Return eopt
  461. | Break eopt | Continue eopt ->
  462. Common.opt (expr env) eopt
  463. | Throw e -> expr env e
  464. | Try (xs, cs, fs) ->
  465. stmtl env xs;
  466. catches env (cs);
  467. finallys env (fs)
  468. | StaticVars xs ->
  469. xs +> List.iter (fun (name, eopt) ->
  470. Common.opt (expr env) eopt;
  471. let (s, tok) = s_tok_of_ident name in
  472. (* less: check if shadows something? *)
  473. env.vars := Map_poly.add s (tok, S.Static, ref 0) !(env.vars);
  474. )
  475. | Global xs ->
  476. xs +> List.iter (fun e ->
  477. (* should be an Var most of the time.
  478. * todo: should check in .globals that this variable actually exists
  479. *)
  480. match e with
  481. | Var name ->
  482. let (s, tok) = s_tok_of_ident name in
  483. env.vars := Map_poly.add s (tok, S.Global, ref 0) !(env.vars);
  484. (* todo: E.warning tok E.UglyGlobalDynamic *)
  485. | _ ->
  486. pr2 (str_of_any (Expr2 e));
  487. raise Todo
  488. )
  489. (* The scope of catch is actually also at the function level in PHP ...
  490. *
  491. * todo: but for this one it is so ugly that I introduce a new scope
  492. * even outside strict mode. It's just too ugly.
  493. * todo: check unused
  494. * todo? could use local ? could have a UnusedExceptionParameter ?
  495. * less: could use ref 1, the exception is often not used
  496. *)
  497. and catch env (_hint_type, name, xs) =
  498. let (s, tok) = s_tok_of_ident name in
  499. env.vars := Map_poly.add s (tok, S.LocalExn, ref 0) !(env.vars);
  500. stmtl env xs
  501. and finally env (xs) =
  502. stmtl env xs
  503. and case env = function
  504. | Case (e, xs) ->
  505. expr env e;
  506. stmtl env xs
  507. | Default xs ->
  508. stmtl env xs
  509. and stmtl env xs = List.iter (stmt env) xs
  510. and casel env xs = List.iter (case env) xs
  511. and catches env xs = List.iter (catch env) xs
  512. and finallys env xs = List.iter (finally env) xs
  513. and foreach_pattern env pattern =
  514. (* People often use only one of the iterator when
  515. * they do foreach like foreach(... as $k => $v).
  516. * We want to make sure that at least one of
  517. * the iterator variables is used, hence this trick to
  518. * make them share the same access count reference.
  519. *)
  520. let shared_ref = ref 0 in
  521. let rec aux e =
  522. (* look Ast_php.foreach_pattern to see the kinds of constructs allowed *)
  523. match e with
  524. | Var name ->
  525. let (s, tok) = s_tok_of_ident name in
  526. (* todo: if already in scope? shadowing? *)
  527. (* todo: if strict then introduce new scope here *)
  528. (* todo: scope_ref := S.LocalIterator; *)
  529. env.vars := Map_poly.add s (tok, S.LocalIterator, shared_ref) !(env.vars)
  530. | Ref x -> aux x
  531. | Arrow (e1, e2) -> aux e1; aux e2
  532. | List xs -> List.iter aux xs
  533. (* other kinds of lvalue are permitted too, but it's a little bit wierd
  534. * and very rarely used in www
  535. *)
  536. | Array_get (e, eopt) ->
  537. aux e;
  538. eopt +> Common.do_option (expr env)
  539. (* todo: E.warning tok E.WeirdForeachNoIteratorVar *)
  540. | _ ->
  541. failwith ("Warning: unexpected `foreach` value " ^
  542. (str_of_any (Expr2 pattern)))
  543. in
  544. aux pattern
  545. (* ---------------------------------------------------------------------- *)
  546. (* Expr *)
  547. (* ---------------------------------------------------------------------- *)
  548. and expr env e =
  549. match e with
  550. | Int _ | Double _ | String _ -> ()
  551. | Var name ->
  552. check_defined ~incr_count:true env name
  553. | Id _name -> ()
  554. | Assign (None, e1, e2) ->
  555. (* e1 should be an lvalue *)
  556. (match e1 with
  557. | Var name ->
  558. (* Does an assignation counts as a use? If you only
  559. * assign and never use a variable what is the point?
  560. * This should be legal only for parameters passed by reference.
  561. * (note that here I talk about parameters, not arguments)
  562. *
  563. * TODO: hmm if you take a reference to something, then
  564. * assigning something to it should be considered as a use too.
  565. *)
  566. create_new_local_if_necessary ~incr_count:false env name;
  567. (* extract all vars, and share the same reference *)
  568. | List xs ->
  569. (* Use the same trick than for LocalIterator *)
  570. let shared_ref = ref 0 in
  571. let rec aux = function
  572. (* should be an lvalue again *)
  573. | Var name ->
  574. let (s, tok) = s_tok_of_ident name in
  575. (match lookup_opt s !(env.vars) with
  576. | None ->
  577. env.vars := Map_poly.add s (tok, S.ListBinded, shared_ref)
  578. !(env.vars);
  579. | Some (_tok, _scope, _access_cnt) ->
  580. ()
  581. )
  582. | ((Array_get _ | Obj_get _ | Class_get _) as e) ->
  583. expr env e
  584. | List xs -> List.iter aux xs
  585. | _ ->
  586. pr2 (str_of_any (Expr2 (List xs)));
  587. raise Todo
  588. in
  589. List.iter aux xs
  590. | Array_get (e_arr, e_opt) ->
  591. (* make sure the array is declared *)
  592. expr env e_arr;
  593. Common.opt (expr env) e_opt
  594. (* checks for use of undefined member should be in check_classes *)
  595. | Obj_get (_, _) | Class_get (_, _) ->
  596. (* just recurse on the whole thing so the code for Obj_get/Class_get
  597. * below will be triggered
  598. *)
  599. expr env e1
  600. | Call (Id[(("__builtin__eval_var", _) as name)], _args) ->
  601. env.bailout := true;
  602. E.warning (tok_of_ident name) E.DynamicCode
  603. (* can we have another kind of lvalue? *)
  604. | e ->
  605. pr2 (str_of_any (Expr2 e));
  606. failwith "WrongLvalue"
  607. );
  608. expr env e2
  609. | Assign (Some _, e1, e2) ->
  610. exprl env [e1;e2]
  611. | List _xs ->
  612. let tok = Meta_ast_php_simple.toks_of_any (Expr2 e) +> List.hd in
  613. failwith (spf "list(...) should be used only in an Assign context at %s"
  614. (PI.string_of_info tok))
  615. (* Arrow used to be allowed only in Array and Foreach context, but now
  616. * can we also have code like yield $k => $v, so this is really a pair.
  617. *)
  618. | Arrow (e1, e2) ->
  619. exprl env [e1; e2]
  620. (* A mention of a variable in a unset() should not be really
  621. * considered as a use of variable. There should be another
  622. * statement in the function that actually uses the variable.
  623. *)
  624. | Call (Id[ ("__builtin__unset", _tok)], args) ->
  625. args +> List.iter (function
  626. (* should be an lvalue again *)
  627. (* less: The use of 'unset' on a variable is still not clear to me. *)
  628. | Var name ->
  629. check_defined ~incr_count:false env name
  630. (* Unsetting a field, seems like a valid use.
  631. * Unsetting a prop, not clear why you want that.
  632. * Unsetting a class var, not clear why you want that either
  633. *)
  634. | (Array_get (_, _) | Obj_get _ | Class_get _) as e ->
  635. (* make sure that the array used is actually defined *)
  636. expr env e
  637. | e ->
  638. pr2 (str_of_any (Expr2 e));
  639. raise Todo
  640. )
  641. (* special case, could factorize maybe with pass_var_by_ref *)
  642. | Call (Id[ ("sscanf", _tok)], x::y::vars) ->
  643. (* what if no x and y? wrong number of arguments, not our business here*)
  644. expr env x;
  645. expr env y;
  646. vars +> List.iter (function
  647. | Var name ->
  648. create_new_local_if_necessary ~incr_count:false env name
  649. (* less: wrong, it should be a variable? *)
  650. | e -> expr env e
  651. )
  652. (* The right fix is to forbid people to use isset/empty or unset on var.
  653. * todo: could have if(isset($x)) { ... code using $x}.
  654. * maybe we should have a bailout_vars and skip further errors on $x.
  655. * todo: could have isset(Array_get(...) there too no?
  656. *)
  657. | Call (Id[ ("__builtin__isset", _tok)], [Var _name]) ->
  658. ()
  659. (* http://php.net/manual/en/function.empty.php
  660. * "empty() does not generate a warning if the variable does not exist."
  661. *)
  662. | Call (Id[ ("__builtin__empty", _tok)], [Var _name]) ->
  663. ()
  664. | Call (Id[ ((("__builtin__eval" | "__builtin__eval_var" |
  665. "extract" | "compact"
  666. ), _) as name)], _args) ->
  667. env.bailout := true;
  668. E.warning (tok_of_ident name) E.DynamicCode
  669. (* facebook specific? should be a hook instead to visit_prog? *)
  670. | Call(Id[("param_post"|"param_get"|"param_request"|"param_cookie"as kind,_)],
  671. (ConsArray (array_args))::rest_param_xxx_args) ->
  672. (* have passed a 'prefix' arg, or nothing *)
  673. if List.length rest_param_xxx_args <= 1
  674. then begin
  675. let prefix_opt =
  676. match rest_param_xxx_args with
  677. | [String(str_prefix, _tok_prefix)] ->
  678. Some str_prefix
  679. | [] ->
  680. (match kind with
  681. | "param_post" -> Some "post_"
  682. | "param_get" -> Some "get_"
  683. | "param_request" -> Some "req_"
  684. | "param_cookie" -> Some "cookie_"
  685. | _ -> raise Impossible
  686. )
  687. | _ ->
  688. (* less: display an error? weird argument to param_xxx func?*)
  689. None
  690. in
  691. prefix_opt +> Common.do_option (fun prefix ->
  692. array_args +> List.iter (function
  693. | Arrow(String(param_string, tok_param), _typ_param) ->
  694. let s = "$" ^ prefix ^ param_string in
  695. let tok = A.tok_of_ident (param_string, tok_param) in
  696. env.vars := Map_poly.add s (tok, S.Local, ref 0) !(env.vars);
  697. (* less: display an error? weird argument to param_xxx func? *)
  698. | _ -> ()
  699. )
  700. )
  701. end
  702. | Call (e, es) ->
  703. expr env e;
  704. (* getting the def for args passed by ref false positives fix *)
  705. let def_opt = funcdef_of_call_or_new_opt env e in
  706. let es_with_parameters =
  707. match def_opt with
  708. | None ->
  709. es +> List.map (fun e -> e, None)
  710. | Some def ->
  711. let params =
  712. def.Ast_php.f_params +> Ast_php.unparen +> Ast_php.uncomma_dots
  713. in
  714. let rec zip args params =
  715. match args, params with
  716. | [], [] -> []
  717. (* more params than arguments, maybe because default parameters *)
  718. | [], _y::_ys -> []
  719. (* more arguments than params, maybe because func_get_args() *)
  720. | x::xs, [] -> (x, None)::zip xs []
  721. | x::xs, y::ys -> (x, Some y)::zip xs ys
  722. in
  723. zip es params
  724. in
  725. es_with_parameters +> List.iter (fun (arg, param_opt) ->
  726. match arg, param_opt with
  727. (* keyword argument; do not consider this variable as unused.
  728. * We consider this variable as a pure comment here and just pass over.
  729. * todo: could make sure they are not defined in the current
  730. * environment in strict mode? and if they are, shout because of
  731. * bad practice?
  732. *)
  733. | Assign (None, Var _name, e2), _ ->
  734. expr env e2
  735. (* a variable passed by reference, this can considered a new decl *)
  736. | Var name, Some {Ast_php.p_ref = Some _;_} ->
  737. (* if was already assigned and passed by refs,
  738. * increment its use counter then.
  739. * less: or should we increase only if inout param?
  740. *)
  741. create_new_local_if_necessary ~incr_count:true env name
  742. | _ -> expr env arg
  743. )
  744. | This name ->
  745. (* when we do use($this) in closures, we create a fresh $this variable
  746. * with a refcount of 0, so we need to increment it here.
  747. *)
  748. check_defined ~incr_count:true env name
  749. (* array used as an rvalue; the lvalue case should be handled in Assign. *)
  750. | Array_get (e, eopt) ->
  751. expr env e;
  752. Common.opt (expr env) eopt
  753. | Obj_get (e1, e2) ->
  754. expr env e1;
  755. (match e2 with
  756. (* with 'echo $o->$v' we have a dynamic field, we need to visit
  757. * e2 to mark $v as used at least.
  758. *)
  759. | Id _name -> ()
  760. | _ -> expr env e2
  761. )
  762. | Class_get (e1, e2) ->
  763. expr env e1;
  764. (match e2 with
  765. (* with 'echo A::$v' we should not issue a UseOfUndefinedVariable,
  766. * check_classes_php.ml will handle this case.
  767. *)
  768. | Var _ -> ()
  769. | _ -> expr env e2
  770. )
  771. | New (e, es) ->
  772. expr env (Call (Class_get(e, Id[ ("__construct", None)]), es))
  773. | InstanceOf (e1, e2) -> exprl env [e1;e2]
  774. | Infix (_, e) | Postfix (_, e) | Unop (_, e) -> expr env e
  775. | Binop (_, e1, e2) -> exprl env [e1; e2]
  776. | Guil xs -> exprl env xs
  777. | Ref e | Unpack e -> expr env e
  778. | ConsArray (xs) -> array_valuel env xs
  779. | Collection (_n, xs) ->
  780. array_valuel env xs
  781. | Xhp x -> xml env x
  782. | CondExpr (e1, e2, e3) -> exprl env [e1; e2; e3]
  783. | Cast (_, e) -> expr env e
  784. | Lambda def ->
  785. let in_long_lambda =
  786. match def.f_kind with ShortLambda -> false | _ -> true
  787. in
  788. func_def { env with in_long_lambda } def
  789. and array_value env x =
  790. match x with
  791. | Arrow (e1, e2) -> exprl env [e1; e2]
  792. | e -> expr env e
  793. and xml env x =
  794. x.xml_attrs +> List.iter (fun (_name, xhp_attr) -> expr env xhp_attr);
  795. x.xml_body +> List.iter (xhp env)
  796. and xhp env = function
  797. | XhpText _s -> ()
  798. | XhpExpr e -> expr env e
  799. | XhpXml x -> xml env x
  800. and exprl env xs = List.iter (expr env) xs
  801. and array_valuel env xs = List.iter (array_value env) xs
  802. (* ---------------------------------------------------------------------- *)
  803. (* Misc *)
  804. (* ---------------------------------------------------------------------- *)
  805. (* checks for use of undefined members should be in check_classes, but
  806. * it's hard to do locally.
  807. *
  808. * todo: inline the code of traits?
  809. *)
  810. and class_def env def =
  811. let env = { env with in_class = Some def.c_name } in
  812. List.iter (constant_def env) def.c_constants;
  813. List.iter (class_var env) def.c_variables;
  814. List.iter (method_def env) def.c_methods
  815. (* cst_body should be a static scalar so there should not be any
  816. * variable in it so in theory we don't need to check it ... doesn't
  817. * hurt though, one day maybe this will change.
  818. *)
  819. and constant_def env def =
  820. Common.opt (expr env) def.cst_body
  821. (* type definitions do not contain any variables *)
  822. and typedef_def _env _def =
  823. ()
  824. and class_var env v =
  825. Common.opt (expr env) v.cv_value
  826. and method_def env x = func_def env x
  827. (*****************************************************************************)
  828. (* Main entry point *)
  829. (*****************************************************************************)
  830. let check_and_annotate_program2 find_entity prog =
  831. let env = {
  832. (* less: should be a in globals field instead? *)
  833. vars = ref (Env_php.globals_builtins +> List.map (fun s ->
  834. "$" ^ s, (Ast_php.fakeInfo s, S.Global, ref 1)
  835. ) +> Map_poly.of_list);
  836. db = find_entity;
  837. in_class = None;
  838. in_long_lambda = false;
  839. bailout = ref false;
  840. scope_vars_used = Hashtbl.create 101;
  841. }
  842. in
  843. let ast = Ast_php_simple_build.program_with_position_information prog in
  844. program env ast;
  845. (* annotating the scope of Var *)
  846. (Ast_php.Program prog) +>
  847. Visitor_php.mk_visitor { Visitor_php.default_visitor with
  848. Visitor_php.kexpr = (fun (k, _) x ->
  849. match x with
  850. | Ast_php.IdVar (dname, aref) ->
  851. let tok = Ast_php.info_of_dname dname in
  852. (try
  853. aref := Hashtbl.find env.scope_vars_used tok
  854. (* keep NoScope *)
  855. with Not_found -> ()
  856. )
  857. | _ -> k x
  858. );
  859. };
  860. ()
  861. let check_and_annotate_program a b =
  862. Common.profile_code "Checker.variables" (fun () ->
  863. check_and_annotate_program2 a b)