public function generateFileReport(XRef_IParsedFile $pf) { if ($pf->getFileType() != $this->supportedFileType) { return; } // collect all declared classes foreach ($pf->getClasses() as $pfc) { $c = $this->getOrCreate($pfc->name); $c->isInterface = $pfc->kind == T_INTERFACE; $definedAt = new XRef_FilePosition($pf, $pfc->nameIndex); $c->definedAt[] = $definedAt; // link from source file HTML page to report page "reportId/objectId" $this->xref->addSourceFileLink($definedAt, $this->reportId, $c->id); // extended classes/interfaces foreach ($pfc->extends as $ext_name) { $c->extends[] = $ext_name; $ext_class = $this->getOrCreate($ext_name); $ext_class->inheritedBy[] = $pfc->name; $extendsIndex = $pfc->extendsIndex[$ext_name]; $filePos = new XRef_FilePosition($pf, $extendsIndex[0], $extendsIndex[1]); $ext_class->usedAt[] = $filePos; // link from source file HTML page to report page "reportId/objectId" $this->xref->addSourceFileLink($filePos, $this->reportId, $ext_class->id); } // extended classes/interfaces foreach ($pfc->implements as $imp_name) { $c->implements[] = $imp_name; $imp_class = $this->getOrCreate($imp_name); $imp_class->inheritedBy[] = $pfc->name; $extendsIndex = $pfc->implementsIndex[$imp_name]; $filePos = new XRef_FilePosition($pf, $extendsIndex[0], $extendsIndex[1]); $imp_class->usedAt[] = $filePos; // link from source file HTML page to report page "reportId/objectId" $this->xref->addSourceFileLink($filePos, $this->reportId, $imp_class->id); } } // foreach declared class // collect all places where this class is instantiated or mentioned: $tokens = $pf->getTokens(); $tokens_count = count($tokens); for ($i = 0; $i < $tokens_count; ++$i) { $t = $tokens[$i]; // "new" <name> // "new" $className // "new" "(" <callable-name> ")" // AS3 // "new" "<" <int> ">" // AS3 if ($t->kind == T_NEW) { $t = $t->nextNS(); if ($t->kind != T_STRING) { continue; } // TODO: scan for namespaced name, e.g. new \Foo\Bar() $name = $pf->qualifyName($t->text, $t->index); $new = $this->getOrCreate($name); $filePos = new XRef_FilePosition($pf, $t->index); $new->instantiatedAt[] = $filePos; // link from source file HTML page to report page "reportId/objectId" $this->xref->addSourceFileLink($filePos, $this->reportId, $new->id); continue; } // T_NEW // // Class :: $foo // Class :: foo() // but not Class :: CONST if ($t->kind == T_DOUBLE_COLON) { $p = $t->prevNS(); if ($p->kind == T_STRING) { $n = $t->nextNS(); if ($n->kind == T_VARIABLE) { // ok, static field } elseif ($n->kind == T_STRING) { // method or constant $nn = $n->nextNS(); if ($nn->text != '(') { continue; } } elseif ($n->text == '$') { // rare case: Class::$$static_field_name continue; } else { throw new XRef_ParseException($n); } $className = $pf->qualifyName($p->text, $p->index); if ($pf->getFileType() == XRef::FILETYPE_PHP) { if ($className == 'self') { $class = $pf->getClassAt($p->index); if (!$class) { error_log("Reference to self:: class not inside a class method at " . $pf->getFileName() . ":{$t->lineNumber}"); continue; } $className = $class->name; } // TODO: super:: class resolution needs 2-pass parser } $c = $this->getOrCreate($className); $filePos = new XRef_FilePosition($pf, $p->index); $c->usedAt[] = $filePos; $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id); } else { error_log("Unexpected token {$t->text} at " . $pf->getFileName() . ":{$t->lineNumber}"); } } // $foo isinstanceof Bar // $foo isinstanceof $bar if ($t->kind == T_INSTANCEOF) { $n = $t->nextNS(); if ($n->kind == T_STRING) { $className = $pf->qualifyName($n->text, $n->index); $c = $this->getOrCreate($className); $filePos = new XRef_FilePosition($pf, $n->index); $c->usedAt[] = $filePos; $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id); } } // is_a($foo, "Bar") if ($t->kind == T_STRING && $t->text == "is_a") { $n = $t->nextNS(); if ($n->text != '(') { continue; } // TODO: add more checks below $n = $n->nextNS(); // var name. Actually, should skip any expression, e.g.: is_a($user->world, "World") $n = $n->nextNS(); // comma $n = $n->nextNS(); // class name literal? $className = $pf->qualifyName(preg_replace("#[\\'\"]#", '', $n->text), $n->index); $c = $this->getOrCreate($className); $filePos = new XRef_FilePosition($pf, $n->index); $c->usedAt[] = $filePos; $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id); } } // foreach $token }
/** * @param XRef_ParsedFile $pf * @param bool $is_library_file * @return * any data */ public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false) { /** array of arrays with info about called functions and methods */ $called_functions = array(); /** map: (function call info => true), to report any call once only */ $uniqs = array(); /** map of function names, checked for existence by function_exists() */ $checked_for_functions = array(); $tokens = $pf->getTokens(); for ($i = 0; $i < count($tokens); ++$i) { $t = $tokens[$i]; // $this->foo(); // Foo\Barr::foo(); if ($t->text == '(') { $p = $t->prevNS(); if ($p->kind != T_STRING) { continue; } // array with parts of the name (T_NS_SEPARATOR && T_STRING) $function_name_parts = array($p->text); $class_name = ''; $pp = $p->prevNS(); if ($pp->kind == T_NS_SEPARATOR) { // Foo\bar(); // \bar(); while ($pp->kind == T_NS_SEPARATOR || $pp->kind == T_STRING) { array_unshift($function_name_parts, $pp->text); $pp = $pp->prevNS(); } } elseif ($pp->kind == T_OBJECT_OPERATOR) { // $var->bar() // $this->bar(); $pp = $pp->prevNS(); if ($pp->kind == T_VARIABLE && $pp->text == '$this') { $class_name = 'self'; // will be resolved later } else { // TODO continue; } } elseif ($pp->kind == T_DOUBLE_COLON) { // Foo::bar(); // Foo\Bar::bar(); // self::foo(); // static::bar(); $pp = $pp->prevNS(); while ($pp->kind == T_NS_SEPARATOR || $pp->kind == T_STRING || $pp->kind == T_STATIC) { $class_name = $pp->text . $class_name; $pp = $pp->prevNS(); } } if ($pp->text == '&') { $pp = $pp->prevNS(); } if ($pp->kind == T_FUNCTION) { // skip function declarations continue; } $arguments = $pf->extractList($t->nextNS()); $num_of_arguments = count($arguments); $from_class = $pf->getClassAt($t->index); $from_class_name = $from_class ? $from_class->name : ''; if ($pp->kind == T_NEW) { // new Something(); // new ns\Another\Something(); // new \Foo(); $class_name = implode('', $function_name_parts); if ($class_name == 'static' || $class_name == 'self') { $class_name = $from_class_name; } elseif ($class_name == 'parent') { $class_name = $from_class && $from_class->extends ? $from_class->extends[0] : null; } else { $class_name = $pf->qualifyName($class_name, $t->index); } if ($class_name) { $uniq = "new##{$class_name}#{$num_of_arguments}"; if (!isset($uniqs[$uniq])) { $uniqs[$uniq] = true; $called_functions[] = array(self::F_CLASS_CONSTRUCTOR, null, $class_name, $t->lineNumber, $num_of_arguments); } } } elseif ($class_name) { // method call: // $this->foo(); // self::foo(); // Foo::bar(); // \bar\Baz::foo(); if ($class_name == 'static' || $class_name == 'self') { $class_name = $from_class_name; } elseif ($class_name == 'parent') { $class_name = $from_class && $from_class->extends ? $from_class->extends[0] : null; } else { $class_name = $pf->qualifyName($class_name, $t->index); } if ($class_name) { if (count($function_name_parts) > 1) { throw new Exception("{$class_name}::" . implode('', $function_name_parts)); } $function_name = $function_name_parts[0]; if ($function_name == '__construct') { $uniq = "new##{$class_name}#{$num_of_arguments}"; if (!isset($uniqs[$uniq])) { $uniqs[$uniq] = true; $called_functions[] = array(self::F_CLASS_CONSTRUCTOR, null, $class_name, $t->lineNumber, $num_of_arguments); } } else { $uniq = "{$function_name}##{$class_name}#{$num_of_arguments}"; if (!isset($uniqs[$uniq])) { $uniqs[$uniq] = true; $called_functions[] = array(self::F_CLASS_METHOD, $function_name, $class_name, $t->lineNumber, $num_of_arguments); } } } } else { // function call: qualified or unqualified // foo(); // foo\bar(); // \foo\bar\baz(); if (count($function_name_parts) > 1) { $function_name = $pf->qualifyName(implode('', $function_name_parts), $t->index); $uniq = "{$function_name}##{$num_of_arguments}"; if (!isset($uniqs[$uniq])) { $uniqs[$uniq] = true; $called_functions[] = array(self::F_FULLY_QUALIFIED_FUNC, $function_name, null, $t->lineNumber, $num_of_arguments); } } else { $function_name = $function_name_parts[0]; $namespace = $pf->getNamespaceAt($t->index); $namespace_name = $namespace ? $namespace->name : ''; $uniq = "{$function_name}##{$namespace_name}#{$num_of_arguments}"; if (!isset($uniqs[$uniq])) { $uniqs[$uniq] = true; $called_functions[] = array(self::F_NOT_QUALIFIED_FUNC, $function_name, $namespace_name, $t->lineNumber, $num_of_arguments, $from_class_name); } } if ($function_name == 'function_exists') { $n = $t->nextNS(); if ($n->kind == T_CONSTANT_ENCAPSED_STRING) { $checked_for_function_name = trim($n->text, '\'"'); $checked_for_functions[$checked_for_function_name] = true; } } } } } $file_slice = array("called" => $called_functions, "checked" => $checked_for_functions); return $file_slice; }
public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false) { $this->slice = array(); $this->already_seen = array(); $tokens = $pf->getTokens(); for ($i = 0; $i < count($tokens); ++$i) { $t = $tokens[$i]; // new Foo(); // new \Bar\Baz(); // new $class // new self() if ($t->kind == T_NEW) { $n = $t->nextNS(); $class_name = ''; while ($n->kind == T_NS_SEPARATOR || $n->kind == T_STRING) { $class_name = $class_name . $n->text; $n = $n->nextNS(); } if ($class_name) { $class_name = $pf->qualifyName($class_name, $t->index); if ($class_name == 'self' || $class_name == 'parent' || $class_name == 'static') { $class = $pf->getClassAt($t->index); if (!$class) { continue; } $class_name = $class->name; } $this->addUsedConstruct($class_name, 'method', '__construct', $t->lineNumber, $class_name, false, false); } continue; } // $this->foo(); // $this->bar; if ($t->kind == T_VARIABLE && $t->text == '$this') { $n = $t->nextNS(); if ($n->text == '->') { $n = $n->nextNS(); if ($n->kind == T_STRING) { $name = $n->text; $n = $n->nextNS(); $class = $pf->getClassAt($t->index); if (!$class) { continue; } $class_name = $class->name; $key = $n->text == '(' ? 'method' : 'property'; $this->addUsedConstruct($class_name, $key, $name, $t->lineNumber, $class_name, false, false); } } continue; } // Foo::bar(); // \Foo::bar(); // Foo\Bar::BAZ if ($t->kind == T_DOUBLE_COLON) { $class_name = ''; $p = $t->prevNS(); while ($p->kind == T_NS_SEPARATOR || $p->kind == T_STRING) { $class_name = $p->text . $class_name; $p = $p->prevNS(); } if ($class_name) { $class_name = $pf->qualifyName($class_name, $t->index); $check_parent_only = $class_name == 'parent'; $from_class = $pf->getClassAt($t->index); $from_class_name = $from_class ? $from_class->name : ''; if ($class_name == 'self' || $class_name == 'static' || $class_name == 'parent') { $check_parent_only = $class_name == 'parent'; $class_name = $from_class_name; $from_method = $pf->getMethodAt($t->index); $is_static_context = !$from_class || !$from_method || XRef::isStatic($from_method->attributes); } else { $is_static_context = true; } $n = $t->nextNS(); if ($n->kind == T_STRING) { $nn = $n->nextNS(); if ($nn->text == '(') { // Foo::bar() // self::bar() - this can be either static or instance access, depends on context $this->addUsedConstruct($class_name, 'method', $n->text, $t->lineNumber, $from_class_name, $is_static_context, $check_parent_only); } else { // Foo::BAR - constant $const_name = $n->text; $this->addUsedConstruct($class_name, 'constant', $n->text, $t->lineNumber, $from_class_name, true, $check_parent_only); } } elseif ($n->kind == T_VARIABLE) { // Foo::$bar $property_name = substr($n->text, 1); // skip '$' sign $this->addUsedConstruct($class_name, 'property', $property_name, $t->lineNumber, $from_class_name, true, $check_parent_only); } else { // e.g. self::$$keyName //error_log($n); } continue; } } } return $this->slice; }