src/Phan/Debug.php
<?php
declare(strict_types=1);
namespace Phan;
use ast;
use ast\flags;
use ast\Node;
use LogicException;
use Phan\Analysis\BlockExitStatusChecker;
use Phan\AST\Parser;
use Phan\AST\TolerantASTConverter\Shim;
// Provides AST_ARROW_FUNC and other new node kinds that aren't in php-ast 1.0.1
Shim::load();
/**
* Debug utilities
*
* Mostly utilities for printing representations of AST nodes.
* Also see `Debug/`
*
* @phan-file-suppress PhanPluginRemoveDebugAny
*/
class Debug
{
// option for self::astDump
public const AST_DUMP_LINENOS = 1;
/**
* Print a lil' something to the console to
* see if a thing is called
*
* @suppress PhanUnreferencedPublicMethod
*/
public static function mark(): void
{
print "mark\n";
}
/**
* Print an AST node
*
* @param string|int|float|Node|null $node
* An AST node
* @suppress PhanUnreferencedPublicMethod
*/
public static function printNode($node): void
{
print self::nodeToString($node);
}
/**
* Print the name of a node to the terminal
*
* @suppress PhanUnreferencedPublicMethod
* @param Node|string|null $node
* @param int $indent
*/
public static function printNodeName($node, int $indent = 0): void
{
print \str_repeat("\t", $indent);
print self::nodeName($node);
print "\n";
}
/**
* Print $message with the given indent level
* @suppress PhanUnreferencedPublicMethod
*/
public static function print(string $message, int $indent = 0): void
{
print \str_repeat("\t", $indent);
print $message . "\n";
}
/**
* Return the name of a node
*
* @param Node|string|null $node
* @return string The name of the node
*/
public static function nodeName($node): string
{
if (\is_string($node)) {
return "string";
}
if (!$node) {
return 'null';
}
$kind = $node->kind;
if (\is_string($kind)) {
// For placeholders created by tolerant-php-parser-to-php-ast
return "string ($kind)";
}
try {
return Parser::getKindName($kind);
} catch (LogicException $_) {
return "UNKNOWN_KIND($kind)";
}
}
/**
* Convert an AST node to a compact string representation of that node.
*
* @param string|int|float|Node|null $node
* An AST node
*
* @param int|float|string|null $name
* The name of the node (if this node has a parent)
*
* @param int $indent
* The indentation level for the string
*
* @return string
* A string representation of an AST node
*
* @phan-side-effect-free
*/
public static function nodeToString(
$node,
$name = null,
int $indent = 0
): string {
$string = \str_repeat("\t", $indent);
if ($name !== null) {
$string .= "$name => ";
}
if (\is_string($node)) {
return $string . $node . "\n";
}
if ($node === null) {
return $string . 'null' . "\n";
}
if (!\is_object($node)) {
return $string . (\is_array($node) ? \json_encode($node) : $node) . "\n";
}
$kind = $node->kind;
$string .= self::nodeName($node);
$string .= ' ['
. (\is_int($kind) ? self::astFlagDescription($node->flags ?? 0, $kind) : 'unknown')
. ']';
if (isset($node->lineno)) {
$string .= ' #' . $node->lineno;
}
$end_lineno = $node->endLineno ?? null;
if (!\is_null($end_lineno)) {
$string .= ':' . $end_lineno;
}
$string .= "\n";
foreach ($node->children as $name => $child_node) {
if (\is_string($name) && \strncmp($name, 'phan', 4) === 0) {
// Dynamic property added by Phan
continue;
}
$string .= self::nodeToString(
$child_node,
$name,
$indent + 1
);
}
return $string;
}
/**
* Computes a string representation of AST node flags such as
* 'ASSIGN_DIV|TYPE_ARRAY'
* @see self::formatFlags() for a similar function also printing the integer flag value.
*/
public static function astFlagDescription(int $flags, int $kind): string
{
[$exclusive, $combinable] = self::getFlagInfo();
$flag_names = [];
if (isset($exclusive[$kind])) {
$flag_info = $exclusive[$kind];
if (isset($flag_info[$flags])) {
$flag_names[] = $flag_info[$flags];
}
} elseif (isset($combinable[$kind])) {
$flag_info = $combinable[$kind];
foreach ($flag_info as $flag => $name) {
if ($flags & $flag) {
$flag_names[] = $name;
}
}
}
return \implode('|', $flag_names);
}
/**
* @return string
* Get a string representation of AST node flags such as
* 'ASSIGN_DIV (26)'
* Source: https://github.com/nikic/php-ast/blob/master/util.php
*/
public static function formatFlags(int $kind, int $flags): string
{
[$exclusive, $combinable] = self::getFlagInfo();
if (isset($exclusive[$kind])) {
$flag_info = $exclusive[$kind];
if (isset($flag_info[$flags])) {
return "{$flag_info[$flags]} ($flags)";
}
} elseif (isset($combinable[$kind])) {
$flag_info = $combinable[$kind];
$names = [];
foreach ($flag_info as $flag => $name) {
if ($flags & $flag) {
$names[] = $name;
}
}
if (\count($names) > 0) {
return \implode(" | ", $names) . " ($flags)";
}
}
return (string) $flags;
}
/**
* @return void
* Pretty-printer for debug_backtrace
*
* @suppress PhanUnreferencedPublicMethod
*/
public static function backtrace(int $levels = 0): void
{
$bt = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, $levels + 1);
foreach ($bt as $level => $context) {
if (!$level) {
continue;
}
$file = $context['file'] ?? 'unknown';
$line = $context['line'] ?? 1;
$class = $context['class'] ?? 'global';
$function = $context['function'] ?? '';
echo "#" . ($level - 1) . " $file:$line $class ";
if (isset($context['type'])) {
echo $context['class'] . $context['type'];
}
echo $function;
echo "\n";
}
}
/**
* Dumps abstract syntax tree
* Source: https://github.com/nikic/php-ast/blob/master/util.php
* @param Node|string|int|float|null $ast
* @param int $options (self::AST_DUMP_*)
*/
public static function astDump($ast, int $options = 0): string
{
if ($ast instanceof Node) {
$result = Parser::getKindName($ast->kind);
if ($options & self::AST_DUMP_LINENOS) {
$result .= " @ $ast->lineno";
$end_lineno = $ast->endLineno ?? null;
if (!\is_null($end_lineno)) {
$result .= "-$end_lineno";
}
}
if (ast\kind_uses_flags($ast->kind)) {
$flags_without_phan_additions = $ast->flags & ~BlockExitStatusChecker::STATUS_BITMASK;
if ($flags_without_phan_additions !== 0) {
$result .= "\n flags: " . self::formatFlags($ast->kind, $flags_without_phan_additions);
}
}
foreach ($ast->children as $i => $child) {
$result .= "\n $i: " . \str_replace("\n", "\n ", self::astDump($child, $options));
}
return $result;
} elseif ($ast === null) {
return 'null';
} elseif (\is_string($ast)) {
return "\"$ast\"";
} else {
return (string) $ast;
}
}
/**
* Source: https://github.com/nikic/php-ast/blob/master/util.php
*
* Returns the information necessary to map the node id to the flag id to the name.
*
* @return array{0:associative-array<int,array<int,string>>,1:associative-array<int,array<int,string>>}
* Returns [string[][] $exclusive, string[][] $combinable].
*/
private static function getFlagInfo(): array
{
// TODO: Use AST's built in flag info if available.
static $exclusive, $combinable;
// Write this in a way that lets Phan infer the value of $combinable at the end.
if ($exclusive === null) {
$function_modifiers = [
flags\MODIFIER_PUBLIC => 'MODIFIER_PUBLIC',
flags\MODIFIER_PROTECTED => 'MODIFIER_PROTECTED',
flags\MODIFIER_PRIVATE => 'MODIFIER_PRIVATE',
flags\MODIFIER_STATIC => 'MODIFIER_STATIC',
flags\MODIFIER_ABSTRACT => 'MODIFIER_ABSTRACT',
flags\MODIFIER_FINAL => 'MODIFIER_FINAL',
flags\FUNC_RETURNS_REF => 'FUNC_RETURNS_REF',
flags\FUNC_GENERATOR => 'FUNC_GENERATOR',
];
$property_modifiers = [
flags\MODIFIER_PUBLIC => 'MODIFIER_PUBLIC',
flags\MODIFIER_PROTECTED => 'MODIFIER_PROTECTED',
flags\MODIFIER_PRIVATE => 'MODIFIER_PRIVATE',
flags\MODIFIER_STATIC => 'MODIFIER_STATIC',
flags\MODIFIER_ABSTRACT => 'MODIFIER_ABSTRACT',
flags\MODIFIER_FINAL => 'MODIFIER_FINAL',
];
$types = [
flags\TYPE_NULL => 'TYPE_NULL',
flags\TYPE_FALSE => 'TYPE_FALSE',
flags\TYPE_BOOL => 'TYPE_BOOL',
flags\TYPE_LONG => 'TYPE_LONG',
flags\TYPE_DOUBLE => 'TYPE_DOUBLE',
flags\TYPE_STRING => 'TYPE_STRING',
flags\TYPE_ARRAY => 'TYPE_ARRAY',
flags\TYPE_OBJECT => 'TYPE_OBJECT',
flags\TYPE_CALLABLE => 'TYPE_CALLABLE',
flags\TYPE_VOID => 'TYPE_VOID',
flags\TYPE_ITERABLE => 'TYPE_ITERABLE',
flags\TYPE_STATIC => 'TYPE_STATIC',
];
$use_types = [
flags\USE_NORMAL => 'USE_NORMAL',
flags\USE_FUNCTION => 'USE_FUNCTION',
flags\USE_CONST => 'USE_CONST',
];
$shared_binary_ops = [
flags\BINARY_BITWISE_OR => 'BINARY_BITWISE_OR',
flags\BINARY_BITWISE_AND => 'BINARY_BITWISE_AND',
flags\BINARY_BITWISE_XOR => 'BINARY_BITWISE_XOR',
flags\BINARY_CONCAT => 'BINARY_CONCAT',
flags\BINARY_ADD => 'BINARY_ADD',
flags\BINARY_SUB => 'BINARY_SUB',
flags\BINARY_MUL => 'BINARY_MUL',
flags\BINARY_DIV => 'BINARY_DIV',
flags\BINARY_MOD => 'BINARY_MOD',
flags\BINARY_POW => 'BINARY_POW',
flags\BINARY_SHIFT_LEFT => 'BINARY_SHIFT_LEFT',
flags\BINARY_SHIFT_RIGHT => 'BINARY_SHIFT_RIGHT',
flags\BINARY_COALESCE => 'BINARY_COALESCE',
];
$exclusive = [
ast\AST_NAME => [
flags\NAME_FQ => 'NAME_FQ',
flags\NAME_NOT_FQ => 'NAME_NOT_FQ',
flags\NAME_RELATIVE => 'NAME_RELATIVE',
],
ast\AST_CLASS => [
flags\CLASS_ABSTRACT => 'CLASS_ABSTRACT',
flags\CLASS_FINAL => 'CLASS_FINAL',
flags\CLASS_TRAIT => 'CLASS_TRAIT',
flags\CLASS_INTERFACE => 'CLASS_INTERFACE',
flags\CLASS_ANONYMOUS => 'CLASS_ANONYMOUS',
],
ast\AST_TYPE => $types,
ast\AST_CAST => $types,
ast\AST_UNARY_OP => [
flags\UNARY_BOOL_NOT => 'UNARY_BOOL_NOT',
flags\UNARY_BITWISE_NOT => 'UNARY_BITWISE_NOT',
flags\UNARY_MINUS => 'UNARY_MINUS',
flags\UNARY_PLUS => 'UNARY_PLUS',
flags\UNARY_SILENCE => 'UNARY_SILENCE',
],
ast\AST_BINARY_OP => $shared_binary_ops + [
flags\BINARY_BOOL_AND => 'BINARY_BOOL_AND',
flags\BINARY_BOOL_OR => 'BINARY_BOOL_OR',
flags\BINARY_BOOL_XOR => 'BINARY_BOOL_XOR',
flags\BINARY_IS_IDENTICAL => 'BINARY_IS_IDENTICAL',
flags\BINARY_IS_NOT_IDENTICAL => 'BINARY_IS_NOT_IDENTICAL',
flags\BINARY_IS_EQUAL => 'BINARY_IS_EQUAL',
flags\BINARY_IS_NOT_EQUAL => 'BINARY_IS_NOT_EQUAL',
flags\BINARY_IS_SMALLER => 'BINARY_IS_SMALLER',
flags\BINARY_IS_SMALLER_OR_EQUAL => 'BINARY_IS_SMALLER_OR_EQUAL',
flags\BINARY_IS_GREATER => 'BINARY_IS_GREATER',
flags\BINARY_IS_GREATER_OR_EQUAL => 'BINARY_IS_GREATER_OR_EQUAL',
flags\BINARY_SPACESHIP => 'BINARY_SPACESHIP',
],
ast\AST_ASSIGN_OP => $shared_binary_ops,
ast\AST_MAGIC_CONST => [
flags\MAGIC_LINE => 'MAGIC_LINE',
flags\MAGIC_FILE => 'MAGIC_FILE',
flags\MAGIC_DIR => 'MAGIC_DIR',
flags\MAGIC_NAMESPACE => 'MAGIC_NAMESPACE',
flags\MAGIC_FUNCTION => 'MAGIC_FUNCTION',
flags\MAGIC_METHOD => 'MAGIC_METHOD',
flags\MAGIC_CLASS => 'MAGIC_CLASS',
flags\MAGIC_TRAIT => 'MAGIC_TRAIT',
],
ast\AST_USE => $use_types,
ast\AST_GROUP_USE => $use_types,
ast\AST_USE_ELEM => $use_types,
ast\AST_INCLUDE_OR_EVAL => [
flags\EXEC_EVAL => 'EXEC_EVAL',
flags\EXEC_INCLUDE => 'EXEC_INCLUDE',
flags\EXEC_INCLUDE_ONCE => 'EXEC_INCLUDE_ONCE',
flags\EXEC_REQUIRE => 'EXEC_REQUIRE',
flags\EXEC_REQUIRE_ONCE => 'EXEC_REQUIRE_ONCE',
],
ast\AST_ARRAY => [
flags\ARRAY_SYNTAX_LIST => 'ARRAY_SYNTAX_LIST',
flags\ARRAY_SYNTAX_LONG => 'ARRAY_SYNTAX_LONG',
flags\ARRAY_SYNTAX_SHORT => 'ARRAY_SYNTAX_SHORT',
],
ast\AST_ARRAY_ELEM => [
flags\ARRAY_ELEM_REF => 'ARRAY_ELEM_REF',
],
ast\AST_CLOSURE_VAR => [
flags\CLOSURE_USE_REF => 'CLOSURE_USE_REF',
],
];
$combinable = [
ast\AST_METHOD => $function_modifiers,
ast\AST_FUNC_DECL => $function_modifiers,
ast\AST_CLOSURE => $function_modifiers,
ast\AST_ARROW_FUNC => $function_modifiers,
ast\AST_CLASS_CONST_DECL => [
flags\MODIFIER_PUBLIC => 'MODIFIER_PUBLIC',
flags\MODIFIER_PROTECTED => 'MODIFIER_PROTECTED',
flags\MODIFIER_PRIVATE => 'MODIFIER_PRIVATE',
],
ast\AST_PROP_GROUP => $property_modifiers,
ast\AST_TRAIT_ALIAS => $property_modifiers,
ast\AST_DIM => [
flags\DIM_ALTERNATIVE_SYNTAX => 'DIM_ALTERNATIVE_SYNTAX',
],
ast\AST_CONDITIONAL => [
flags\PARENTHESIZED_CONDITIONAL => 'PARENTHESIZED_CONDITIONAL',
],
ast\AST_PARAM => [
flags\PARAM_REF => 'PARAM_REF',
flags\PARAM_VARIADIC => 'PARAM_VARIADIC',
],
];
}
return [$exclusive, $combinable];
}
/**
* Print a message with the file and line.
* @suppress PhanUnreferencedPublicMethod added for debugging
*/
public static function debugLog(string $message): void
{
$frame = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)[0];
\fprintf(\STDERR, "%s:%d %s\n", $frame['file'] ?? 'unknown', $frame['line'] ?? 0, $message);
}
}