2016-05-09 14:56:07 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace CodeInspector;
|
|
|
|
|
|
|
|
use PhpParser;
|
|
|
|
|
|
|
|
class ScopeChecker
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Do all code paths in this list of statements exit the block (return/throw)
|
|
|
|
*
|
|
|
|
* @param array<PhpParser\Node\Stmt> $stmts
|
|
|
|
* @param bool $check_continue - also looks for a continue
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-06-10 20:47:44 +02:00
|
|
|
public static function doesLeaveBlock(array $stmts, $check_continue = true, $check_break = true)
|
2016-05-09 14:56:07 +02:00
|
|
|
{
|
2016-06-13 07:48:29 +02:00
|
|
|
if (empty($stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-09 14:56:07 +02:00
|
|
|
for ($i = count($stmts) - 1; $i >= 0; $i--) {
|
|
|
|
$stmt = $stmts[$i];
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
|
|
|
|
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
|
2016-06-21 00:10:24 +02:00
|
|
|
$stmt instanceof PhpParser\Node\Expr\Exit_ ||
|
2016-06-10 20:47:44 +02:00
|
|
|
($check_continue && $stmt instanceof PhpParser\Node\Stmt\Continue_) ||
|
|
|
|
($check_break && $stmt instanceof PhpParser\Node\Stmt\Break_)) {
|
2016-05-09 14:56:07 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
2016-06-20 22:18:31 +02:00
|
|
|
if ($stmt->else &&
|
|
|
|
self::doesLeaveBlock($stmt->stmts, $check_continue, $check_break) &&
|
2016-06-10 20:47:44 +02:00
|
|
|
self::doesLeaveBlock($stmt->else->stmts, $check_continue, $check_break)) {
|
|
|
|
|
2016-05-09 14:56:07 +02:00
|
|
|
if (empty($stmt->elseifs)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($stmt->elseifs as $elseif) {
|
2016-06-10 20:47:44 +02:00
|
|
|
if (!self::doesLeaveBlock($elseif->stmts, $check_continue, $check_break)) {
|
2016-05-09 14:56:07 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Switch_ && $stmt->cases[count($stmt->cases) - 1]->cond === null) {
|
|
|
|
$all_cases_terminate = true;
|
|
|
|
|
|
|
|
foreach ($stmt->cases as $case) {
|
|
|
|
if (!self::doesLeaveBlock($case->stmts, false)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2016-06-17 00:52:12 +02:00
|
|
|
|
2016-06-20 22:18:31 +02:00
|
|
|
public static function doesBreakOrContinue(array $stmts)
|
|
|
|
{
|
|
|
|
if (empty($stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ($i = count($stmts) - 1; $i >= 0; $i--) {
|
|
|
|
$stmt = $stmts[$i];
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Continue_ || $stmt instanceof PhpParser\Node\Stmt\Break_) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
|
|
|
if ($stmt->else && self::doesBreakOrContinue($stmt->stmts) && self::doesBreakOrContinue($stmt->else->stmts)) {
|
|
|
|
if (empty($stmt->elseifs)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($stmt->elseifs as $elseif) {
|
|
|
|
if (!self::doesBreakOrContinue($elseif->stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Switch_ && $stmt->cases[count($stmt->cases) - 1]->cond === null) {
|
|
|
|
$all_cases_terminate = true;
|
|
|
|
|
|
|
|
foreach ($stmt->cases as $case) {
|
|
|
|
if (!self::doesBreakOrContinue($case->stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function doesReturnOrThrow(array $stmts)
|
|
|
|
{
|
|
|
|
if (empty($stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ($i = count($stmts) - 1; $i >= 0; $i--) {
|
|
|
|
$stmt = $stmts[$i];
|
|
|
|
|
2016-06-21 00:10:24 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
|
|
|
|
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
|
|
|
|
$stmt instanceof PhpParser\Node\Expr\Exit_
|
|
|
|
) {
|
2016-06-20 22:18:31 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
|
|
|
if ($stmt->else && self::doesReturnOrThrow($stmt->stmts) && self::doesReturnOrThrow($stmt->else->stmts)) {
|
|
|
|
if (empty($stmt->elseifs)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($stmt->elseifs as $elseif) {
|
|
|
|
if (!self::doesReturnOrThrow($elseif->stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Switch_ && $stmt->cases[count($stmt->cases) - 1]->cond === null) {
|
|
|
|
$all_cases_terminate = true;
|
|
|
|
|
|
|
|
$has_default = false;
|
|
|
|
foreach ($stmt->cases as $case) {
|
|
|
|
if (self::doesBreakOrContinue($case->stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self::doesReturnOrThrow($case->stmts)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$case->cond) {
|
|
|
|
$has_default = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($has_default) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-06-21 00:10:24 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\While_) {
|
|
|
|
if (self::doesReturnOrThrow($stmt->stmts)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
|
|
|
|
if (self::doesReturnOrThrow($stmt->stmts)) {
|
|
|
|
foreach ($stmt->catches as $catch) {
|
|
|
|
if (!self::doesReturnOrThrow($catch->stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-20 22:18:31 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-17 00:52:12 +02:00
|
|
|
public static function onlyThrows(array $stmts)
|
|
|
|
{
|
|
|
|
if (empty($stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ($i = count($stmts) - 1; $i >= 0; $i--) {
|
|
|
|
$stmt = $stmts[$i];
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Throw_) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Nop) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-09 14:56:07 +02:00
|
|
|
}
|