Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.47% |
86 / 93 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
PreservedTaintedness | |
92.47% |
86 / 93 |
|
77.78% |
7 / 9 |
52.11 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
emptySingleton | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
withOffsetTaintedness | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
withKeysOffsets | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
asMergedWith | |
70.00% |
14 / 20 |
|
0.00% |
0 / 1 |
14.27 | |||
asTaintednessForArgument | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
asTaintednessForBackpropError | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
asTaintednessForVarBackpropError | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
6 | |||
isEmpty | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
8 | |||
toShortString | n/a |
0 / 0 |
n/a |
0 / 0 |
6 | |||||
__toString | n/a |
0 / 0 |
n/a |
0 / 0 |
1 |
1 | <?php declare( strict_types=1 ); |
2 | |
3 | namespace SecurityCheckPlugin; |
4 | |
5 | use ast\Node; |
6 | |
7 | /** |
8 | * This class represents what taintedness is passed through (=preserved) by a function parameter |
9 | */ |
10 | class PreservedTaintedness { |
11 | /** @var ParamLinksOffsets */ |
12 | private $ownOffsets; |
13 | |
14 | /** @var self[] Taintedness for each possible array element */ |
15 | private $dimTaint = []; |
16 | |
17 | /** @var ParamLinksOffsets|null */ |
18 | private $keysOffsets; |
19 | |
20 | /** |
21 | * @var self|null Taintedness for array elements that we couldn't attribute to any key |
22 | */ |
23 | private $unknownDimsTaint; |
24 | |
25 | /** |
26 | * @param ParamLinksOffsets $offsets |
27 | */ |
28 | public function __construct( ParamLinksOffsets $offsets ) { |
29 | $this->ownOffsets = $offsets; |
30 | } |
31 | |
32 | /** |
33 | * @return self |
34 | */ |
35 | public static function emptySingleton(): self { |
36 | static $singleton; |
37 | if ( !$singleton ) { |
38 | $singleton = new self( ParamLinksOffsets::getInstance( SecurityCheckPlugin::NO_TAINT ) ); |
39 | } |
40 | return $singleton; |
41 | } |
42 | |
43 | /** |
44 | * Set the taintedness for $offset to $value, in place |
45 | * |
46 | * @param Node|mixed $offset Node or a scalar value, already resolved |
47 | * @param self $value |
48 | * @return self |
49 | */ |
50 | public function withOffsetTaintedness( $offset, self $value ): self { |
51 | $ret = clone $this; |
52 | if ( is_scalar( $offset ) ) { |
53 | $ret->dimTaint[$offset] = $value; |
54 | } else { |
55 | $ret->unknownDimsTaint = $ret->unknownDimsTaint |
56 | ? $ret->unknownDimsTaint->asMergedWith( $value ) |
57 | : $value; |
58 | } |
59 | return $ret; |
60 | } |
61 | |
62 | public function withKeysOffsets( ParamLinksOffsets $offsets ): self { |
63 | $ret = clone $this; |
64 | $ret->keysOffsets = $offsets; |
65 | return $ret; |
66 | } |
67 | |
68 | /** |
69 | * @param self $other |
70 | * @return self |
71 | * @suppress PhanUnreferencedPublicMethod Kept for consistency |
72 | */ |
73 | public function asMergedWith( self $other ): self { |
74 | $emptySingleton = self::emptySingleton(); |
75 | if ( $this === $emptySingleton ) { |
76 | return $other; |
77 | } |
78 | if ( $other === $emptySingleton ) { |
79 | return $this; |
80 | } |
81 | |
82 | $ret = clone $this; |
83 | $ret->ownOffsets = $ret->ownOffsets->asMergedWith( $other->ownOffsets ); |
84 | |
85 | if ( $other->keysOffsets && !$ret->keysOffsets ) { |
86 | $ret->keysOffsets = $other->keysOffsets; |
87 | } elseif ( $other->keysOffsets ) { |
88 | $ret->keysOffsets = $ret->keysOffsets->asMergedWith( $other->keysOffsets ); |
89 | } |
90 | |
91 | if ( $other->unknownDimsTaint && !$ret->unknownDimsTaint ) { |
92 | $ret->unknownDimsTaint = $other->unknownDimsTaint; |
93 | } elseif ( $other->unknownDimsTaint ) { |
94 | $ret->unknownDimsTaint = $ret->unknownDimsTaint->asMergedWith( $other->unknownDimsTaint ); |
95 | } |
96 | foreach ( $other->dimTaint as $key => $val ) { |
97 | if ( !array_key_exists( $key, $ret->dimTaint ) ) { |
98 | $ret->dimTaint[$key] = $val; |
99 | } else { |
100 | $ret->dimTaint[$key] = $ret->dimTaint[$key]->asMergedWith( $val ); |
101 | } |
102 | } |
103 | |
104 | return $ret; |
105 | } |
106 | |
107 | /** |
108 | * @param Taintedness $argTaint |
109 | * @return Taintedness |
110 | */ |
111 | public function asTaintednessForArgument( Taintedness $argTaint ): Taintedness { |
112 | $safeTaint = Taintedness::safeSingleton(); |
113 | if ( $argTaint === $safeTaint || $this === self::emptySingleton() ) { |
114 | return $safeTaint; |
115 | } |
116 | |
117 | $ret = $this->ownOffsets->appliedToTaintedness( $argTaint ); |
118 | |
119 | foreach ( $this->dimTaint as $k => $val ) { |
120 | $ret = $ret->withAddedOffsetTaintedness( $k, $val->asTaintednessForArgument( $argTaint ) ); |
121 | } |
122 | if ( $this->unknownDimsTaint ) { |
123 | $ret = $ret->withAddedOffsetTaintedness( |
124 | null, |
125 | $this->unknownDimsTaint->asTaintednessForArgument( $argTaint ) |
126 | ); |
127 | } |
128 | if ( $this->keysOffsets ) { |
129 | $ret = $ret->withAddedKeysTaintedness( $this->keysOffsets->appliedToTaintedness( $argTaint )->get() ); |
130 | } |
131 | return $ret; |
132 | } |
133 | |
134 | public function asTaintednessForBackpropError( Taintedness $sinkTaint ): Taintedness { |
135 | $safeTaint = Taintedness::safeSingleton(); |
136 | if ( $sinkTaint === $safeTaint || $this === self::emptySingleton() ) { |
137 | return $safeTaint; |
138 | } |
139 | |
140 | $ret = $this->ownOffsets->appliedToTaintednessForBackprop( $sinkTaint ); |
141 | |
142 | foreach ( $this->dimTaint as $val ) { |
143 | $ret = $ret->asMergedWith( $val->asTaintednessForBackpropError( $sinkTaint ) ); |
144 | } |
145 | if ( $this->unknownDimsTaint ) { |
146 | $ret = $ret->asMergedWith( |
147 | $this->unknownDimsTaint->asTaintednessForBackpropError( $sinkTaint ) |
148 | ); |
149 | } |