Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891628
phutil_analyzer.php
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
11 KB
Referenced Files
None
Subscribers
None
phutil_analyzer.php
View Options
#!/usr/bin/env php
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$builtin_classes
=
get_declared_classes
(
)
;
$builtin_interfaces
=
get_declared_interfaces
(
)
;
$builtin_functions
=
get_defined_functions
(
)
;
$builtin_functions
=
$builtin_functions
[
'internal'
]
;
$builtin
=
array
(
'class'
=>
array_fill_keys
(
$builtin_classes
,
true
)
,
'function'
=>
array_fill_keys
(
$builtin_functions
,
true
)
+
array
(
'empty'
=>
true
,
'isset'
=>
true
,
'echo'
=>
true
,
'print'
=>
true
,
'exit'
=>
true
,
'die'
=>
true
,
'phutil_module_exists'
=>
true
,
)
,
'interface'
=>
array_fill_keys
(
$builtin_interfaces
,
true
)
,
)
;
require_once
dirname
(
__FILE__
)
.
'/__init_script__.php'
;
if
(
$argc
!=
2
)
{
$self
=
basename
(
$argv
[
0
]
)
;
echo
"usage: {$self} <module>\n"
;
exit
(
1
)
;
}
phutil_require_module
(
'phutil'
,
'filesystem'
)
;
$dir
=
Filesystem
::
resolvePath
(
$argv
[
1
]
)
;
phutil_require_module
(
'phutil'
,
'parser/xhpast/bin'
)
;
phutil_require_module
(
'phutil'
,
'parser/xhpast/api/tree'
)
;
phutil_require_module
(
'arcanist'
,
'lint/linter/phutilmodule'
)
;
phutil_require_module
(
'arcanist'
,
'lint/message'
)
;
phutil_require_module
(
'arcanist'
,
'staticanalysis/parsers/phutilmodule'
)
;
$data
=
array
(
)
;
$futures
=
array
(
)
;
foreach
(
Filesystem
::
listDirectory
(
$dir
,
$hidden_files
=
false
)
as
$file
)
{
if
(
!
preg_match
(
'/.php$/'
,
$file
)
)
{
continue
;
}
$data
[
$file
]
=
Filesystem
::
readFile
(
$dir
.
'/'
.
$file
)
;
$futures
[
$file
]
=
xhpast_get_parser_future
(
$data
[
$file
]
)
;
}
$requirements
=
new
PhutilModuleRequirements
(
)
;
$requirements
->
addBuiltins
(
$builtin
)
;
$has_init
=
false
;
$has_files
=
false
;
foreach
(
Futures
(
$futures
)
as
$file
=>
$future
)
{
try
{
$tree
=
XHPASTTree
::
newFromDataAndResolvedExecFuture
(
$data
[
$file
]
,
$future
->
resolve
(
)
)
;
}
catch
(
XHPASTSyntaxErrorException
$ex
)
{
echo
"Syntax Error! In '{$file}': "
.
$ex
->
getMessage
(
)
.
"\n"
;
exit
(
1
)
;
}
$root
=
$tree
->
getRootNode
(
)
;
$requirements
->
setCurrentFile
(
$file
)
;
if
(
$file
==
'__init__.php'
)
{
$has_init
=
true
;
$calls
=
$root
->
selectDescendantsOfType
(
'n_FUNCTION_CALL'
)
;
foreach
(
$calls
as
$call
)
{
$name
=
$call
->
getChildByIndex
(
0
)
;
$call_name
=
$name
->
getConcreteString
(
)
;
if
(
$call_name
==
'phutil_require_source'
)
{
$params
=
$call
->
getChildByIndex
(
1
)
->
getChildren
(
)
;
if
(
count
(
$params
)
!==
1
)
{
$requirements
->
addLint
(
$call
,
$call
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"Call to phutil_require_source() must have exactly one argument."
)
;
continue
;
}
$param
=
reset
(
$params
)
;
$value
=
$param
->
getStringLiteralValue
(
)
;
if
(
$value
===
null
)
{
$requirements
->
addLint
(
$param
,
$param
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"phutil_require_source() parameter must be a string literal."
)
;
continue
;
}
$requirements
->
addSourceDependency
(
$name
,
$value
)
;
}
else
if
(
$call_name
==
'phutil_require_module'
)
{
analyze_require_module
(
$call
,
$requirements
)
;
}
}
}
else
{
$has_files
=
true
;
$requirements
->
addSourceDeclaration
(
basename
(
$file
)
)
;
// Function uses:
// - Explicit call
// TODO?: String literal in ReflectionFunction().
$calls
=
$root
->
selectDescendantsOfType
(
'n_FUNCTION_CALL'
)
;
foreach
(
$calls
as
$call
)
{
$name
=
$call
->
getChildByIndex
(
0
)
;
if
(
$name
->
getTypeName
(
)
==
'n_VARIABLE'
||
$name
->
getTypeName
(
)
==
'n_VARIABLE_VARIABLE'
)
{
$requirements
->
addLint
(
$name
,
$name
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_DYNAMIC
,
"Use of variable function calls prevents dependencies from being "
.
"checked statically. This module may have undetectable errors."
)
;
continue
;
}
if
(
$name
->
getTypeName
(
)
==
'n_CLASS_STATIC_ACCESS'
)
{
// We'll pick this up later.
continue
;
}
$call_name
=
$name
->
getConcreteString
(
)
;
if
(
$call_name
==
'phutil_require_module'
)
{
analyze_require_module
(
$call
,
$requirements
)
;
}
else
if
(
$call_name
==
'call_user_func'
||
$call_name
==
'call_user_func_array'
)
{
$params
=
$call
->
getChildByIndex
(
1
)
->
getChildren
(
)
;
if
(
count
(
$params
)
==
0
)
{
$requirements
->
addLint
(
$call
,
$call
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"Call to {$call_name}() must have at least one argument."
)
;
}
$symbol
=
array_shift
(
$params
)
;
$symbol_value
=
$symbol
->
getStringLiteralValue
(
)
;
if
(
$symbol_value
)
{
$requirements
->
addFunctionDependency
(
$symbol
,
$symbol_value
)
;
}
else
{
$requirements
->
addLint
(
$symbol
,
$symbol
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_DYNAMIC
,
"Use of variable arguments to {$call_name} prevents dependencies "
.
"from being checked statically. This module may have undetectable "
.
"errors."
)
;
}
}
else
{
$requirements
->
addFunctionDependency
(
$name
,
$name
->
getConcreteString
(
)
)
;
}
}
$functions
=
$root
->
selectDescendantsOfType
(
'n_FUNCTION_DECLARATION'
)
;
foreach
(
$functions
as
$function
)
{
$name
=
$function
->
getChildByIndex
(
2
)
;
$requirements
->
addFunctionDeclaration
(
$name
,
$name
->
getConcreteString
(
)
)
;
}
// Class uses:
// - new
// - extends (in class declaration)
// - Static method call
// - Static property access
// - Constant use
// TODO?: String literal in ReflectionClass().
// TODO?: String literal in array literal in call_user_func /
// call_user_func_array().
$classes
=
$root
->
selectDescendantsOfType
(
'n_CLASS_DECLARATION'
)
;
foreach
(
$classes
as
$class
)
{
$class_name
=
$class
->
getChildByIndex
(
1
)
;
$requirements
->
addClassDeclaration
(
$class_name
,
$class_name
->
getConcreteString
(
)
)
;
$extends
=
$class
->
getChildByIndex
(
2
)
;
foreach
(
$extends
->
selectDescendantsOfType
(
'n_CLASS_NAME'
)
as
$parent
)
{
$requirements
->
addClassDependency
(
$class_name
->
getConcreteString
(
)
,
$parent
,
$parent
->
getConcreteString
(
)
)
;
}
$implements
=
$class
->
getChildByIndex
(
3
)
;
$interfaces
=
$implements
->
selectDescendantsOfType
(
'n_CLASS_NAME'
)
;
foreach
(
$interfaces
as
$interface
)
{
$requirements
->
addInterfaceDependency
(
$class_name
->
getConcreteString
(
)
,
$interface
,
$interface
->
getConcreteString
(
)
)
;
}
}
if
(
count
(
$classes
)
>
1
)
{
foreach
(
$classes
as
$class
)
{
$class_name
=
$class
->
getChildByIndex
(
1
)
;
$class_string
=
$class_name
->
getConcreteString
(
)
;
$requirements
->
addLint
(
$class_name
,
$class_string
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_MULTIPLE_CLASSES
,
"This file declares more than one class. Declare only one class per "
.
"file."
)
;
break
;
}
}
else
if
(
count
(
$classes
)
==
1
)
{
foreach
(
$classes
as
$class
)
{
$class_name
=
$class
->
getChildByIndex
(
1
)
;
$class_string
=
$class_name
->
getConcreteString
(
)
;
if
(
$file
!=
$class_string
.
'.php'
)
{
$rename
=
$class_string
.
'.php'
;
$requirements
->
addLint
(
$class_name
,
$class_string
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_CLASS_FILENAME
,
"The name of this file differs from the name of the class it "
.
"declares. Rename the file to '{$rename}'."
)
;
}
break
;
}
}
$uses_of_new
=
$root
->
selectDescendantsOfType
(
'n_NEW'
)
;
foreach
(
$uses_of_new
as
$new_operator
)
{
$name
=
$new_operator
->
getChildByIndex
(
0
)
;
if
(
$name
->
getTypeName
(
)
==
'n_VARIABLE'
||
$name
->
getTypeName
(
)
==
'n_VARIABLE_VARIABLE'
)
{
$requirements
->
addLint
(
$name
,
$name
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_DYNAMIC
,
"Use of variable class instantiation prevents dependencies from "
.
"being checked statically. This module may have undetectable "
.
"errors."
)
;
continue
;
}
$requirements
->
addClassDependency
(
null
,
$name
,
$name
->
getConcreteString
(
)
)
;
}
$static_uses
=
$root
->
selectDescendantsOfType
(
'n_CLASS_STATIC_ACCESS'
)
;
foreach
(
$static_uses
as
$static_use
)
{
$name
=
$static_use
->
getChildByIndex
(
0
)
;
if
(
$name
->
getTypeName
(
)
!=
'n_CLASS_NAME'
)
{
echo
"WARNING UNLINTABLE\n"
;
continue
;
}
$name_concrete
=
$name
->
getConcreteString
(
)
;
$magic_names
=
array
(
'static'
=>
true
,
'parent'
=>
true
,
'self'
=>
true
,
)
;
if
(
isset
(
$magic_names
[
$name_concrete
]
)
)
{
continue
;
}
$requirements
->
addClassDependency
(
null
,
$name
,
$name_concrete
)
;
}
// Interface uses:
// - implements
// - extends (in interface declaration)
$interfaces
=
$root
->
selectDescendantsOfType
(
'n_INTERFACE_DECLARATION'
)
;
foreach
(
$interfaces
as
$interface
)
{
$interface_name
=
$interface
->
getChildByIndex
(
1
)
;
$requirements
->
addInterfaceDeclaration
(
$interface_name
,
$interface_name
->
getConcreteString
(
)
)
;
$extends
=
$interface
->
getChildByIndex
(
2
)
;
foreach
(
$extends
->
selectDescendantsOfType
(
'n_CLASS_NAME'
)
as
$parent
)
{
$requirements
->
addInterfaceDependency
(
$class_name
->
getConcreteString
(
)
,
$parent
,
$parent
->
getConcreteString
(
)
)
;
}
}
}
}
if
(
!
$has_init
&&
$has_files
)
{
$requirements
->
addRawLint
(
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_NO_INIT
,
"Create an __init__.php file in this module."
)
;
}
echo
json_encode
(
$requirements
->
toDictionary
(
)
)
;
function
analyze_require_module
(
XHPASTNode
$call
,
PhutilModuleRequirements
$requirements
)
{
$name
=
$call
->
getChildByIndex
(
0
)
;
$params
=
$call
->
getChildByIndex
(
1
)
->
getChildren
(
)
;
if
(
count
(
$params
)
!==
2
)
{
$requirements
->
addLint
(
$call
,
$call
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"Call to phutil_require_module() must have exactly two arguments."
)
;
return
;
}
$module_param
=
array_pop
(
$params
)
;
$library_param
=
array_pop
(
$params
)
;
$library_value
=
$library_param
->
getStringLiteralValue
(
)
;
if
(
$library_value
===
null
)
{
$requirements
->
addLint
(
$library_param
,
$library_param
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"phutil_require_module() parameters must be string literals."
)
;
return
;
}
$module_value
=
$module_param
->
getStringLiteralValue
(
)
;
if
(
$module_value
===
null
)
{
$requirements
->
addLint
(
$module_param
,
$module_param
->
getConcreteString
(
)
,
ArcanistPhutilModuleLinter
::
LINT_ANALYZER_SIGNATURE
,
"phutil_require_module() parameters must be string literals."
)
;
return
;
}
$requirements
->
addModuleDependency
(
$name
,
$library_value
.
':'
.
$module_value
)
;
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jan 19, 15:30 (3 w, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126006
Default Alt Text
phutil_analyzer.php (11 KB)
Attached To
Mode
rARC Arcanist
Attached
Detach File
Event Timeline
Log In to Comment