mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +01:00
Add option to dump taint graph (#5080)
* Add option to dump taint graph * Fix types * Simplify types Co-authored-by: Matthew Brown <github@muglug.com>
This commit is contained in:
parent
99148f4d99
commit
6c0b2f8cb9
@ -81,3 +81,12 @@ To generate a SARIF report run Psalm with the `--report` flag and a `.sarif` ext
|
||||
```bash
|
||||
psalm --report=results.sarif
|
||||
```
|
||||
|
||||
## Debugging the taint graph
|
||||
|
||||
Psalm can output the taint graph using the DOT language. This is useful when expected taints are not detected. To generate a DOT graph run Psalm with the `--dump-taint-graph` flag. For example:
|
||||
|
||||
```bash
|
||||
psalm --taint-analysis --dump-taint-graph=taints.dot
|
||||
dot -Tsvg -o taints.svg taints.dot
|
||||
```
|
||||
|
@ -8,6 +8,9 @@ use function substr;
|
||||
use function strlen;
|
||||
use function array_reverse;
|
||||
use function array_sum;
|
||||
use function array_walk;
|
||||
use function array_merge;
|
||||
use function array_keys;
|
||||
|
||||
abstract class DataFlowGraph
|
||||
{
|
||||
@ -134,4 +137,18 @@ abstract class DataFlowGraph
|
||||
|
||||
return [$count, \count($origin_counts), \count($destination_counts), $mean];
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return list<list<string>>
|
||||
*/
|
||||
public function summarizeEdges(): array
|
||||
{
|
||||
$edges = [];
|
||||
|
||||
foreach ($this->forward_edges as $source => $destinations) {
|
||||
$edges[] = array_merge([$source], array_keys($destinations));
|
||||
}
|
||||
|
||||
return $edges;
|
||||
}
|
||||
}
|
||||
|
@ -345,6 +345,9 @@ Surfacing issues:
|
||||
--taint-analysis
|
||||
Run Psalm in taint analysis mode – see https://psalm.dev/docs/security_analysis for more info
|
||||
|
||||
--dump-taint-graph=OUTPUT_PATH
|
||||
Output the taint graph using the DOT language – requires --taint-analysis
|
||||
|
||||
Issue baselines:
|
||||
--set-baseline=PATH
|
||||
Save all current error level issues to a file, to mark them as info in subsequent runs
|
||||
|
@ -126,6 +126,7 @@ $valid_long_options = [
|
||||
'track-tainted-input',
|
||||
'taint-analysis',
|
||||
'security-analysis',
|
||||
'dump-taint-graph:',
|
||||
'find-unused-psalm-suppress',
|
||||
'error-level:',
|
||||
];
|
||||
@ -282,6 +283,9 @@ $run_taint_analysis = (isset($options['track-tainted-input'])
|
||||
|| isset($options['security-analysis'])
|
||||
|| isset($options['taint-analysis']));
|
||||
|
||||
/** @var string|null $dump_taint_graph */
|
||||
$dump_taint_graph = $options['dump-taint-graph'] ?? null;
|
||||
|
||||
if (array_key_exists('v', $options)) {
|
||||
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
|
||||
exit;
|
||||
@ -683,6 +687,18 @@ if ($find_references_to) {
|
||||
$project_analyzer->findReferencesTo($find_references_to);
|
||||
}
|
||||
|
||||
$flow_graph = $project_analyzer->getCodebase()->taint_flow_graph;
|
||||
if ($flow_graph !== null && $dump_taint_graph !== null) {
|
||||
file_put_contents($dump_taint_graph, "digraph Taints {\n\t".
|
||||
implode("\n\t", array_map(
|
||||
function (array $edges) {
|
||||
return '"'.implode('" -> "', $edges).'"';
|
||||
},
|
||||
$flow_graph->summarizeEdges()
|
||||
)) .
|
||||
"\n}\n");
|
||||
}
|
||||
|
||||
if (isset($options['set-baseline']) && is_string($options['set-baseline'])) {
|
||||
fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL);
|
||||
|
||||
|
@ -169,6 +169,25 @@ class PsalmEndToEndTest extends TestCase
|
||||
$this->assertSame(1, $result['CODE']);
|
||||
}
|
||||
|
||||
public function testTaintGraphDumping(): void
|
||||
{
|
||||
$this->runPsalmInit(1);
|
||||
$result = $this->runPsalm(
|
||||
[
|
||||
'--taint-analysis',
|
||||
'--dump-taint-graph='.self::$tmpDir.'/taints.dot',
|
||||
],
|
||||
self::$tmpDir,
|
||||
true
|
||||
);
|
||||
|
||||
$this->assertSame(1, $result['CODE']);
|
||||
$this->assertFileEquals(
|
||||
__DIR__ . '/../fixtures/expected_taint_graph.dot',
|
||||
self::$tmpDir.'/taints.dot'
|
||||
);
|
||||
}
|
||||
|
||||
public function testLegacyConfigWithoutresolveFromConfigFile(): void
|
||||
{
|
||||
$this->runPsalmInit(1);
|
||||
|
15
tests/fixtures/expected_taint_graph.dot
vendored
Normal file
15
tests/fixtures/expected_taint_graph.dot
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
digraph Taints {
|
||||
"$_GET:src/FileWithErrors.php:345" -> "$_GET['abc']-src/FileWithErrors.php:345-349"
|
||||
"$_GET['abc']-src/FileWithErrors.php:345-349" -> "coalesce-src/FileWithErrors.php:345-363"
|
||||
"$s-src/FileWithErrors.php:109-110" -> "acme\sampleproject\bar"
|
||||
"$s-src/FileWithErrors.php:162-163" -> "acme\sampleproject\baz"
|
||||
"$s-src/FileWithErrors.php:215-216" -> "acme\sampleproject\bat"
|
||||
"$s-src/FileWithErrors.php:269-270" -> "acme\sampleproject\bang"
|
||||
"acme\sampleproject\bang#1" -> "$s-src/FileWithErrors.php:269-270"
|
||||
"acme\sampleproject\bar#1" -> "$s-src/FileWithErrors.php:109-110"
|
||||
"acme\sampleproject\bat#1" -> "$s-src/FileWithErrors.php:215-216"
|
||||
"acme\sampleproject\baz#1" -> "$s-src/FileWithErrors.php:162-163"
|
||||
"acme\sampleproject\foo#1" -> "$s-src/FileWithErrors.php:57-58"
|
||||
"call to echo-src/FileWithErrors.php:335-364" -> "echo#1-src/filewitherrors.php:330"
|
||||
"coalesce-src/FileWithErrors.php:345-363" -> "call to echo-src/FileWithErrors.php:335-364"
|
||||
}
|
Loading…
Reference in New Issue
Block a user