Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.68% |
75 / 76 |
|
87.50% |
7 / 8 |
CRAP | |
0.00% |
0 / 1 |
ParamLinksOffsets | |
98.68% |
75 / 76 |
|
87.50% |
7 / 8 |
42 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getInstance | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
asMergedWith | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
7 | |||
withOffsetPushed | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
8 | |||
asMovedToKeys | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
appliedToTaintedness | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
appliedToTaintednessForBackprop | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
isEmpty | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
7 | |||
__toString | n/a |
0 / 0 |
n/a |
0 / 0 |
6 |
1 | <?php declare( strict_types=1 ); |
2 | |
3 | namespace SecurityCheckPlugin; |
4 | |
5 | use ast\Node; |
6 | |
7 | /** |
8 | * Tree-like object of possible offset combinations for partial links to a parameter |
9 | */ |
10 | class ParamLinksOffsets { |
11 | /** @var int What taint flags are preserved for this offset */ |
12 | private $ownFlags; |
13 | |
14 | /** @var self[] */ |
15 | private $dims = []; |
16 | |
17 | /** @var self|null */ |
18 | private $unknown; |
19 | |
20 | /** @var int */ |
21 | private $keysFlags = SecurityCheckPlugin::NO_TAINT; |
22 | |
23 | /** |
24 | * @param int $flags |
25 | */ |
26 | public function __construct( int $flags ) { |
27 | $this->ownFlags = $flags; |
28 | } |
29 | |
30 | public static function getInstance( int $flags ): self { |
31 | static $singletons = []; |
32 | if ( !isset( $singletons[$flags] ) ) { |
33 | $singletons[$flags] = new self( $flags ); |
34 | } |
35 | return $singletons[$flags]; |
36 | } |
37 | |
38 | public function asMergedWith( self $other ): self { |
39 | if ( $this === $other ) { |
40 | return $this; |
41 | } |
42 | |
43 | $ret = clone $this; |
44 | |
45 | $ret->ownFlags |= $other->ownFlags; |
46 | if ( $other->unknown && !$ret->unknown ) { |
47 | $ret->unknown = $other->unknown; |
48 | } elseif ( $other->unknown ) { |
49 | $ret->unknown = $ret->unknown->asMergedWith( $other->unknown ); |
50 | } |
51 | foreach ( $other->dims as $key => $val ) { |
52 | if ( !isset( $ret->dims[$key] ) ) { |
53 | $ret->dims[$key] = $val; |
54 | } else { |
55 | $ret->dims[$key] = $ret->dims[$key]->asMergedWith( $val ); |
56 | } |
57 | } |
58 | $ret->keysFlags |= $other->keysFlags; |
59 | |
60 | return $ret; |
61 | } |
62 | |
63 | /** |
64 | * Pushes $offsets to all leaves. |
65 | * @param Node|string|int|null $offset |
66 | */ |
67 | public function withOffsetPushed( $offset ): self { |
68 | $ret = clone $this; |
69 | |
70 | foreach ( $ret->dims as $key => $val ) { |
71 | $ret->dims[$key] = $val->withOffsetPushed( $offset ); |
72 | } |
73 | if ( $ret->unknown ) { |
74 | $ret->unknown = $ret->unknown->withOffsetPushed( $offset ); |
75 | } |
76 | |
77 | if ( $ret->ownFlags === SecurityCheckPlugin::NO_TAINT ) { |
78 | return $ret; |
79 | } |
80 | |
81 | $ownFlags = $ret->ownFlags; |
82 | $ret->ownFlags = SecurityCheckPlugin::NO_TAINT; |
83 | if ( is_scalar( $offset ) && !isset( $ret->dims[$offset] ) ) { |
84 | $ret->dims[$offset] = self::getInstance( $ownFlags ); |
85 | } elseif ( !is_scalar( $offset ) && !$ret->unknown ) { |
86 | $ret->unknown = self::getInstance( $ownFlags ); |
87 | } |
88 | |
89 | return $ret; |
90 | } |
91 | |
92 | /** |
93 | * @return self |
94 | */ |
95 | public function asMovedToKeys(): self { |
96 | $ret = new self( SecurityCheckPlugin::NO_TAINT ); |
97 | |
98 | foreach ( $this->dims as $k => $val ) { |
99 | $ret->dims[$k] = $val->asMovedToKeys(); |
100 | } |
101 | if ( $this->unknown ) { |
102 | $ret->unknown = $this->unknown->asMovedToKeys(); |
103 | } |
104 | |
105 | $ret->keysFlags = $this->ownFlags; |
106 | |
107 | return $ret; |
108 | } |
109 | |
110 | /** |
111 | * @param Taintedness $taintedness |
112 | * @return Taintedness |
113 | */ |
114 | public function appliedToTaintedness( Taintedness $taintedness ): Taintedness { |
115 | if ( $this->ownFlags ) { |
116 | $ret = $taintedness->withOnly( $this->ownFlags ); |
117 | } else { |
118 | $ret = Taintedness::safeSingleton(); |
119 | } |
120 | foreach ( $this->dims as $k => $val ) { |
121 | $ret = $ret->asMergedWith( |
122 | $val->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( $k ) ) |
123 | ); |
124 | } |
125 | if ( $this->unknown ) { |
126 | $ret = $ret->asMergedWith( |
127 | $this->unknown->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( null ) ) |
128 | ); |
129 | } |
130 | $ret = $ret->with( $taintedness->asKeyForForeach()->withOnly( $this->keysFlags )->get() ); |
131 | return $ret; |
132 | } |
133 | |
134 | public function appliedToTaintednessForBackprop( Taintedness $taintedness ): Taintedness { |
135 | if ( $this->ownFlags ) { |
136 | $ret = $taintedness->withOnly( $this->ownFlags ); |
137 | } else { |
138 | $ret = Taintedness::safeSingleton(); |
139 | } |
140 | |
141 | foreach ( $this->dims as $k => $val ) { |
142 | $ret = $ret->withAddedOffsetTaintedness( |
143 | $k, |
144 | $val->appliedToTaintednessForBackprop( $taintedness->getTaintednessForOffsetOrWhole( $k ) ) |
145 | ); |
146 | } |
147 | if ( $this->unknown ) { |
148 | $ret = $ret->withAddedOffsetTaintedness( |
149 | null, |
150 | $this->unknown->appliedToTaintednessForBackprop( $taintedness->getTaintednessForOffsetOrWhole( null ) ) |
151 | ); |
152 | } |
153 | $ret = $ret->withAddedKeysTaintedness( $taintedness->asKeyForForeach()->withOnly( $this->keysFlags )->get() ); |
154 | return $ret; |
155 | } |
156 | |
157 | public function isEmpty(): bool { |
158 | if ( $this->ownFlags || $this->keysFlags ) { |
159 | return false; |
160 | } |
161 | foreach ( $this->dims as $val ) { |
162 | if ( !$val->isEmpty() ) { |
163 | return false; |
164 | } |
165 | } |
166 | |
167 | if ( $this->unknown && !$this->unknown->isEmpty() ) { |
168 | return false; |
169 | } |
170 | |
171 | return true; |
172 | } |
173 | |
174 | /** |
175 | * @codeCoverageIgnore |
176 | */ |
177 | public function __toString(): string { |
178 | $ret = '<(own): ' . SecurityCheckPlugin::taintToString( $this->ownFlags ); |
179 | |
180 | if ( $this->keysFlags ) { |
181 | $ret .= ', keys: ' . SecurityCheckPlugin::taintToString( $this->keysFlags ); |
182 | } |
183 | |
184 | if ( $this->dims || $this->unknown ) { |
185 | $ret .= ', dims: ['; |
186 | $dimBits = []; |
187 | foreach ( $this->dims as $k => $val ) { |
188 | $dimBits[] = "$k => " . $val->__toString(); |
189 | } |
190 | if ( $this->unknown ) { |
191 | $dimBits[] = '(unknown): ' . $this->unknown->__toString(); |
192 | } |
193 | $ret .= implode( ', ', $dimBits ) . ']'; |
194 | } |
195 | return $ret . '>'; |
196 | } |
197 | } |