Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHPCodeValidator #360

Draft
wants to merge 8 commits into
base: dev
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CleantalkSP\Common\Scanner\HeuristicAnalyser\Modules\Tokens;
use CleantalkSP\Common\Scanner\HeuristicAnalyser\Modules\Transformations;
use CleantalkSP\Common\Scanner\HeuristicAnalyser\Modules\Variables;
use CleantalkSP\Common\Scanner\HeuristicAnalyser\Modules\PHPCodeValidator;

/**
* Class Heuristic
Expand Down Expand Up @@ -178,6 +179,11 @@ class HeuristicAnalyser
*/
private $mathematics;

/*
* @var PHPCodeValidator
*/
private $php_code_validator;

/**
* Heuristic constructor.
* Getting common info about file|text and it's content
Expand Down Expand Up @@ -229,6 +235,8 @@ public function __construct($input, $self = null)
$this->includes = new Includes($this->tokens, $this->variables, $this->curr_dir, $this->is_text);
$this->evaluations = new Evaluations($this->tokens, $this->variables, $this->includes, $this->sqls);
$this->code_style = new CodeStyle($this->tokens);
$this->php_code_validator = new PHPCodeValidator($this->tokens);


if ( isset($input['path']) && version_compare(PHP_VERSION, '8.1', '>=') && extension_loaded('mbstring') ) {
// Do not run entropy analysis on included constructs
Expand Down Expand Up @@ -276,12 +284,17 @@ private function checkFileSize($file_size)
*
* @return void
* @psalm-suppress PossiblyUnusedMethod
* @throws HeuristicScannerException
*/
public function processContent()
{
// Skip files does not contain PHP code
if ( $this->extension !== 'php' && ! $this->code_style->hasPHPOpenTags() ) {
return;
if ( $this->extension !== 'php' && !$this->php_code_validator->hasCorrectPHPOpenTags() ) {
throw new HeuristicScannerException('NOT_PHP_CODE');
}

if (!$this->php_code_validator->isValidPHPCode()) {
throw new HeuristicScannerException('NOT_VALID_PHP_CODE');
}

// Analysing code style
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,32 +224,6 @@ public function detectBadLines()
return $result;
}

/**
* Check if file contains PHP open tags ("<\?php" or `<\?`).
* @return bool
*/
public function hasPHPOpenTags()
{
foreach ( $this->tokens as $_token => $content ) {
if ( isset($content[0]) && isset($this->tokens->next1[0]) ) {
if ( $content[0] === 'T_OPEN_TAG' ) {
//check if open tag is short
$is_short = isset($content[1]) && $content[1] === '<?';
if (
// should be whitespaces after tag
$is_short && $this->tokens->next1[0] === 'T_WHITESPACE' ||
// should be whitespaces or variable after tag
!$is_short && in_array($this->tokens->next1[0], array('T_WHITESPACE', 'T_VARIABLE'))
) {
return true;
}
}
}
}

return false;
}

/**
* Count special service chars like <>!= etc. and return the proportion to the total chars count.
* Uses $this->tokens as content source.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
<?php

namespace CleantalkSP\Common\Scanner\HeuristicAnalyser\Modules;

class PHPCodeValidator
{
/**
* The result of checks
*
* @var array
*/
public $check_list_result;

/**
* The tokens to be validated.
*
* @var Tokens
*/
private $tokens;

/**
* PHPCodeValidator constructor.
*
* @param Tokens $tokens The tokens to be validated.
*/
public function __construct($tokens)
{
$this->tokens = $tokens;
}

/**
* Checks if the PHP code is valid.
*
* @return bool Returns true if the PHP code is valid, false otherwise.
* @psalm-suppress PossiblyUnusedMethod
*/
public function isValidPHPCode()
{
$this->hasCorrectPHPOpenTags();
$this->checkBraces();
$this->checkBrackets();
$this->checkParentheses();
$this->checkSingleQuotes();
$this->checkDoubleQuotes();
$this->checkDigitsStartedVariables();
return empty($this->check_list_result);
}

/**
* Checks if the count of left and right braces and brackets are equal.
*
* @return bool Returns true if the count is equal, false otherwise.
*/
private function checkBraces()
{
$braces_l_count = 0;
$braces_r_count = 0;

foreach ( $this->tokens as $token ) {
if ( $token[0] === '__SERV' ) {
if ( $token[1] === '(' ) {
$braces_l_count++;
}
if ( $token[1] === ')' ) {
$braces_r_count++;
}
}
}

if ( $braces_l_count !== $braces_r_count ) {
$this->check_list_result[__FUNCTION__] = 'Braces () count is not equal ' . $braces_l_count . ' != ' . $braces_r_count;
return false;
}

return true;
}

/**
* Checks if the count of left and right brackets are equal.
*
* @return bool Returns true if the count is equal, false otherwise.
*/
private function checkBrackets()
{
$brackets_l_count = 0;
$brackets_r_count = 0;

foreach ( $this->tokens as $token ) {
if ( $token[0] === '__SERV' ) {
if ( $token[1] === '[' ) {
$brackets_l_count++;
}
if ( $token[1] === ']' ) {
$brackets_r_count++;
}
}
}

if ( $brackets_l_count !== $brackets_r_count ) {
$this->check_list_result[__FUNCTION__] = 'Brackets [] count is not equal';
return false;
}

return true;
}

/**
* Checks if the count of left and right parentheses are equal.
*
* @return bool Returns true if the count is equal, false otherwise.
*/
private function checkParentheses()
{
$parentheses_l_count = 0;
$parentheses_r_count = 0;

/**
* init opening and closing tokens, key is token type, value is string to search
*/
$opening_tokens = array(
'__SERV' => '{',
'T_CURLY_OPEN' => '{',
'T_DOLLAR_OPEN_CURLY_BRACES' => '${',
'T_STRING_VARNAME' => '{'
);

$closing_tokens = array(
'__SERV' => '}',
'T_STRING_VARNAME' => '}'
);

foreach ( $this->tokens as $token ) {
if ( isset($opening_tokens[$token[0]]) &&
$token[1] === $opening_tokens[$token[0]] ) {
$parentheses_l_count++;
}
if ( isset($closing_tokens[$token[0]]) &&
$token[1] === $closing_tokens[$token[0]] ) {
$parentheses_r_count++;
}
}

if ( $parentheses_l_count !== $parentheses_r_count ) {
$this->check_list_result[__FUNCTION__] = 'Parentheses {} count is not equal';
return false;
}

return true;
}

/**
* Checks if the count of single quotes are even.
*
* @return bool Returns true if the count is even, false otherwise.
*/
private function checkSingleQuotes()
{
$single_quotes_count = 0;

foreach ( $this->tokens as $token ) {
if ( $token[0] === '__SERV' ) {
if ( $token[1] === "'" ) {
$single_quotes_count++;
}
}
}

if ( $single_quotes_count % 2 !== 0 ) {
$this->check_list_result[__FUNCTION__] = 'Single quotes count is not even';
return false;
}
return true;
}

/**
* Checks if the count of double quotes are even.
*
* @return bool Returns true if the count is even, false otherwise.
*/
private function checkDoubleQuotes()
{
$double_quotes_count = 0;

foreach ( $this->tokens as $token ) {
if ( $token[0] === '__SERV' ) {
if ( $token[1] === '"' ) {
$double_quotes_count++;
}
}
}

if ( $double_quotes_count % 2 !== 0 ) {
$this->check_list_result[__FUNCTION__] = 'Double quotes count is not even';
return false;
}
return true;
}

/**
* Checks if variables contain digits.
*
* @return bool Returns true if no variables contain digits, false otherwise.
*/
private function checkDigitsStartedVariables()
{
foreach ( $this->tokens as $token ) {
if ( $token[0] === 'T_VARIABLE' ) {
if ( preg_match('/^\$\d.+/', $token[1]) ) {
$this->check_list_result[__FUNCTION__] = 'Variable starts with digits [' . $token[1] . ']';
return false;
}
}
}
return true;
}

/**
* Checks if the file does not contain PHP open tags ("<\?php" or `<\?`).
*
* @return bool Returns true if the file does not contain PHP open tags, false otherwise.
*/
public function hasCorrectPHPOpenTags()
{
foreach ( $this->tokens as $_token => $content ) {
if ( isset($content[0]) && isset($this->tokens->next1[0]) ) {
if ( $content[0] === 'T_OPEN_TAG' ) {
//check if open tag is short
$is_short = isset($content[1]) && $content[1] === '<?';
if ( $is_short ) {
if ( $this->tokens->next1[0] !== 'T_WHITESPACE' ) {
$this->check_list_result[__FUNCTION__] = 'PHP open tags are not valid';
return false;
}
} else {
return true;
}
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,51 @@ class Tokens implements \Iterator, \ArrayAccess, \Countable
*/
private $groups;

/**
* @var Token|null $prev4 The fourth previous token in the iteration.
*/
public $prev4;

/**
* @var Token|null $prev3 The third previous token in the iteration.
*/
public $prev3;

/**
* @var Token|null $prev2 The second previous token in the iteration.
*/
public $prev2;

/**
* @var Token|null $prev1 The first previous token in the iteration.
*/
public $prev1;

/**
* @var Token|null $current The current token in the iteration.
*/
public $current;

/**
* @var Token|null $next1 The first next token in the iteration.
*/
public $next1;

/**
* @var Token|null $next2 The second next token in the iteration.
*/
public $next2;

/**
* @var Token|null $next3 The third next token in the iteration.
*/
public $next3;

/**
* @var Token|null $next4 The fourth next token in the iteration.
*/
public $next4;

/**
* @param $content
* @psalm-suppress PossiblyUnusedMethod
Expand Down Expand Up @@ -646,6 +691,7 @@ public function count()
*
* @return Token|null
* @psalm-suppress PossiblyUnusedReturnValue
* @psalm-suppress PossiblyUnusedMethod
*/
public function __get($name)
{
Expand Down Expand Up @@ -679,6 +725,7 @@ public function __get($name)
/**
* @param $name
* @param $value
* @psalm-suppress PossiblyUnusedMethod
*/
public function __set($name, $value)
{
Expand Down
Loading
Loading