*/ private $removed_unref_vars = []; /** * @param array $stmts * @param array $var_loc_map * */ public function findUnusedAssignment( Codebase $codebase, array $stmts, array $var_loc_map, string $var_id, CodeLocation $original_location ): void { $search_result = $this->findAssignStmt($stmts, $var_id, $original_location); [$assign_stmt, $assign_exp] = $search_result; $chain_assignment = false; if ($assign_stmt !== null && $assign_exp !== null) { // Check if we have to remove assignment statement as expression (i.e. just "$var = ") // Consider chain of assignments $rhs_exp = $assign_exp->expr; if ($rhs_exp instanceof PhpParser\Node\Expr\Assign || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef ) { $chain_assignment = true; $removable_stmt = $this->checkRemovableChainAssignment($assign_exp, $var_loc_map); } else { $removable_stmt = true; } if ($removable_stmt) { $traverser = new PhpParser\NodeTraverser(); $visitor = new CheckTrivialExprVisitor(); $traverser->addVisitor($visitor); $traverser->traverse([$rhs_exp]); $rhs_exp_trivial = (count($visitor->getNonTrivialExpr()) === 0); if ($rhs_exp_trivial) { $treat_as_expr = false; } else { $treat_as_expr = true; } } else { $treat_as_expr = true; } if ($treat_as_expr) { $is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef; $new_file_manipulation = self::getPartialRemovalBounds( $codebase, $original_location, $assign_stmt->getEndFilePos(), $is_assign_ref ); $this->removed_unref_vars[$var_id] = $original_location; } else { // Remove whole assignment statement $new_file_manipulation = new FileManipulation( $assign_stmt->getStartFilePos(), $assign_stmt->getEndFilePos() + 1, "", false, true ); // If statement we are removing is a chain of assignments, mark other variables as removed if ($chain_assignment) { $this->markRemovedChainAssignVar($assign_exp, $var_loc_map); } else { $this->removed_unref_vars[$var_id] = $original_location; } } FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]); } elseif ($assign_exp !== null) { $is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef; $new_file_manipulation = self::getPartialRemovalBounds( $codebase, $original_location, $assign_exp->getEndFilePos(), $is_assign_ref ); FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]); $this->removed_unref_vars[$var_id] = $original_location; } } private static function getPartialRemovalBounds( Codebase $codebase, CodeLocation $var_loc, int $end_bound, bool $assign_ref = false ): FileManipulation { $var_start_loc= $var_loc->raw_file_start; $stmt_content = $codebase->file_provider->getContents( $var_loc->file_path ); $str_for_token = " $var_loc_map */ private function markRemovedChainAssignVar(PhpParser\Node\Expr $cur_assign, array $var_loc_map): void { $var = $cur_assign->var; if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) { $var_name = "$" . $var->name; $var_loc = $var_loc_map[$var_name]; $this->removed_unref_vars[$var_name] = $var_loc; $rhs_exp = $cur_assign->expr; if ($rhs_exp instanceof PhpParser\Node\Expr\Assign || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef ) { $this->markRemovedChainAssignVar($rhs_exp, $var_loc_map); } } } /** * @param PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef $cur_assign * @param array $var_loc_map */ private function checkRemovableChainAssignment(PhpParser\Node\Expr $cur_assign, array $var_loc_map): bool { // Check if current assignment expr's variable is removable $var = $cur_assign->var; if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) { $var_loc = $cur_assign->var->getStartFilePos(); $var_name = "$" . $var->name; if (array_key_exists($var_name, $var_loc_map) && $var_loc_map[$var_name]->raw_file_start === $var_loc) { $curr_removable = true; } else { $curr_removable = false; } if ($curr_removable) { $rhs_exp = $cur_assign->expr; if ($rhs_exp instanceof PhpParser\Node\Expr\Assign || $rhs_exp instanceof PhpParser\Node\Expr\AssignOp || $rhs_exp instanceof PhpParser\Node\Expr\AssignRef ) { return $this->checkRemovableChainAssignment($rhs_exp, $var_loc_map); } } return $curr_removable; } return false; } /** * @param array $stmts * @return array{ * 0: PhpParser\Node\Stmt|null, * 1: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null * } */ private function findAssignStmt(array $stmts, string $var_id, CodeLocation $original_location): array { $assign_stmt = null; $assign_exp = null; $assign_exp_found = false; $i = 0; while ($i < count($stmts) && !$assign_exp_found) { $stmt = $stmts[$i]; if ($stmt instanceof PhpParser\Node\Stmt\Expression) { $search_result = $this->findAssignExp($stmt->expr, $var_id, $original_location->raw_file_start); [$target_exp, $levels_taken] = $search_result; if ($target_exp !== null) { $assign_exp_found = true; $assign_exp = $target_exp; $assign_stmt = $levels_taken === 1 ? $stmt : null; } } elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } foreach ($stmt->catches as $catch_stmt) { $search_result = $this->findAssignStmt($catch_stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } } elseif ($stmt instanceof PhpParser\Node\Stmt\Do_ || $stmt instanceof PhpParser\Node\Stmt\While_ ) { $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) { $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } elseif ($stmt instanceof PhpParser\Node\Stmt\For_) { $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } elseif ($stmt instanceof PhpParser\Node\Stmt\If_) { $search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } foreach ($stmt->elseifs as $elseif_stmt) { $search_result = $this->findAssignStmt($elseif_stmt->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } if ($stmt->else) { $search_result = $this->findAssignStmt($stmt->else->stmts, $var_id, $original_location); if ($search_result[0] && $search_result[1]) { return $search_result; } } } $i++; } return [$assign_stmt, $assign_exp]; } /** * @return array{ * 0: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null, * 1: int * } */ private function findAssignExp( PhpParser\Node\Expr $current_node, string $var_id, int $var_start_loc, int $search_level = 1 ): array { if ($current_node instanceof PhpParser\Node\Expr\Assign || $current_node instanceof PhpParser\Node\Expr\AssignOp || $current_node instanceof PhpParser\Node\Expr\AssignRef ) { $var = $current_node->var; if ($var instanceof PhpParser\Node\Expr\Variable && $var->name === substr($var_id, 1) && $var->getStartFilePos() === $var_start_loc ) { return [$current_node, $search_level]; } $rhs_exp = $current_node->expr; $rhs_search_result = $this->findAssignExp($rhs_exp, $var_id, $var_start_loc, $search_level + 1); return [$rhs_search_result[0], $rhs_search_result[1]]; } return [null, $search_level]; } public function checkIfVarRemoved(string $var_id, CodeLocation $var_loc): bool { return array_key_exists($var_id, $this->removed_unref_vars) && $this->removed_unref_vars[$var_id] === $var_loc; } }