2013-01-29 07:03:03 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @group linter
|
|
|
|
*/
|
|
|
|
final class ArcanistPhutilXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
|
|
|
|
|
|
|
const LINT_ARRAY_COMBINE = 2;
|
2013-02-09 02:38:56 +01:00
|
|
|
const LINT_DEPRECATED_FUNCTION = 3;
|
2013-02-02 21:33:22 +01:00
|
|
|
const LINT_UNSAFE_DYNAMIC_STRING = 4;
|
2013-01-29 07:03:03 +01:00
|
|
|
|
|
|
|
private $xhpastLinter;
|
2013-02-10 15:35:25 +01:00
|
|
|
private $deprecatedFunctions = array();
|
|
|
|
private $dynamicStringFunctions = array();
|
|
|
|
private $dynamicStringClasses = array();
|
2013-01-29 07:03:03 +01:00
|
|
|
|
|
|
|
public function setXHPASTLinter(ArcanistXHPASTLinter $linter) {
|
|
|
|
$this->xhpastLinter = $linter;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-02-10 15:35:25 +01:00
|
|
|
public function setDeprecatedFunctions($map) {
|
|
|
|
$this->deprecatedFunctions = $map;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setDynamicStringFunctions($map) {
|
|
|
|
$this->dynamicStringFunctions = $map;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setDynamicStringClasses($map) {
|
|
|
|
$this->dynamicStringClasses = $map;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-01-29 07:03:03 +01:00
|
|
|
public function setEngine(ArcanistLintEngine $engine) {
|
|
|
|
if (!$this->xhpastLinter) {
|
|
|
|
throw new Exception(
|
|
|
|
'Call setXHPASTLinter() before using ArcanistPhutilXHPASTLinter.');
|
|
|
|
}
|
|
|
|
$this->xhpastLinter->setEngine($engine);
|
|
|
|
return parent::setEngine($engine);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLintNameMap() {
|
|
|
|
return array(
|
|
|
|
self::LINT_ARRAY_COMBINE => 'array_combine() Unreliable',
|
2013-02-10 00:04:20 +01:00
|
|
|
self::LINT_DEPRECATED_FUNCTION => 'Use of Deprecated Function',
|
2013-02-02 21:33:22 +01:00
|
|
|
self::LINT_UNSAFE_DYNAMIC_STRING => 'Unsafe Usage of Dynamic String',
|
2013-01-29 07:03:03 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLintSeverityMap() {
|
2013-02-02 21:33:22 +01:00
|
|
|
$warning = ArcanistLintSeverity::SEVERITY_WARNING;
|
2013-01-29 07:03:03 +01:00
|
|
|
return array(
|
2013-02-02 21:33:22 +01:00
|
|
|
self::LINT_ARRAY_COMBINE => $warning,
|
2013-02-09 02:38:56 +01:00
|
|
|
self::LINT_DEPRECATED_FUNCTION => $warning,
|
2013-02-02 21:33:22 +01:00
|
|
|
self::LINT_UNSAFE_DYNAMIC_STRING => $warning,
|
2013-01-29 07:03:03 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLinterName() {
|
|
|
|
return 'PHLXHP';
|
|
|
|
}
|
|
|
|
|
2013-02-02 21:33:22 +01:00
|
|
|
public function getCacheVersion() {
|
2013-02-25 21:33:51 +01:00
|
|
|
$version = '2';
|
|
|
|
$path = xhpast_get_binary_path();
|
|
|
|
if (Filesystem::pathExists($path)) {
|
|
|
|
$version .= '-'.md5_file($path);
|
|
|
|
}
|
|
|
|
return $version;
|
2013-02-02 21:33:22 +01:00
|
|
|
}
|
|
|
|
|
2013-02-19 22:42:09 +01:00
|
|
|
protected function buildFutures(array $paths) {
|
|
|
|
return $this->xhpastLinter->buildFutures($paths);
|
2013-01-29 07:03:03 +01:00
|
|
|
}
|
|
|
|
|
2013-02-19 22:42:09 +01:00
|
|
|
protected function resolveFuture($path, Future $future) {
|
2013-01-29 07:03:03 +01:00
|
|
|
$tree = $this->xhpastLinter->getXHPASTTreeForPath($path);
|
|
|
|
if (!$tree) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$root = $tree->getRootNode();
|
|
|
|
|
2013-02-15 00:49:22 +01:00
|
|
|
$method_codes = array(
|
|
|
|
'lintArrayCombine' => self::LINT_ARRAY_COMBINE,
|
|
|
|
'lintUnsafeDynamicString' => self::LINT_UNSAFE_DYNAMIC_STRING,
|
|
|
|
'lintDeprecatedFunctions' => self::LINT_DEPRECATED_FUNCTION,
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($method_codes as $method => $codes) {
|
|
|
|
foreach ((array)$codes as $code) {
|
|
|
|
if ($this->isCodeEnabled($code)) {
|
|
|
|
call_user_func(array($this, $method), $root);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-01-29 07:03:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-02-02 21:33:22 +01:00
|
|
|
private function lintUnsafeDynamicString($root) {
|
2013-02-10 15:35:25 +01:00
|
|
|
$safe = $this->dynamicStringFunctions + array(
|
2013-02-12 03:46:02 +01:00
|
|
|
'pht' => 0,
|
|
|
|
|
2013-02-02 21:33:22 +01:00
|
|
|
'hsprintf' => 0,
|
2013-02-22 01:44:34 +01:00
|
|
|
'jsprintf' => 0,
|
|
|
|
|
|
|
|
'hgsprintf' => 0,
|
2013-02-02 21:33:22 +01:00
|
|
|
|
|
|
|
'csprintf' => 0,
|
|
|
|
'vcsprintf' => 0,
|
|
|
|
'execx' => 0,
|
|
|
|
'exec_manual' => 0,
|
|
|
|
'phutil_passthru' => 0,
|
|
|
|
|
|
|
|
'qsprintf' => 1,
|
|
|
|
'vqsprintf' => 1,
|
|
|
|
'queryfx' => 1,
|
|
|
|
'vqueryfx' => 1,
|
|
|
|
'queryfx_all' => 1,
|
|
|
|
'vqueryfx_all' => 1,
|
|
|
|
'queryfx_one' => 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
|
|
|
$this->lintUnsafeDynamicStringCall($calls, $safe);
|
|
|
|
|
2013-02-10 15:35:25 +01:00
|
|
|
$safe = $this->dynamicStringClasses + array(
|
|
|
|
'ExecFuture' => 0,
|
2013-02-02 21:33:22 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$news = $root->selectDescendantsOfType('n_NEW');
|
|
|
|
$this->lintUnsafeDynamicStringCall($news, $safe);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function lintUnsafeDynamicStringCall(
|
|
|
|
AASTNodeList $calls,
|
|
|
|
array $safe) {
|
|
|
|
|
2013-02-10 15:35:25 +01:00
|
|
|
$safe = array_combine(
|
|
|
|
array_map('strtolower', array_keys($safe)),
|
|
|
|
$safe);
|
|
|
|
|
2013-02-02 21:33:22 +01:00
|
|
|
foreach ($calls as $call) {
|
|
|
|
$name = $call->getChildByIndex(0)->getConcreteString();
|
|
|
|
$param = idx($safe, strtolower($name));
|
|
|
|
|
|
|
|
if ($param === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parameters = $call->getChildByIndex(1);
|
|
|
|
if (count($parameters->getChildren()) <= $param) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$identifier = $parameters->getChildByIndex($param);
|
|
|
|
if (!$identifier->isConstantString()) {
|
|
|
|
$this->raiseLintAtNode(
|
|
|
|
$call,
|
|
|
|
self::LINT_UNSAFE_DYNAMIC_STRING,
|
|
|
|
"Parameter ".($param + 1)." of {$name}() should be a scalar string, ".
|
|
|
|
"otherwise it's not safe.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-29 07:03:03 +01:00
|
|
|
private function lintArrayCombine($root) {
|
|
|
|
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
|
|
|
foreach ($function_calls as $call) {
|
|
|
|
$name = $call->getChildByIndex(0)->getConcreteString();
|
|
|
|
if (strcasecmp($name, 'array_combine') == 0) {
|
|
|
|
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
|
|
|
|
if (count($parameter_list->getChildren()) !== 2) {
|
|
|
|
// Wrong number of parameters, but raise that elsewhere if we want.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first = $parameter_list->getChildByIndex(0);
|
|
|
|
$second = $parameter_list->getChildByIndex(1);
|
|
|
|
|
|
|
|
if ($first->getConcreteString() == $second->getConcreteString()) {
|
|
|
|
$this->raiseLintAtNode(
|
|
|
|
$call,
|
|
|
|
self::LINT_ARRAY_COMBINE,
|
|
|
|
'Prior to PHP 5.4, array_combine() fails when given empty '.
|
|
|
|
'arrays. Prefer to write array_combine(x, x) as array_fuse(x).');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-09 02:38:56 +01:00
|
|
|
private function lintDeprecatedFunctions($root) {
|
2013-02-10 15:35:25 +01:00
|
|
|
$map = $this->deprecatedFunctions + array(
|
2013-02-09 02:38:56 +01:00
|
|
|
'phutil_render_tag' =>
|
|
|
|
'The phutil_render_tag() function is deprecated and unsafe. '.
|
|
|
|
'Use phutil_tag() instead.',
|
|
|
|
);
|
|
|
|
|
|
|
|
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
|
|
|
foreach ($function_calls as $call) {
|
|
|
|
$name = $call->getChildByIndex(0)->getConcreteString();
|
|
|
|
|
|
|
|
$name = strtolower($name);
|
|
|
|
if (empty($map[$name])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->raiseLintAtNode(
|
|
|
|
$call,
|
|
|
|
self::LINT_DEPRECATED_FUNCTION,
|
|
|
|
$map[$name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-29 07:03:03 +01:00
|
|
|
}
|