mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-22 14:52:40 +01:00
Fix false negatives in "break;" lint check
Summary: See D3449, comments, unit tests. Test Plan: Unit tests. Reviewers: vrana Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D3463
This commit is contained in:
parent
339cabedcc
commit
918eff8ff9
2 changed files with 86 additions and 14 deletions
|
@ -487,28 +487,61 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
|
|
||||||
|
|
||||||
foreach ($blocks as $key => $block) {
|
foreach ($blocks as $key => $block) {
|
||||||
$tokens = $block->getTokens();
|
// Collect all the tokens in this block which aren't at top level.
|
||||||
|
// We want to ignore "break", and "continue" in these blocks.
|
||||||
|
$lower_level = $block->selectDescendantsOfType('n_WHILE');
|
||||||
|
$lower_level->add($block->selectDescendantsOfType('n_DO_WHILE'));
|
||||||
|
$lower_level->add($block->selectDescendantsOfType('n_FOR'));
|
||||||
|
$lower_level->add($block->selectDescendantsOfType('n_FOREACH'));
|
||||||
|
$lower_level->add($block->selectDescendantsOfType('n_SWITCH'));
|
||||||
|
$lower_level_tokens = array();
|
||||||
|
foreach ($lower_level as $lower_level_block) {
|
||||||
|
$lower_level_tokens += $lower_level_block->getTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all the tokens in this block which aren't in this scope
|
||||||
|
// (because they're inside class, function or interface declarations).
|
||||||
|
// We want to ignore all of these tokens.
|
||||||
|
$decls = $block->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||||
|
$decls->add($block->selectDescendantsOfType('n_CLASS_DECLARATION'));
|
||||||
|
// For completeness; these can't actually have anything.
|
||||||
|
$decls->add($block->selectDescendantsOfType('n_INTERFACE_DECLARATION'));
|
||||||
|
$different_scope_tokens = array();
|
||||||
|
foreach ($decls as $decl) {
|
||||||
|
$different_scope_tokens += $decl->getTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
$lower_level_tokens += $different_scope_tokens;
|
||||||
|
|
||||||
// Get all the trailing nonsemantic tokens, since we need to look for
|
// Get all the trailing nonsemantic tokens, since we need to look for
|
||||||
// "fallthrough" comments past the end of the semantic block.
|
// "fallthrough" comments past the end of the semantic block.
|
||||||
|
|
||||||
|
$tokens = $block->getTokens();
|
||||||
$last = end($tokens);
|
$last = end($tokens);
|
||||||
while ($last && $last = $last->getNextToken()) {
|
while ($last && $last = $last->getNextToken()) {
|
||||||
if (!$last->isSemantic()) {
|
if (!$last->isSemantic()) {
|
||||||
$tokens[] = $last;
|
$tokens[$last->getTokenID()] = $last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$blocks[$key] = $tokens;
|
$blocks[$key] = array(
|
||||||
|
$tokens,
|
||||||
|
$lower_level_tokens,
|
||||||
|
$different_scope_tokens,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($blocks as $tokens) {
|
foreach ($blocks as $token_lists) {
|
||||||
|
list(
|
||||||
|
$tokens,
|
||||||
|
$lower_level_tokens,
|
||||||
|
$different_scope_tokens) = $token_lists;
|
||||||
|
|
||||||
// Test each block (case or default statement) to see if it's OK. It's
|
// Test each block (case or default statement) to see if it's OK. It's
|
||||||
// OK if:
|
// OK if:
|
||||||
//
|
//
|
||||||
// - it is empty; or
|
// - it is empty; or
|
||||||
// - it ends in break, return, throw, continue or exit; or
|
// - it ends in break, return, throw, continue or exit at top level; or
|
||||||
// - it has a comment with "fallthrough" in its text.
|
// - it has a comment with "fallthrough" in its text.
|
||||||
|
|
||||||
// Empty blocks are OK, so we start this at `true` and only set it to
|
// Empty blocks are OK, so we start this at `true` and only set it to
|
||||||
|
@ -519,7 +552,7 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
// the block (break, return, throw, continue) or something else.
|
// the block (break, return, throw, continue) or something else.
|
||||||
$statement_ok = false;
|
$statement_ok = false;
|
||||||
|
|
||||||
foreach ($tokens as $token) {
|
foreach ($tokens as $token_id => $token) {
|
||||||
if (!$token->isSemantic()) {
|
if (!$token->isSemantic()) {
|
||||||
// Liberally match "fall" in the comment text so that comments like
|
// Liberally match "fall" in the comment text so that comments like
|
||||||
// "fallthru", "fall through", "fallthrough", etc., are accepted.
|
// "fallthru", "fall through", "fallthrough", etc., are accepted.
|
||||||
|
@ -532,6 +565,14 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
|
|
||||||
$tok_type = $token->getTypeName();
|
$tok_type = $token->getTypeName();
|
||||||
|
|
||||||
|
if ($tok_type == 'T_FUNCTION' ||
|
||||||
|
$tok_type == 'T_CLASS' ||
|
||||||
|
$tok_type == 'T_INTERFACE') {
|
||||||
|
// These aren't statements, but mark the block as nonempty anyway.
|
||||||
|
$block_ok = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($tok_type == ';') {
|
if ($tok_type == ';') {
|
||||||
if ($statement_ok) {
|
if ($statement_ok) {
|
||||||
$statment_ok = false;
|
$statment_ok = false;
|
||||||
|
@ -541,13 +582,23 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($tok_type == 'T_BREAK' ||
|
||||||
|
$tok_type == 'T_CONTINUE') {
|
||||||
|
if (empty($lower_level_tokens[$token_id])) {
|
||||||
|
$statement_ok = true;
|
||||||
|
$block_ok = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($tok_type == 'T_RETURN' ||
|
if ($tok_type == 'T_RETURN' ||
|
||||||
$tok_type == 'T_BREAK' ||
|
|
||||||
$tok_type == 'T_CONTINUE' ||
|
|
||||||
$tok_type == 'T_THROW' ||
|
$tok_type == 'T_THROW' ||
|
||||||
$tok_type == 'T_EXIT') {
|
$tok_type == 'T_EXIT') {
|
||||||
$statement_ok = true;
|
if (empty($different_scope_tokens[$token_id])) {
|
||||||
$block_ok = true;
|
$statement_ok = true;
|
||||||
|
$block_ok = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,16 +54,37 @@ switch ($x) {
|
||||||
while (true) {
|
while (true) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// TODO: false negative
|
|
||||||
case 2:
|
case 2:
|
||||||
switch ($y) {
|
switch ($y) {
|
||||||
case 1:
|
case 1:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// TODO: false negative
|
case 3:
|
||||||
|
while (true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
function f() { throw new Exception(); }
|
||||||
|
g(function() { return; });
|
||||||
|
final class C { public function m() { return; } }
|
||||||
|
interface I { }
|
||||||
|
case 5:
|
||||||
|
do {
|
||||||
|
break;
|
||||||
|
} while (true);
|
||||||
|
case 6:
|
||||||
|
// We accept this "false" positive because the construction is bizarre and
|
||||||
|
// the author can easily add an unreachable "break;" for clarity.
|
||||||
|
do {
|
||||||
|
break 2;
|
||||||
|
} while (true);
|
||||||
}
|
}
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
warning:41:3
|
warning:41:3
|
||||||
warning:48:3
|
warning:48:3
|
||||||
disabled:57:8
|
warning:53:3
|
||||||
disabled:63:8
|
warning:57:3
|
||||||
|
warning:66:3
|
||||||
|
disabled:68:7 # PHP 5.3 features
|
||||||
|
warning:71:3
|
||||||
|
warning:75:3
|
Loading…
Reference in a new issue