Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
91.27% |
115 / 126 |
|
53.85% |
7 / 13 |
CRAP | |
0.00% |
0 / 1 |
CausedByLines | |
91.27% |
115 / 126 |
|
53.85% |
7 / 13 |
71.08 | |
0.00% |
0 / 1 |
addLines | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
12.34 | |||
asPreservingTaintednessAndLinks | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
asIntersectedWithTaintedness | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
asFilteredForFuncAndParam | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
getLinesForGenericReturn | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
withTaintAddedToMethodArgLinks | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
mergeWith | |
97.44% |
38 / 39 |
|
0.00% |
0 / 1 |
17 | |||
asMergedWith | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getArraySubsetIdx | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
7.02 | |||
toStringForIssue | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
getRelevantLinesForTaintedness | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
toLinesArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
toDebugString | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 |
1 | <?php declare( strict_types=1 ); |
2 | |
3 | namespace SecurityCheckPlugin; |
4 | |
5 | use Phan\Language\Element\FunctionInterface; |
6 | |
7 | /** |
8 | * Value object used to store caused-by lines. |
9 | * |
10 | * @note `clone` will not deep-clone instances of this class. That'd be more correct in theory, but it's |
11 | * tremendously expensive for this class (+35s and +600MB for MW core). |
12 | * |
13 | * @todo Keep per-offset caused-by lines |
14 | */ |
15 | class CausedByLines { |
16 | private const MAX_LINES_PER_ISSUE = 80; |
17 | // XXX Hack: Enforce a hard limit, or things may explode |
18 | private const LINES_HARD_LIMIT = 100; |
19 | |
20 | /** |
21 | * Note: the links are nullable for performance. |
22 | * @var array<array<Taintedness|string|MethodLinks|null>> |
23 | * @phan-var list<array{0:Taintedness,1:string,2:?MethodLinks}> |
24 | */ |
25 | private $lines = []; |
26 | |
27 | /** |
28 | * @param string[] $lines |
29 | * @param Taintedness $taintedness |
30 | * @param MethodLinks|null $links |
31 | * @note Taintedness and links are cloned as needed |
32 | */ |
33 | public function addLines( array $lines, Taintedness $taintedness, MethodLinks $links = null ): void { |
34 | if ( !$this->lines ) { |
35 | foreach ( $lines as $line ) { |
36 | $this->lines[] = [ clone $taintedness, $line, $links ? clone $links : null ]; |
37 | } |
38 | return; |
39 | } |
40 | |
41 | foreach ( $lines as $line ) { |
42 | if ( count( $this->lines ) >= self::LINES_HARD_LIMIT ) { |
43 | break; |
44 | } |
45 | $idx = array_search( $line, array_column( $this->lines, 1 ), true ); |
46 | if ( $idx !== false ) { |
47 | $this->lines[ $idx ][0]->mergeWith( $taintedness ); |
48 | if ( $links && !$this->lines[$idx][2] ) { |
49 | $this->lines[$idx][2] = clone $links; |
50 | } elseif ( $links && $links !== $this->lines[$idx][2] ) { |
51 | $this->lines[$idx][2]->mergeWith( $links ); |
52 | } |
53 | } else { |
54 | $this->lines[] = [ clone $taintedness, $line, $links ? clone $links : null ]; |
55 | } |
56 | } |
57 | } |
58 | |
59 | /** |
60 | * Move any possibly preserved taintedness stored in the method links to the actual taintedness of this line, |
61 | * and use $links as the new links being preserved. |
62 | * |
63 | * @param Taintedness $taintedness |
64 | * @param MethodLinks $links |
65 | * @return self |
66 | */ |
67 | public function asPreservingTaintednessAndLinks( Taintedness $taintedness, MethodLinks $links ): self { |
68 | $ret = new self; |
69 | if ( !$this->lines ) { |
70 | return $ret; |
71 | } |
72 | $curTaint = $taintedness->get(); |
73 | foreach ( $this->lines as [ $_, $eLine, $eLinks ] ) { |
74 | $preservedFlags = $eLinks && ( $curTaint !== SecurityCheckPlugin::NO_TAINT ) |
75 | ? $eLinks->filterPreservedFlags( $curTaint ) |
76 | : SecurityCheckPlugin::NO_TAINT; |
77 | $ret->lines[] = [ new Taintedness( $preservedFlags ), $eLine, clone $links ]; |
78 | } |
79 | return $ret; |
80 | } |
81 | |
82 | /** |
83 | * @param Taintedness $taintedness |
84 | * @return self |
85 | */ |
86 | public function asIntersectedWithTaintedness( Taintedness $taintedness ): self { |
87 | $ret = new self; |