mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-22 06:42:41 +01:00
Add a linter rule to detect mismatched parameters for formatted strings
Summary: Fixes T7046. Adds a linter rule to detect mismatched parameters for formatted strings. Originally I had considered putting this rule in `ArcanistPhutilXHPASTLinter`, but I later decided to move it to `ArcanistXHPASTLinter` as I think that it is general enough to be more widely useful. Test Plan: This seems to work but needs some polish. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T7046 Differential Revision: https://secure.phabricator.com/D11661
This commit is contained in:
parent
b32cce79b2
commit
d8ba7e6f71
2 changed files with 120 additions and 1 deletions
|
@ -53,9 +53,11 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
const LINT_BLACKLISTED_FUNCTION = 51;
|
const LINT_BLACKLISTED_FUNCTION = 51;
|
||||||
const LINT_IMPLICIT_VISIBILITY = 52;
|
const LINT_IMPLICIT_VISIBILITY = 52;
|
||||||
const LINT_CALL_TIME_PASS_BY_REF = 53;
|
const LINT_CALL_TIME_PASS_BY_REF = 53;
|
||||||
|
const LINT_FORMATTED_STRING = 54;
|
||||||
|
|
||||||
private $blacklistedFunctions = array();
|
private $blacklistedFunctions = array();
|
||||||
private $naminghook;
|
private $naminghook;
|
||||||
|
private $printfFunctions = array();
|
||||||
private $switchhook;
|
private $switchhook;
|
||||||
private $version;
|
private $version;
|
||||||
private $windowsVersion;
|
private $windowsVersion;
|
||||||
|
@ -118,6 +120,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
self::LINT_BLACKLISTED_FUNCTION => 'Use of Blacklisted Function',
|
self::LINT_BLACKLISTED_FUNCTION => 'Use of Blacklisted Function',
|
||||||
self::LINT_IMPLICIT_VISIBILITY => 'Implicit Method Visibility',
|
self::LINT_IMPLICIT_VISIBILITY => 'Implicit Method Visibility',
|
||||||
self::LINT_CALL_TIME_PASS_BY_REF => 'Call-Time Pass-By-Reference',
|
self::LINT_CALL_TIME_PASS_BY_REF => 'Call-Time Pass-By-Reference',
|
||||||
|
self::LINT_FORMATTED_STRING => 'Formatted String',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +178,14 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
'Name of a concrete subclass of ArcanistXHPASTLintNamingHook which '.
|
'Name of a concrete subclass of ArcanistXHPASTLintNamingHook which '.
|
||||||
'enforces more granular naming convention rules for symbols.'),
|
'enforces more granular naming convention rules for symbols.'),
|
||||||
),
|
),
|
||||||
|
'xhpast.printf-functions' => array(
|
||||||
|
'type' => 'optional map<string, int>',
|
||||||
|
'help' => pht(
|
||||||
|
'%s-style functions which take a format string and list of values '.
|
||||||
|
'as arguments. The value for the mapping is the start index of the '.
|
||||||
|
'function parameters (the index of the format string parameter).',
|
||||||
|
'printf()'),
|
||||||
|
),
|
||||||
'xhpast.switchhook' => array(
|
'xhpast.switchhook' => array(
|
||||||
'type' => 'optional string',
|
'type' => 'optional string',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
|
@ -200,6 +211,9 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
case 'xhpast.naminghook':
|
case 'xhpast.naminghook':
|
||||||
$this->naminghook = $value;
|
$this->naminghook = $value;
|
||||||
return;
|
return;
|
||||||
|
case 'xhpast.printf-functions':
|
||||||
|
$this->printfFunctions = $value;
|
||||||
|
return;
|
||||||
case 'xhpast.switchhook':
|
case 'xhpast.switchhook':
|
||||||
$this->switchhook = $value;
|
$this->switchhook = $value;
|
||||||
return;
|
return;
|
||||||
|
@ -216,7 +230,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
|
|
||||||
public function getVersion() {
|
public function getVersion() {
|
||||||
// The version number should be incremented whenever a new rule is added.
|
// The version number should be incremented whenever a new rule is added.
|
||||||
return '15';
|
return '16';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveFuture($path, Future $future) {
|
protected function resolveFuture($path, Future $future) {
|
||||||
|
@ -293,6 +307,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
'lintMethodModifier' => self::LINT_IMPLICIT_VISIBILITY,
|
'lintMethodModifier' => self::LINT_IMPLICIT_VISIBILITY,
|
||||||
'lintPropertyModifier' => self::LINT_IMPLICIT_VISIBILITY,
|
'lintPropertyModifier' => self::LINT_IMPLICIT_VISIBILITY,
|
||||||
'lintCallTimePassByReference' => self::LINT_CALL_TIME_PASS_BY_REF,
|
'lintCallTimePassByReference' => self::LINT_CALL_TIME_PASS_BY_REF,
|
||||||
|
'lintFormattedString' => self::LINT_FORMATTED_STRING,
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($method_codes as $method => $codes) {
|
foreach ($method_codes as $method => $codes) {
|
||||||
|
@ -3103,6 +3118,83 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function lintFormattedString(XHPASTNode $root) {
|
||||||
|
static $functions = array(
|
||||||
|
// Core PHP
|
||||||
|
'fprintf' => 1,
|
||||||
|
'printf' => 0,
|
||||||
|
'sprintf' => 0,
|
||||||
|
'vfprintf' => 1,
|
||||||
|
|
||||||
|
// libphutil
|
||||||
|
'csprintf' => 0,
|
||||||
|
'execx' => 0,
|
||||||
|
'exec_manual' => 0,
|
||||||
|
'hgsprintf' => 0,
|
||||||
|
'hsprintf' => 0,
|
||||||
|
'jsprintf' => 0,
|
||||||
|
'pht' => 0,
|
||||||
|
'phutil_passthru' => 0,
|
||||||
|
'qsprintf' => 1,
|
||||||
|
'queryfx' => 1,
|
||||||
|
'queryfx_all' => 1,
|
||||||
|
'queryfx_one' => 1,
|
||||||
|
'vcsprintf' => 0,
|
||||||
|
'vqsprintf' => 1,
|
||||||
|
'vqueryfx' => 1,
|
||||||
|
'vqueryfx_all' => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||||
|
|
||||||
|
foreach ($function_calls as $call) {
|
||||||
|
$name = $call->getChildByIndex(0)->getConcreteString();
|
||||||
|
|
||||||
|
$name = strtolower($name);
|
||||||
|
$start = idx($functions + $this->printfFunctions, $name);
|
||||||
|
|
||||||
|
if ($start === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
|
||||||
|
$argc = count($parameters->getChildren()) - $start;
|
||||||
|
|
||||||
|
if ($argc < 1) {
|
||||||
|
$this->raiseLintAtNode(
|
||||||
|
$call,
|
||||||
|
self::LINT_FORMATTED_STRING,
|
||||||
|
pht('This function is expected to have a format string.'));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$format = $parameters->getChildByIndex($start);
|
||||||
|
if ($format->getTypeName() != 'n_STRING_SCALAR') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$argv = array($format->evalStatic()) + array_fill(0, $argc, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
xsprintf(array($this, 'xsprintfCallback'), null, $argv);
|
||||||
|
} catch (BadFunctionCallException $ex) {
|
||||||
|
$this->raiseLintAtNode(
|
||||||
|
$call,
|
||||||
|
self::LINT_FORMATTED_STRING,
|
||||||
|
$ex->getMessage());
|
||||||
|
} catch (InvalidArgumentException $ex) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub function to be used as an @{function:xsprintf} callback.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function xsprintfCallback() {}
|
||||||
|
|
||||||
public function getSuperGlobalNames() {
|
public function getSuperGlobalNames() {
|
||||||
return array(
|
return array(
|
||||||
'$GLOBALS',
|
'$GLOBALS',
|
||||||
|
|
27
src/lint/linter/__tests__/xhpast/formatted-string.lint-test
Normal file
27
src/lint/linter/__tests__/xhpast/formatted-string.lint-test
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
printf();
|
||||||
|
printf(null);
|
||||||
|
printf('');
|
||||||
|
|
||||||
|
sprintf('%s');
|
||||||
|
pht('%s', 'foo', 'bar');
|
||||||
|
|
||||||
|
fprintf(null, 'x');
|
||||||
|
queryfx(null, 'x', 'y');
|
||||||
|
|
||||||
|
foobar(null, null, '%s');
|
||||||
|
~~~~~~~~~~
|
||||||
|
error:2:1
|
||||||
|
error:6:1
|
||||||
|
error:7:1
|
||||||
|
error:10:1
|
||||||
|
error:12:1
|
||||||
|
~~~~~~~~~~
|
||||||
|
~~~~~~~~~~
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"xhpast.printf-functions": {
|
||||||
|
"foobar": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue