diff --git a/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php --- a/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php @@ -94,7 +94,10 @@ $argv = array($format->evalStatic()) + array_fill(0, $argc, null); try { - xsprintf(null, null, $argv); + xsprintf( + 'ArcanistFormattedStringXHPASTLinterRule::processXsprintfCallback', + null, + $argv); } catch (BadFunctionCallException $ex) { $this->raiseLintAtNode( $call, @@ -105,4 +108,23 @@ } } + public static function processXsprintfCallback( + $userdata, + &$pattern, + &$pos, + &$value, + &$length) { + + if ($value !== null) { + throw new Exception('Expected dummy value to be null'); + } + + // Turn format "%$pattern" with argument null into format "%s" with + // argument "%$pattern". This ensures we always provide valid input for + // sprintf to avoid getting a ValueError when using custom format + // specifiers. + $value = '%'.$pattern[$pos]; + $pattern[$pos] = 's'; + } + }