PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/phplinter/Lint/BaseLint.php

http://github.com/robotis/PHPLinter
PHP | 538 lines | 362 code | 5 blank | 171 comment | 77 complexity | 09244e054d4f0b3050cc4b972e4932f0 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. ----------------------------------------------------------------------+
  4. * @desc Base linter.
  5. ----------------------------------------------------------------------+
  6. * @file BaseLint.php
  7. * @author Jóhann T. Maríusson <jtm@robot.is>
  8. * @since Oct 29, 2011
  9. * @package PHPLinter
  10. * @copyright
  11. * phplinter is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License as published by
  13. * the Free Software Foundation, either version 3 of the License, or
  14. * (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. ----------------------------------------------------------------------+
  24. */
  25. namespace phplinter\Lint;
  26. use PHPLinter\Tokenizer;
  27. class BaseLint {
  28. /* @var Array */
  29. protected $reports;
  30. /* @var Array */
  31. protected $locals;
  32. /* @var Object */
  33. protected $node;
  34. /* @var Float */
  35. protected $penalty;
  36. /**
  37. ----------------------------------------------------------------------+
  38. * __construct
  39. * @param Object Element Object
  40. * @param Array Rule set
  41. * @param Int Option flags
  42. ----------------------------------------------------------------------+
  43. */
  44. public function __construct($node, $config) {
  45. $this->reports = array();
  46. $this->locals = array();
  47. $this->node = $node;
  48. $this->config = $config;
  49. $this->scope = -1;
  50. $this->branches = -1;
  51. $this->switch = false;
  52. $this->final_return = false;
  53. $dir = dirname(__FILE__);
  54. $this->globals = require $dir . '/../globals.php';
  55. $this->uvars = require $dir . '/../uservars.php';
  56. if($this->config->report_on('S')) {
  57. $this->sec_1 = require($dir . '/../Security/command_exection.php');
  58. $this->sec_2 = require($dir . '/../Security/filesystem.php');
  59. $this->sec_3 = require($dir . '/../Security/low_risk.php');
  60. $this->sec_4 = require($dir . '/../Security/information_disclosure.php');
  61. $this->sec_5 = require($dir . '/../Security/accept_callbacks.php');
  62. }
  63. }
  64. /**
  65. ----------------------------------------------------------------------+
  66. * FIXME
  67. * @param FIXME
  68. * @return FIXME
  69. ----------------------------------------------------------------------+
  70. */
  71. protected function report($flag, $extra=null, $line=null) {
  72. $report = $this->config->getRule($flag);
  73. if(isset($report['used']) && $report['used'] === false)
  74. return;
  75. if(!empty($report) && $this->config->report_on($report['flag'])) {
  76. $where = isset($this->node->parent)
  77. ? $this->node->parent : 'COMMENT';
  78. if(isset($this->node->name)) $where = $this->node->name;
  79. if(isset($report['message_extras'])) {
  80. $cmp = $report['compare'];
  81. if(isset($report['type']) && $report['type'] === 'assoc' && array_key_exists($extra, $cmp)) {
  82. $cmp = $cmp[$extra];
  83. }
  84. $report['message'] = sprintf($report['message_extras'],
  85. $extra, $cmp);
  86. } elseif(isset($report['message_extra'])) {
  87. $report['message'] = sprintf($report['message_extra'], $extra);
  88. }
  89. $report['where'] = $where;
  90. $report['line'] = empty($line) ? $this->node->start_line : $line;
  91. $this->reports[] = $report;
  92. $flag = $report['flag'][0];
  93. if(isset($report['penalty'])) {
  94. $this->penalty -= $report['penalty'];
  95. }
  96. else eval('$this->penalty -= '.$flag.'_PENALTY;');
  97. }
  98. }
  99. /**
  100. ----------------------------------------------------------------------+
  101. * Tokens common to all scopes.
  102. * @param int current position
  103. ----------------------------------------------------------------------+
  104. */
  105. public function common_tokens($pos) {
  106. $token = $this->node->tokens[$pos];
  107. if($this->final_return === 1
  108. && $token[0] !== T_CLOSE_SCOPE
  109. && \phplinter\Tokenizer::meaningfull($token[0]))
  110. {
  111. $this->final_return = 2;
  112. $this->report('WAR_UNREACHABLE_CODE', null, $token[2]);
  113. }
  114. $t = $token[0];
  115. if($this->config->match_rule('DEP_DEPRECATED_TOKEN', $t))
  116. {
  117. $this->report('DEP_DEPRECATED_TOKEN', \phplinter\Tokenizer::token_name($t));
  118. }
  119. switch($t) {
  120. case T_INLINE_HTML:
  121. $this->report('REF_HTML_MIXIN', null, $token[2]);
  122. break;
  123. case T_REQUIRE:
  124. case T_REQUIRE_ONCE:
  125. case T_INCLUDE:
  126. case T_INCLUDE_ONCE:
  127. $this->sec_includes($pos);
  128. break;
  129. case T_IS_EQUAL:
  130. case T_IS_NOT_EQUAL:
  131. $this->report('INF_COMPARE', null, $token[2]);
  132. break;
  133. case T_BACKTICK:
  134. $this->sec_backtick($pos);
  135. break;
  136. case T_STRING:
  137. $this->parse_string($pos);
  138. break;
  139. case T_RETURN:
  140. case T_EXIT:
  141. $prev = $this->node->tokens[$this->prev($pos)];
  142. if(!in_array($prev[0], array(T_LOGICAL_OR, T_BOOLEAN_OR))
  143. && $this->scope === 0
  144. && $this->final_return === false)
  145. {
  146. $this->final_return = true;
  147. }
  148. break;
  149. case T_OPEN_SCOPE:
  150. $this->branches++;
  151. if($token[1] === 'switch') {
  152. if($this->switch !== false) {
  153. $this->report('REF_NESTED_SWITCH', null, $token[2]);
  154. }
  155. $this->switch = $this->scope;
  156. }
  157. $this->scope++;
  158. if($this->config->match_rule('REF_DEEP_NESTING', $this->scope)) {
  159. $this->report('REF_DEEP_NESTING', $this->scope, $token[2]);
  160. }
  161. break;
  162. case T_CLOSE_SCOPE:
  163. $this->scope--;
  164. if($this->switch === $this->scope)
  165. $this->switch = false;
  166. break;
  167. case T_SEMICOLON:
  168. if($this->final_return === true) {
  169. $this->final_return = 1;
  170. }
  171. break;
  172. }
  173. }
  174. /**
  175. ----------------------------------------------------------------------+
  176. * Penalty
  177. * @return Float
  178. ----------------------------------------------------------------------+
  179. */
  180. public function penalty() {
  181. return $this->penalty;
  182. }
  183. /**
  184. ----------------------------------------------------------------------+
  185. * Search for security infractions
  186. * @param int current position
  187. ----------------------------------------------------------------------+
  188. */
  189. protected function security($pos) {
  190. if($this->config->report_on('S')) {
  191. $token = $this->node->tokens[$pos];
  192. $this->sec_strings($pos);
  193. if(in_array($token[1], array_keys($this->sec_5))) {
  194. $this->sec_callbacks($pos);
  195. }
  196. /* Special */
  197. elseif($token[1] == 'preg_replace') {
  198. // check for '//e' flag
  199. }
  200. }
  201. }
  202. /**
  203. ----------------------------------------------------------------------+
  204. * Search for security infractions in callback positions
  205. * @param int
  206. ----------------------------------------------------------------------+
  207. */
  208. protected function sec_callbacks($pos) {
  209. $o = $this->node->tokens;
  210. $t = $o[$pos];
  211. $this->report('INF_UNSECURE', $t[1], $t[2]);
  212. foreach($this->sec_5[$t[1]] as $_) {
  213. $p = 0;
  214. $i = $pos;
  215. while($o[++$i][0] != T_PARENTHESIS_CLOSE) {
  216. if(in_array($o[$i][1], $this->uvars)) {
  217. /* In callback position */
  218. if($p == $_) {
  219. $this->report('SEC_ERROR_CALLBACK', $t[1], $t[2]);
  220. }
  221. }
  222. $p++;
  223. }
  224. /* Last position */
  225. if(in_array($o[$i-1][1], $this->uvars)
  226. && $_ == -1) {
  227. $this->report('SEC_ERROR_CALLBACK', $t[1], $t[2]);
  228. }
  229. }
  230. }
  231. /**
  232. ----------------------------------------------------------------------+
  233. * Search for security infractions in strings
  234. * @param int
  235. ----------------------------------------------------------------------+
  236. */
  237. protected function sec_strings($pos) {
  238. $o = $this->node->tokens;
  239. $t = $o[$pos];
  240. foreach(array(
  241. array('sec_1', 'INF_UNSECURE', true),
  242. array('sec_2', 'INF_UNSECURE', true),
  243. array('sec_3', 'INF_UNSECURE', false),
  244. array('sec_4', 'INF_WARNING_DISCLOSURE', false)
  245. ) as $_) {
  246. if(in_array($t[1], $this->$_[0])) {
  247. $this->report($_[1], $t[1], $t[2]);
  248. $i = $pos;
  249. if($_[2]) {
  250. while($o[++$i][0] != T_PARENTHESIS_CLOSE) {
  251. if(in_array($o[$i][1], $this->uvars)) {
  252. $this->report('SEC_ERROR_REQUEST', $t[1], $t[2]);
  253. }
  254. }
  255. }
  256. }
  257. }
  258. }
  259. /**
  260. ----------------------------------------------------------------------+
  261. * Search for security infractions in includes
  262. * @param int
  263. ----------------------------------------------------------------------+
  264. */
  265. protected function sec_includes($pos) {
  266. $i = $pos;
  267. $o = $this->node->tokens;
  268. while(isset($o[++$i]) && $o[$i][0] !== T_SEMICOLON) {
  269. if(in_array($o[$i][1], $this->uvars)) {
  270. $this->report('SEC_ERROR_INCLUDE', $o[$pos][1], $o[$pos][2]);
  271. }
  272. }
  273. }
  274. /**
  275. ----------------------------------------------------------------------+
  276. * Search for security infractions in backticks
  277. * @param int
  278. ----------------------------------------------------------------------+
  279. */
  280. protected function sec_backtick($pos) {
  281. $i = $pos;
  282. $o = $this->node->tokens;
  283. while(true) {
  284. if(empty($o[++$i])) break;
  285. $t = $o[$i];
  286. if($t[0] === T_BACKTICK || $t[0] === T_SEMICOLON) break;
  287. if(in_array($t[1], $this->uvars)) {
  288. $this->report('SEC_ERROR_REQUEST', $o[$pos][1], $o[$pos][2]);
  289. }
  290. }
  291. }
  292. /**
  293. ----------------------------------------------------------------------+
  294. * Parse a string token
  295. * @param int current position
  296. ----------------------------------------------------------------------+
  297. */
  298. protected function parse_string($pos) {
  299. $token = $this->node->tokens[$pos];
  300. $ntp = $this->next($pos);
  301. $nt = $this->node->tokens[$ntp][0];
  302. if($nt === T_PARENTHESIS_OPEN || $nt === T_DOUBLE_COLON) {
  303. $this->security($pos);
  304. if($nt === T_DOUBLE_COLON) {
  305. $name = $token[1] . '::' . $this->node->tokens[$this->next($ntp)][1];
  306. if($this->config->match_rule('DEP_DEPRECATED_NAME', $name)) {
  307. $this->report('DEP_DEPRECATED_NAME', $name);
  308. }
  309. if($this->config->match_rule('DEP_DEPRECATED_NAME_REPLACE', $name)) {
  310. $this->report('DEP_DEPRECATED_NAME_REPLACE', $name);
  311. }
  312. }
  313. }
  314. if($this->config->match_rule('DEP_DEPRECATED_NAME', $token[1])) {
  315. $this->report('DEP_DEPRECATED_NAME', $token[1]);
  316. }
  317. if($this->config->match_rule('DEP_DEPRECATED_NAME_REPLACE', $token[1])) {
  318. $this->report('DEP_DEPRECATED_NAME_REPLACE', $token[1]);
  319. }
  320. }
  321. /**
  322. ----------------------------------------------------------------------+
  323. * Return the next meaningfull token
  324. * @param int current position
  325. * @return Int
  326. ----------------------------------------------------------------------+
  327. */
  328. protected function next($pos) {
  329. $i = $pos;
  330. $o = $this->node->tokens;
  331. $c = $this->node->token_count;
  332. while(++$i < $c) {
  333. if(\phplinter\Tokenizer::meaningfull($o[$i][0]))
  334. return $i;
  335. }
  336. return false;
  337. }
  338. /**
  339. ----------------------------------------------------------------------+
  340. * Return the previous meaningfull token
  341. * @param int current position
  342. * @return Int
  343. ----------------------------------------------------------------------+
  344. */
  345. protected function prev($pos) {
  346. $i = $pos;
  347. $o = $this->node->tokens;
  348. $c = $this->node->start;
  349. while(--$i > $c) {
  350. if(\phplinter\Tokenizer::meaningfull($o[$i][0]))
  351. return $i;
  352. }
  353. return false;
  354. }
  355. /**
  356. ----------------------------------------------------------------------+
  357. * Find the next token.
  358. * @param int current position
  359. * @param int Seek token
  360. * @param int Limit to forward track
  361. * @return Int
  362. ----------------------------------------------------------------------+
  363. */
  364. protected function find($pos, $token, $limit=10) {
  365. $i = $pos;
  366. $o = $this->node->tokens;
  367. $c = $this->node->token_count;
  368. while(++$i < $c) {
  369. if($o[$i][0] == $token)
  370. return $i;
  371. if(!empty($limit) && ($i - $pos) == $limit)
  372. break;
  373. }
  374. return false;
  375. }
  376. /**
  377. ----------------------------------------------------------------------+
  378. * Analyse node
  379. * @return Array Reports
  380. ----------------------------------------------------------------------+
  381. */
  382. public function lint() {
  383. $this->node->dochead = false;
  384. if(!empty($this->node->comments)) {
  385. foreach($this->node->comments as $node) {
  386. if($node->type === T_DOC_COMMENT) $this->node->dochead = true;
  387. $node->owner = $this->node->name;
  388. $lint = new LComment($node, $this->config);
  389. foreach($lint->bind($this)->lint() as $_) {
  390. $this->reports[] = $_;
  391. }
  392. $this->penalty += $lint->penalty();
  393. }
  394. }
  395. $this->recurse();
  396. return $this->_lint();
  397. }
  398. /**
  399. ----------------------------------------------------------------------+
  400. * Analyse nodes owned by current node
  401. ----------------------------------------------------------------------+
  402. */
  403. protected function recurse() {
  404. if(!empty($this->node->nodes)) {
  405. $a = array(
  406. T_CLASS => 'LClass',
  407. T_DOC_COMMENT => 'LComment',
  408. T_FUNCTION => 'LFunction',
  409. T_ANON_FUNCTION => 'LAnon_function',
  410. T_INTERFACE => 'LInterface',
  411. T_METHOD => 'LMethod',
  412. T_FILE => 'LFile',
  413. );
  414. foreach($this->node->nodes as $node) {
  415. $this->profile();
  416. $class = "\\phplinter\\Lint\\{$a[$node->type]}";
  417. $lint = new $class($node, $this->config);
  418. foreach($lint->bind($this)->lint() as $_) $this->reports[] = $_;
  419. $this->penalty += $lint->penalty();
  420. unset($lint);
  421. $this->profile($class.'::'.$node->name);
  422. }
  423. }
  424. }
  425. /**
  426. ----------------------------------------------------------------------+
  427. * Internal profiling
  428. * @param Bool
  429. ----------------------------------------------------------------------+
  430. */
  431. protected function profile($flushmsg=false) {
  432. if(defined('PHPL_PROFILE_ON')) {
  433. $now = microtime(true);
  434. if($flushmsg) {
  435. $time = $this->ptime - $now;
  436. echo "$time -> $flushmsg\n";
  437. } else {
  438. $this->ptime = $now;
  439. }
  440. }
  441. }
  442. /**
  443. ----------------------------------------------------------------------+
  444. * Count and process locals at function scope
  445. * @param Array
  446. * @param Array
  447. * @param Array
  448. ----------------------------------------------------------------------+
  449. */
  450. protected function process_locals($locals, $_locals, $args) {
  451. foreach($locals as $ll) {
  452. // Skip superglobals
  453. if(in_array($ll, $this->globals)) continue;
  454. $cnt = count(array_filter($_locals, function($s) use($ll) {
  455. return $s == $ll;
  456. }));
  457. if($cnt === 1 && !in_array($ll, array_keys($args))) {
  458. if(isset($this->locals[T_VARIABLE])
  459. && !in_array($ll, $this->locals[T_VARIABLE]))
  460. {
  461. $this->report('WAR_UNUSED_VAR', $ll);
  462. }
  463. }
  464. }
  465. }
  466. /**
  467. ----------------------------------------------------------------------+
  468. * Parse argument-list
  469. * @param int current position
  470. * @return Array
  471. ----------------------------------------------------------------------+
  472. */
  473. protected function parse_args(&$i) {
  474. $out = array();
  475. $o = $this->node->tokens;
  476. while(true) {
  477. switch($o[++$i][0]) {
  478. case T_VARIABLE:
  479. if($o[$i+1][0] === T_EQUALS) {
  480. $out[$o[$i][1]] = $o[$i+2][1];
  481. $i += 2;
  482. } else {
  483. $out[$o[$i][1]] = null;
  484. }
  485. break;
  486. case T_PARENTHESIS_CLOSE:
  487. return $out;
  488. }
  489. }
  490. }
  491. /**
  492. ----------------------------------------------------------------------+
  493. * Process argument list to function
  494. * @param Array
  495. * @param Array
  496. ----------------------------------------------------------------------+
  497. */
  498. protected function process_args($locals, $args) {
  499. if(!empty($args)) {
  500. foreach(array_keys($args) as $_)
  501. if(!in_array($_, $locals))
  502. $this->report('WAR_UNUSED_ARG', $_);
  503. }
  504. $this->node->arguments = $args;
  505. }
  506. /**
  507. ----------------------------------------------------------------------+
  508. * Add data to node parent
  509. * @param String Name
  510. * @param int Type
  511. ----------------------------------------------------------------------+
  512. */
  513. protected function add_parent_data($name, $type) {
  514. if(empty($this->parent)) return;
  515. if(!isset($this->parent->locals[$type])) $this->parent->locals[$type] = array();
  516. if(is_array($name)) {
  517. $this->parent->locals[$type] = array_merge($name, $this->parent->locals[$type]);
  518. }
  519. elseif(!in_array($name, $this->parent->locals[$type]))
  520. $this->parent->locals[$type][] = $name;
  521. }
  522. /**
  523. ----------------------------------------------------------------------+
  524. * Bind parent node to current node
  525. * @param Object BaseLint
  526. * @return this
  527. ----------------------------------------------------------------------+
  528. */
  529. public function bind(BaseLint & $parent) {
  530. $this->parent = $parent;
  531. return $this;
  532. }
  533. }