PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lang_php/analyze/checker/check_variables_php.ml

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