Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.40% covered (success)
91.40%
85 / 93
87.50% covered (warning)
87.50%
14 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
FunctionCausedByLines
91.40% covered (success)
91.40%
85 / 93
87.50% covered (warning)
87.50%
14 / 16
53.72
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 emptySingleton
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getGenericLines
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withAddedGenericLines
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withGenericLines
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withAddedParamSinkLines
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 withAddedParamPreservedLines
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 withParamSinkLines
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withParamPreservedLines
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withVariadicParamSinkLines
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 withVariadicParamPreservedLines
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 withAddedVariadicParamSinkLines
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 withAddedVariadicParamPreservedLines
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getParamSinkLines
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getParamPreservedLines
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 asMergedWith
87.10% covered (warning)
87.10%
27 / 31
0.00% covered (danger)
0.00%
0 / 1
16.55
 toString
n/a
0 / 0
n/a
0 / 0
5
 __toString
n/a
0 / 0
n/a
0 / 0
1
1<?php declare( strict_types=1 );
2
3namespace SecurityCheckPlugin;
4
5/**
6 * This class represents caused-by lines for a function-like:
7 * - Lines in genericLines are for taintedness that is added inside the function, regardless of parameters; these
8 *   have a Taintedness object associated, while the associated links are always empty.
9 * - Lines in (variadic)paramSinkLines are those inside a function that EXEC the arguments. These also have a
10 *   Taintedness object associated, and no links.
11 * - Lines in (variadic)paramPreservedLines are responsible for putting a parameter inside the return value. These have
12 *   a safe Taintedness associated, and usually non-empty links.
13 */
14class FunctionCausedByLines {
15    /** @var CausedByLines */
16    private $genericLines;
17    /** @var CausedByLines[] */
18    private $paramSinkLines = [];
19    /** @var CausedByLines[] */
20    private $paramPreservedLines = [];
21    /** @var int|null Index of a variadic parameter, if any */
22    private $variadicParamIndex;
23    /** @var CausedByLines|null */
24    private $variadicParamSinkLines;
25    /** @var CausedByLines|null */
26    private $variadicParamPreservedLines;
27
28    public function __construct() {
29        $this->genericLines = CausedByLines::emptySingleton();
30    }
31
32    public static function emptySingleton(): self {
33        static $singleton;
34        if ( !$singleton ) {
35            $singleton = new self();
36        }
37        return $singleton;
38    }
39
40    /**
41     * @return CausedByLines
42     * @suppress PhanUnreferencedPublicMethod
43     */
44    public function getGenericLines(): CausedByLines {
45        return $this->genericLines;
46    }
47
48    /**
49     * @param string[] $lines
50     * @param Taintedness $taint
51     * @param ?MethodLinks $links
52     * @return self
53     */
54    public function withAddedGenericLines( array $lines, Taintedness $taint, ?MethodLinks $links = null ): self {
55        $ret = clone $this;
56        $ret->genericLines = $this->genericLines->withAddedLines( $lines, $taint, $links );
57        return $ret;
58    }
59
60    public function withGenericLines( CausedByLines $lines ): self {
61        $ret = clone $this;
62        $ret->genericLines = $lines;
63        return $ret;
64    }
65
66    /**
67     * @param int $param
68     * @param string[] $lines
69     * @param Taintedness $taint
70     * @return self
71     */
72    public function withAddedParamSinkLines( int $param, array $lines, Taintedness $taint ): self {
73        assert( $param !== $this->variadicParamIndex );
74        $ret = clone $this;
75        if ( !isset( $ret->paramSinkLines[$param] ) ) {
76            $ret->paramSinkLines[$param] = CausedByLines::emptySingleton();
77        }
78        $ret->paramSinkLines[$param] = $ret->paramSinkLines[$param]->withAddedLines( $lines, $taint );
79        return $ret;
80    }
81
82    /**
83     * @param int $param
84     * @param string[] $lines
85     * @param Taintedness $taint
86     * @param ?MethodLinks $links
87     * @return self
88     */
89    public function withAddedParamPreservedLines(
90        int $param,
91        array $lines,
92        Taintedness $taint,
93        ?MethodLinks $links = null
94    ): self {
95        assert( $param !== $this->variadicParamIndex );
96        $ret = clone $this;
97        if ( !isset( $ret->paramPreservedLines[$param] ) ) {
98            $ret->paramPreservedLines[$param] = CausedByLines::emptySingleton();
99        }
100        $ret->paramPreservedLines[$param] = $ret->paramPreservedLines[$param]
101            ->withAddedLines( $lines, $taint, $links );
102        return $ret;
103    }
104
105    public function withParamSinkLines( int $param, CausedByLines $lines ): self {
106        $ret = clone $this;
107        $ret->paramSinkLines[$param] = $lines;
108        return $ret;
109    }
110
111    public function withParamPreservedLines( int $param, CausedByLines $lines ): self {
112        $ret = clone $this;
113        $ret->paramPreservedLines[$param] = $lines;
114        return $ret;
115    }
116
117    public function withVariadicParamSinkLines( int $param, CausedByLines $lines ): self {
118        $ret = clone $this;
119        $ret->variadicParamIndex = $param;
120        $ret->variadicParamSinkLines = $lines;
121        return $ret;
122    }
123
124    public function withVariadicParamPreservedLines( int $param, CausedByLines $lines ): self {
125        $ret = clone $this;
126        $ret->variadicParamIndex = $param;
127        $ret->variadicParamPreservedLines = $lines;
128        return $ret;
129    }
130
131    /**
132     * @param int $param
133     * @param string[] $lines
134     * @param Taintedness $taint
135     * @return self
136     */
137    public function withAddedVariadicParamSinkLines(
138        int $param,
139        array $lines,
140        Taintedness $taint
141    ): self {
142        assert( !isset( $this->paramSinkLines[$param] ) && !isset( $this->paramPreservedLines[$param] ) );
143        $ret = clone $this;
144        $ret->variadicParamIndex = $param;
145        if ( !$ret->variadicParamSinkLines ) {
146            $ret->variadicParamSinkLines = CausedByLines::emptySingleton();
147        }
148        $ret->variadicParamSinkLines = $ret->variadicParamSinkLines->withAddedLines( $lines, $taint );
149        return $ret;
150    }
151
152    /**
153     * @param int $param
154     * @param string[] $lines
155     * @param Taintedness $taint
156     * @param ?MethodLinks $links
157     * @return self
158     */
159    public function withAddedVariadicParamPreservedLines(
160        int $param,
161        array $lines,
162        Taintedness $taint,
163        ?MethodLinks $links = null
164    ): self {
165        assert( !isset( $this->paramSinkLines[$param] ) && !isset( $this->paramPreservedLines[$param] ) );
166        $ret = clone $this;
167        $ret->variadicParamIndex = $param;
168        if ( !$ret->variadicParamPreservedLines ) {
169            $ret->variadicParamPreservedLines = CausedByLines::emptySingleton();
170        }
171        $ret->variadicParamPreservedLines = $ret->variadicParamPreservedLines
172            ->withAddedLines( $lines, $taint, $links );
173        return $ret;
174    }
175
176    /**
177     * @param int $param
178     * @return CausedByLines
179     */
180    public function getParamSinkLines( int $param ): CausedByLines {
181        if ( isset( $this->paramSinkLines[$param] ) ) {
182            return $this->paramSinkLines[$param];
183        }
184        if (
185            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
186            $this->variadicParamSinkLines
187        ) {
188            return $this->variadicParamSinkLines;
189        }
190        return CausedByLines::emptySingleton();
191    }
192
193    /**
194     * @param int $param
195     * @return CausedByLines
196     */
197    public function getParamPreservedLines( int $param ): CausedByLines {
198        if ( isset( $this->paramPreservedLines[$param] ) ) {
199            return $this->paramPreservedLines[$param];
200        }
201        if (
202            $this->variadicParamIndex !== null && $param >= $this->variadicParamIndex &&
203            $this->variadicParamPreservedLines
204        ) {
205            return $this->variadicParamPreservedLines;
206        }
207        return CausedByLines::emptySingleton();
208    }
209
210    /**
211     * @param FunctionCausedByLines $other
212     * @param FunctionTaintedness $funcTaint To check NO_OVERRIDE
213     * @return self
214     */
215    public function asMergedWith( self $other, FunctionTaintedness $funcTaint ): self {
216        $ret = clone $this;
217        $canOverrideOverall = $funcTaint->canOverrideOverall();
218        if ( $canOverrideOverall ) {
219            $ret->genericLines = $ret->genericLines->asMergedWith( $other->genericLines );
220        }
221
222        foreach ( $other->paramSinkLines as $param => $lines ) {
223            if ( $funcTaint->canOverrideNonVariadicParam( $param ) ) {
224                if ( isset( $ret->paramSinkLines[$param] ) ) {
225                    $ret->paramSinkLines[$param] = $ret->paramSinkLines[$param]->asMergedWith( $lines );
226                } else {
227                    $ret->paramSinkLines[$param] = $lines;
228                }
229            }
230        }
231        if ( $canOverrideOverall ) {
232            foreach ( $other->paramPreservedLines as $param => $lines ) {
233                if ( $funcTaint->canOverrideNonVariadicParam( $param ) ) {
234                    if ( isset( $ret->paramPreservedLines[$param] ) ) {
235                        $ret->paramPreservedLines[$param] = $ret->paramPreservedLines[$param]->asMergedWith( $lines );
236                    } else {
237                        $ret->paramPreservedLines[$param] = $lines;
238                    }
239                }
240            }
241        }
242        $variadicIndex = $other->variadicParamIndex;
243        if ( $variadicIndex !== null && $funcTaint->canOverrideVariadicParam() ) {
244            $ret->variadicParamIndex = $variadicIndex;
245            $sinkVariadic = $other->variadicParamSinkLines;
246            if ( $sinkVariadic ) {
247                if ( $ret->variadicParamSinkLines ) {
248                    $ret->variadicParamSinkLines = $ret->variadicParamSinkLines->asMergedWith( $sinkVariadic );
249                } else {
250                    $ret->variadicParamSinkLines = $sinkVariadic;
251                }
252            }
253            if ( $canOverrideOverall ) {
254                $preserveVariadic = $other->variadicParamPreservedLines;
255                if ( $preserveVariadic ) {
256                    if ( $ret->variadicParamPreservedLines ) {
257                        $ret->variadicParamPreservedLines = $this->variadicParamPreservedLines
258                            ->asMergedWith( $preserveVariadic );
259                    } else {
260                        $ret->variadicParamPreservedLines = $preserveVariadic;
261                    }
262                }
263            }
264        }
265        return $ret;
266    }
267
268    /**
269     * @codeCoverageIgnore
270     */
271    public function toString(): string {
272        $str = "{\nGeneric: " . $this->genericLines->toDebugString() . ",\n";
273        foreach ( $this->paramSinkLines as $par => $lines ) {
274            $str .= "$par (sink): " . $lines->toDebugString() . ",\n";
275        }
276        foreach ( $this->paramPreservedLines as $par => $lines ) {
277            $str .= "$par (preserved): " . $lines->toDebugString() . ",\n";
278        }
279        if ( $this->variadicParamSinkLines ) {
280            $str .= "...{$this->variadicParamIndex} (sink): " . $this->variadicParamSinkLines->toDebugString() . "\n";
281        }
282        if ( $this->variadicParamPreservedLines ) {
283            $str .= "...{$this->variadicParamIndex} (preserved): " .
284                $this->variadicParamPreservedLines->toDebugString() . "\n";
285        }
286        return "$str}";
287    }
288
289    /**
290     * @codeCoverageIgnore
291     */
292    public function __toString(): string {
293        return $this->toString();
294    }
295}