Skip to content

Commit

Permalink
Add phpstan
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Aug 26, 2023
1 parent e6e2779 commit 4ab4c63
Show file tree
Hide file tree
Showing 16 changed files with 112 additions and 34 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"ulrichsg/getopt-php": "^4.0"
},
"require-dev": {
"phpunit/phpunit": "^9"
"phpunit/phpunit": "^9",
"phpstan/phpstan": "^1.10"
},
"suggest": {
"ext-pcntl": "Needed for timeout support"
Expand Down
26 changes: 26 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
parameters:
ignoreErrors:
-
message: "#^Method PhpFuzzer\\\\Fuzzer\\:\\:handleFuzzCommand\\(\\) has parameter \\$getOpt with no value type specified in iterable type GetOpt\\\\GetOpt\\.$#"
count: 1
path: src/Fuzzer.php

-
message: "#^Method PhpFuzzer\\\\Fuzzer\\:\\:handleMinimizeCrashCommand\\(\\) has parameter \\$getOpt with no value type specified in iterable type GetOpt\\\\GetOpt\\.$#"
count: 1
path: src/Fuzzer.php

-
message: "#^Method PhpFuzzer\\\\Fuzzer\\:\\:handleReportCoverage\\(\\) has parameter \\$getOpt with no value type specified in iterable type GetOpt\\\\GetOpt\\.$#"
count: 1
path: src/Fuzzer.php

-
message: "#^Method PhpFuzzer\\\\Fuzzer\\:\\:handleRunSingleCommand\\(\\) has parameter \\$getOpt with no value type specified in iterable type GetOpt\\\\GetOpt\\.$#"
count: 1
path: src/Fuzzer.php

-
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$stmts\\.$#"
count: 1
path: src/Instrumentation/Visitor.php
7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
includes:
- phpstan-baseline.neon

parameters:
level: 6
paths:
- src
3 changes: 3 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
class Config {
public \Closure $target;
public Dictionary $dictionary;
/** @var list<class-string<\Throwable>> */
public array $allowedExceptions = [\Exception::class];
public int $maxLen = PHP_INT_MAX;

Expand All @@ -28,6 +29,8 @@ public function setTarget(\Closure $target): void {
/**
* Set which exceptions are not considered as fuzzing failures.
* Defaults to just "Exception", considering all "Errors" failures.
*
* @param list<class-string<\Throwable>> $allowedExceptions
*/
public function setAllowedExceptions(array $allowedExceptions): void {
$this->allowedExceptions = $allowedExceptions;
Expand Down
17 changes: 8 additions & 9 deletions src/Corpus.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ final class Corpus {
private array $entriesByHash = [];
/** @var CorpusEntry[] Only used to get a random element. */
private array $entriesByIndex = [];
/** @var array<int, bool> */
private array $seenFeatures = [];

/** @var CorpusEntry[] $crashEntries */
private array $crashEntries = [];
/** @var array<int, bool> */
private array $seenCrashFeatures = [];

private int $totalLen = 0;
private int $maxLen = 0;

public function computeUniqueFeatures(CorpusEntry $entry) {
public function computeUniqueFeatures(CorpusEntry $entry): void {
$entry->uniqueFeatures = [];
foreach ($entry->features as $feature => $_) {
if (!isset($this->seenFeatures[$feature])) {
Expand Down Expand Up @@ -78,6 +78,9 @@ public function getMaxLen(): int {
return $this->maxLen;
}

/**
* @return array<int, bool>
*/
public function getSeenBlockMap(): array {
$blocks = [];
foreach ($this->seenFeatures as $feature => $_) {
Expand All @@ -96,10 +99,6 @@ public function addCrashEntry(CorpusEntry $entry): bool {
$this->seenCrashFeatures[$feature] = true;
}
}
if ($hasNewFeature) {
$this->crashEntries[] = $entry;
return true;
}
return false;
return $hasNewFeature;
}
}
}
7 changes: 6 additions & 1 deletion src/CorpusEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
final class CorpusEntry {
public string $input;
public string $hash;
/** @var array<int, bool> */
public array $features;
public ?string $crashInfo;
public ?string $path;
/** @var array<int, bool> */
public array $uniqueFeatures;

/**
* @param array<int, bool> $features
*/
public function __construct(string $input, array $features, ?string $crashInfo) {
$this->input = $input;
$this->hash = \md5($input);
Expand All @@ -33,4 +38,4 @@ public function storeAtPath(string $path): void {
$result = file_put_contents($this->path, $this->input);
assert($result === \strlen($this->input));
}
}
}
10 changes: 8 additions & 2 deletions src/CoverageRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ public function __construct(string $outDir) {
$this->outDir = $outDir;
}

/** @param FileInfo[] $fileInfos */
/**
* @param FileInfo[] $fileInfos
* @param array<int, bool> $seenBlocks
*/
public function render(array $fileInfos, array $seenBlocks): void {
@mkdir($this->outDir);

Expand Down Expand Up @@ -56,6 +59,9 @@ public function render(array $fileInfos, array $seenBlocks): void {
file_put_contents($this->outDir . '/index.html', $overview);
}

/**
* @param list<string> $strings
*/
private function getCommonPrefix(array $strings): string {
if (empty($strings)) {
return '';
Expand All @@ -68,4 +74,4 @@ private function getCommonPrefix(array $strings): string {
}
return $prefix;
}
}
}
5 changes: 4 additions & 1 deletion src/DictionaryParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace PhpFuzzer;

final class DictionaryParser {
/**
* @return list<string>
*/
public function parse(string $code): array {
$lines = explode("\n", $code);
$dictionary = [];
Expand Down Expand Up @@ -32,4 +35,4 @@ public function parse(string $code): array {
}
return $dictionary;
}
}
}
24 changes: 15 additions & 9 deletions src/Fuzzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final class Fuzzer {
public ?string $targetPath = null;

private ?string $coverageDir = null;
/** @var array<string, FileInfo> */
private array $fileInfos = [];
private ?string $lastInput = null;

Expand Down Expand Up @@ -180,7 +181,7 @@ private function isAllowedException(\Throwable $e): bool {
return false;
}

private function runInput(string $input) {
private function runInput(string $input): CorpusEntry {
$this->runs++;
if (\extension_loaded('pcntl')) {
\pcntl_alarm($this->timeout);
Expand All @@ -206,6 +207,10 @@ private function runInput(string $input) {
return new CorpusEntry($input, $features, $crashInfo);
}

/**
* @param array<int, int> $edgeCounts
* @return array<int, bool>
*/
private function edgeCountsToFeatures(array $edgeCounts): array {
$features = [];
foreach ($edgeCounts as $edge => $count) {
Expand Down Expand Up @@ -269,7 +274,7 @@ private function loadCorpus(): bool {
return true;
}

private function printAction(string $action, CorpusEntry $entry) {
private function printAction(string $action, CorpusEntry $entry): void {
$time = microtime(true) - $this->startTime;
$mem = memory_get_usage();
$numFeatures = $this->corpus->getNumFeatures();
Expand Down Expand Up @@ -298,12 +303,12 @@ private function formatBytes(int $bytes): string {
}
}

private function printCrash(string $prefix, CorpusEntry $entry) {
private function printCrash(string $prefix, CorpusEntry $entry): void {
echo "$prefix in $entry->path!\n";
echo $entry->crashInfo . "\n";
}

public function renderCoverage() {
public function renderCoverage(): void {
if ($this->coverageDir === null) {
throw new FuzzerException('Missing coverage directory');
}
Expand All @@ -312,7 +317,7 @@ public function renderCoverage() {
$renderer->render($this->fileInfos, $this->corpus->getSeenBlockMap());
}

private function minimizeCrash(string $path) {
private function minimizeCrash(string $path): void {
if (!is_file($path)) {
throw new FuzzerException("Crash input \"$path\" does not exist");
}
Expand Down Expand Up @@ -391,6 +396,7 @@ public function handleCliArgs(): int {
return 0;
}

/** @var Command|null $command The CommandInterface is missing the getHandler() method. */
$command = $getOpt->getCommand();
if (!$command) {
echo 'Missing command' . PHP_EOL;
Expand Down Expand Up @@ -437,7 +443,7 @@ private function createTemporaryCorpusDirectory(): string {
return $corpusDir;
}

private function handleFuzzCommand(GetOpt $getOpt) {
private function handleFuzzCommand(GetOpt $getOpt): void {
$corpusDir = $getOpt->getOperand('corpus');
if ($corpusDir === null) {
$corpusDir = $this->createTemporaryCorpusDirectory();
Expand All @@ -447,7 +453,7 @@ private function handleFuzzCommand(GetOpt $getOpt) {
$this->fuzz();
}

private function handleRunSingleCommand(GetOpt $getOpt) {
private function handleRunSingleCommand(GetOpt $getOpt): void {
$inputPath = $getOpt->getOperand('input');
if (!is_file($inputPath)) {
throw new FuzzerException('Input "' . $inputPath . '" does not exist');
Expand All @@ -461,14 +467,14 @@ private function handleRunSingleCommand(GetOpt $getOpt) {
}
}

private function handleMinimizeCrashCommand(GetOpt $getOpt) {
private function handleMinimizeCrashCommand(GetOpt $getOpt): void {
if ($this->maxRuns === PHP_INT_MAX) {
$this->maxRuns = 100000;
}
$this->minimizeCrash($getOpt->getOperand('input'));
}

private function handleReportCoverage(GetOpt $getOpt) {
private function handleReportCoverage(GetOpt $getOpt): void {
$this->setCorpusDir($getOpt->getOperand('corpus'));
$this->setCoverageDir($getOpt->getOperand('coverage-dir'));
$this->loadCorpus();
Expand Down
12 changes: 10 additions & 2 deletions src/FuzzingContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@
namespace PhpFuzzer;

final class FuzzingContext {
/** @var int */
public static $prevBlock = 0;
/** @var array<int, int> */
public static $edges = [];

public static function reset() {
public static function reset(): void {
self::$prevBlock = 0;
self::$edges = [];
}

/**
* @template T
* @param int $blockIndex
* @param T $returnValue
* @return T
*/
public static function traceBlock($blockIndex, $returnValue) {
$key = self::$prevBlock << 28 | $blockIndex;
self::$edges[$key] = (self::$edges[$key] ?? 0) + 1;
return $returnValue;
}
}
}
3 changes: 2 additions & 1 deletion src/Instrumentation/FileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
namespace PhpFuzzer\Instrumentation;

final class FileInfo {
/** @var array<int, int> */
public array $blockIndexToPos = [];
}
}
2 changes: 1 addition & 1 deletion src/Instrumentation/MutableString.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/** String that can be modified without invalidating offsets into it */
final class MutableString {
private string $string;
// [[pos, len, newString, order]]
/** @var list<array{0: int, 1: int, 2: string, 3: int}> [[pos, len, newString, order]] */
private array $modifications = [];

public function __construct(string $string) {
Expand Down
2 changes: 1 addition & 1 deletion src/Instrumentation/Visitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private function insertInlineBlockStubInStmts(Node $node): void {
} else if ($endChar === ':') {
$this->context->code->insert($endPos + 1, " $stub");
} else {
assert(false);
throw new \Error("Unexpected end char '$endChar'");
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/Mutation/Dictionary.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
namespace PhpFuzzer\Mutation;

final class Dictionary {
/** @var list<string> */
public array $dict = [];

public function isEmpty(): bool {
return empty($this->dict);
}

/**
* @param list<string> $words
*/
public function addWords(array $words): void {
$this->dict = [...$this->dict, ...$words];
}
}
}
10 changes: 7 additions & 3 deletions src/Mutation/Mutator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
final class Mutator {
private RNG $rng;
private Dictionary $dictionary;
/** @var list<callable> */
private array $mutators;
private ?string $crossOverWith = null; // TODO: Get rid of this

Expand All @@ -29,6 +30,9 @@ public function __construct(RNG $rng, Dictionary $dictionary) {
];
}

/**
* @return list<callable>
*/
public function getMutators(): array {
return $this->mutators;
}
Expand Down Expand Up @@ -151,7 +155,7 @@ public function mutateChangeASCIIInt(string $str, int $maxLen): ?string {
$int <<= 1;
break;
default:
assert(false);
throw new \Error("Cannot happen");
}

$intStr = (string) $int;
Expand Down Expand Up @@ -287,7 +291,7 @@ public function mutateCrossOver(string $str, int $maxLen): ?string {
case 2:
return $this->copyPartOf($this->crossOverWith, $str);
default:
assert(false);
throw new \Error("Cannot happen");
}
}

Expand Down Expand Up @@ -336,4 +340,4 @@ public function mutate(string $str, int $maxLen, ?string $crossOverWith): string
}
}
}
}
}
Loading

0 comments on commit 4ab4c63

Please sign in to comment.