summaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
authorMáté Kocsis <kocsismate@woohoolabs.com>2020-10-13 20:03:00 +0200
committerMáté Kocsis <kocsismate@woohoolabs.com>2020-10-16 10:56:33 +0200
commitd6264b09665c20ee73d7a1017db0730f9a7c7973 (patch)
treebb465d70db16df73ce28bcc1fadab1a57dd92c02 /build
parent3841ea338ef2508d467efef9ec45d03162ba695d (diff)
downloadphp-git-d6264b09665c20ee73d7a1017db0730f9a7c7973.tar.gz
Verify parameter names of function aliases
Closes GH-6335
Diffstat (limited to 'build')
-rwxr-xr-xbuild/gen_stub.php211
1 files changed, 185 insertions, 26 deletions
diff --git a/build/gen_stub.php b/build/gen_stub.php
index e0dd8a2e24..8e7d915452 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -12,7 +12,12 @@ use PhpParser\PrettyPrinterAbstract;
error_reporting(E_ALL);
-function processDirectory(string $dir, Context $context) {
+/**
+ * @return FileInfo[]
+ */
+function processDirectory(string $dir, Context $context): array {
+ $fileInfos = [];
+
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir),
RecursiveIteratorIterator::LEAVES_ONLY
@@ -20,12 +25,17 @@ function processDirectory(string $dir, Context $context) {
foreach ($it as $file) {
$pathName = $file->getPathName();
if (preg_match('/\.stub\.php$/', $pathName)) {
- processStubFile($pathName, $context);
+ $fileInfo = processStubFile($pathName, $context);
+ if ($fileInfo) {
+ $fileInfos[] = $fileInfo;
+ }
}
}
+
+ return $fileInfos;
}
-function processStubFile(string $stubFile, Context $context) {
+function processStubFile(string $stubFile, Context $context): ?FileInfo {
try {
if (!file_exists($stubFile)) {
throw new Exception("File $stubFile does not exist");
@@ -37,15 +47,15 @@ function processStubFile(string $stubFile, Context $context) {
$stubCode = file_get_contents($stubFile);
$stubHash = computeStubHash($stubCode);
$oldStubHash = extractStubHash($arginfoFile);
- if ($stubHash === $oldStubHash && $context->forceRegeneration === false) {
+ if ($stubHash === $oldStubHash && !$context->forceParse) {
/* Stub file did not change, do not regenerate. */
- return;
+ return null;
}
initPhpParser();
$fileInfo = parseStubFile($stubCode);
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
- if (file_put_contents($arginfoFile, $arginfoCode)) {
+ if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($arginfoFile, $arginfoCode)) {
echo "Saved $arginfoFile\n";
}
@@ -54,20 +64,12 @@ function processStubFile(string $stubFile, Context $context) {
$funcInfo->discardInfoForOldPhpVersions();
}
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
- if (file_put_contents($legacyFile, $arginfoCode)) {
+ if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($legacyFile, $arginfoCode)) {
echo "Saved $legacyFile\n";
}
}
- // Collect parameter name statistics.
- foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
- foreach ($funcInfo->args as $argInfo) {
- if (!isset($context->parameterStats[$argInfo->name])) {
- $context->parameterStats[$argInfo->name] = 0;
- }
- $context->parameterStats[$argInfo->name]++;
- }
- }
+ return $fileInfo;
} catch (Exception $e) {
echo "In $stubFile:\n{$e->getMessage()}\n";
exit(1);
@@ -93,9 +95,9 @@ function extractStubHash(string $arginfoFile): ?string {
class Context {
/** @var bool */
+ public $forceParse = false;
+ /** @var bool */
public $forceRegeneration = false;
- /** @var array */
- public $parameterStats = [];
}
class SimpleType {
@@ -358,6 +360,8 @@ interface FunctionOrMethodName {
public function getArgInfoName(): string;
public function __toString(): string;
public function isMagicMethod(): bool;
+ public function isMethod(): bool;
+ public function isConstructor(): bool;
}
class FunctionName implements FunctionOrMethodName {
@@ -402,6 +406,14 @@ class FunctionName implements FunctionOrMethodName {
public function isMagicMethod(): bool {
return false;
}
+
+ public function isMethod(): bool {
+ return false;
+ }
+
+ public function isConstructor(): bool {
+ return false;
+ }
}
class MethodName implements FunctionOrMethodName {
@@ -434,6 +446,14 @@ class MethodName implements FunctionOrMethodName {
public function isMagicMethod(): bool {
return strpos($this->methodName, '__') === 0;
}
+
+ public function isMethod(): bool {
+ return true;
+ }
+
+ public function isConstructor(): bool {
+ return $this->methodName === "__construct";
+ }
}
class ReturnInfo {
@@ -457,6 +477,8 @@ class FuncInfo {
/** @var FunctionOrMethodName */
public $name;
/** @var int */
+ public $classFlags;
+ /** @var int */
public $flags;
/** @var string|null */
public $aliasType;
@@ -464,6 +486,8 @@ class FuncInfo {
public $alias;
/** @var bool */
public $isDeprecated;
+ /** @var bool */
+ public $verify;
/** @var ArgInfo[] */
public $args;
/** @var ReturnInfo */
@@ -475,26 +499,45 @@ class FuncInfo {
public function __construct(
FunctionOrMethodName $name,
+ int $classFlags,
int $flags,
?string $aliasType,
?FunctionOrMethodName $alias,
bool $isDeprecated,
+ bool $verify,
array $args,
ReturnInfo $return,
int $numRequiredArgs,
?string $cond
) {
$this->name = $name;
+ $this->classFlags = $classFlags;
$this->flags = $flags;
$this->aliasType = $aliasType;
$this->alias = $alias;
$this->isDeprecated = $isDeprecated;
+ $this->verify = $verify;
$this->args = $args;
$this->return = $return;
$this->numRequiredArgs = $numRequiredArgs;
$this->cond = $cond;
}
+ public function isMethod(): bool
+ {
+ return $this->name->isMethod();
+ }
+
+ public function isFinalMethod(): bool
+ {
+ return ($this->flags & Class_::MODIFIER_FINAL) || ($this->classFlags & Class_::MODIFIER_FINAL);
+ }
+
+ public function isInstanceMethod(): bool
+ {
+ return !($this->flags & Class_::MODIFIER_STATIC) && $this->isMethod() && !$this->name->isConstructor();
+ }
+
public function equalsApartFromName(FuncInfo $other): bool {
if (count($this->args) !== count($other->args)) {
return false;
@@ -732,6 +775,7 @@ function parseDocComment(DocComment $comment): array {
function parseFunctionLike(
PrettyPrinterAbstract $prettyPrinter,
FunctionOrMethodName $name,
+ int $classFlags,
int $flags,
Node\FunctionLike $func,
?string $cond
@@ -741,6 +785,7 @@ function parseFunctionLike(
$aliasType = null;
$alias = null;
$isDeprecated = false;
+ $verify = true;
$haveDocReturnType = false;
$docParamTypes = [];
@@ -763,6 +808,8 @@ function parseFunctionLike(
}
} else if ($tag->name === 'deprecated') {
$isDeprecated = true;
+ } else if ($tag->name === 'no-verify') {
+ $verify = false;
} else if ($tag->name === 'return') {
$haveDocReturnType = true;
} else if ($tag->name === 'param') {
@@ -843,10 +890,12 @@ function parseFunctionLike(
return new FuncInfo(
$name,
+ $classFlags,
$flags,
$aliasType,
$alias,
$isDeprecated,
+ $verify,
$args,
$return,
$numRequiredArgs,
@@ -917,6 +966,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$prettyPrinter,
new FunctionName($stmt->namespacedName),
0,
+ 0,
$stmt,
$cond
);
@@ -936,6 +986,11 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
throw new Exception("Not implemented {$classStmt->getType()}");
}
+ $classFlags = 0;
+ if ($stmt instanceof Class_) {
+ $classFlags = $stmt->flags;
+ }
+
$flags = $classStmt->flags;
if ($stmt instanceof Stmt\Interface_) {
$flags |= Class_::MODIFIER_ABSTRACT;
@@ -948,6 +1003,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$methodInfos[] = parseFunctionLike(
$prettyPrinter,
new MethodName($className, $classStmt->name->toString()),
+ $classFlags,
$flags,
$classStmt,
$cond
@@ -1262,29 +1318,132 @@ function initPhpParser() {
}
$optind = null;
-$options = getopt("fh", ["force-regeneration", "parameter-stats", "help"], $optind);
+$options = getopt("fh", ["force-regeneration", "parameter-stats", "help", "verify"], $optind);
$context = new Context;
$printParameterStats = isset($options["parameter-stats"]);
-$context->forceRegeneration =
- isset($options["f"]) || isset($options["force-regeneration"]) || $printParameterStats;
+$verify = isset($options["verify"]);
+$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
+$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify;
if (isset($options["h"]) || isset($options["help"])) {
- die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ -h | --help ] [ name.stub.php | directory ]\n\n");
+ die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ --verify ] [ -h | --help ] [ name.stub.php | directory ]\n\n");
}
+$fileInfos = [];
$location = $argv[$optind] ?? ".";
if (is_file($location)) {
// Generate single file.
- processStubFile($location, $context);
+ $fileInfo = processStubFile($location, $context);
+ if ($fileInfo) {
+ $fileInfos[] = $fileInfo;
+ }
} else if (is_dir($location)) {
- processDirectory($location, $context);
+ $fileInfos = processDirectory($location, $context);
} else {
echo "$location is neither a file nor a directory.\n";
exit(1);
}
if ($printParameterStats) {
- arsort($context->parameterStats);
- echo json_encode($context->parameterStats, JSON_PRETTY_PRINT), "\n";
+ $parameterStats = [];
+
+ foreach ($fileInfos as $fileInfo) {
+ foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
+ foreach ($funcInfo->args as $argInfo) {
+ if (!isset($context->parameterStats[$argInfo->name])) {
+ $parameterStats[$argInfo->name] = 0;
+ }
+ $parameterStats[$argInfo->name]++;
+ }
+ }
+ }
+
+ arsort($parameterStats);
+ echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n";
+}
+
+if ($verify) {
+ $errors = [];
+ $funcMap = [];
+ $aliases = [];
+
+ foreach ($fileInfos as $fileInfo) {
+ foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
+ /** @var FuncInfo $funcInfo */
+ $funcMap[$funcInfo->name->__toString()] = $funcInfo;
+
+ if ($funcInfo->aliasType === "alias") {
+ $aliases[] = $funcInfo;
+ }
+ }
+ }
+
+ foreach ($aliases as $aliasFunc) {
+ if (!isset($funcMap[$aliasFunc->alias->__toString()])) {
+ $errors[] = "Aliased function {$aliasFunc->alias}() cannot be found";
+ continue;
+ }
+
+ if (!$aliasFunc->verify) {
+ continue;
+ }
+
+ $aliasedFunc = $funcMap[$aliasFunc->alias->__toString()];
+ $aliasedArgs = $aliasedFunc->args;
+ $aliasArgs = $aliasFunc->args;
+
+ if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) {
+ if ($aliasFunc->isInstanceMethod()) {
+ $aliasedArgs = array_slice($aliasedArgs, 1);
+ }
+
+ if ($aliasedFunc->isInstanceMethod()) {
+ $aliasArgs = array_slice($aliasArgs, 1);
+ }
+ }
+
+ array_map(
+ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) {
+ if ($aliasArg === null) {
+ assert($aliasedArg !== null);
+ $errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing";
+ return null;
+ }
+
+ if ($aliasedArg === null) {
+ assert($aliasArg !== null);
+ $errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing";
+ return null;
+ }
+
+ if ($aliasArg->name !== $aliasedArg->name) {
+ $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name";
+ return null;
+ }
+
+ if ($aliasArg->type != $aliasedArg->type) {
+ $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type";
+ }
+
+ if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) {
+ $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value";
+ }
+ },
+ $aliasArgs, $aliasedArgs
+ );
+
+ if ((!$aliasedFunc->isMethod() || $aliasedFunc->isFinalMethod()) &&
+ (!$aliasFunc->isMethod() || $aliasFunc->isFinalMethod()) &&
+ $aliasFunc->return != $aliasedFunc->return
+ ) {
+ $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
+ }
+ }
+
+ echo implode("\n", $errors);
+ if (!empty($errors)) {
+ echo "\n";
+ exit(1);
+ }
}