Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.47% covered (success)
92.47%
86 / 93
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreservedTaintedness
92.47% covered (success)
92.47%
86 / 93
77.78% covered (warning)
77.78%
7 / 9
52.11
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
 withOffsetTaintedness
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 withKeysOffsets
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 asMergedWith
70.00% covered (warning)
70.00%
14 / 20
0.00% covered (danger)
0.00%
0 / 1
14.27
 asTaintednessForArgument
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 asTaintednessForBackpropError
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 asTaintednessForVarBackpropError
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
6
 isEmpty
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
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
3namespace SecurityCheckPlugin;
4
5use ast\Node;
6
7/**
8 * This class represents what taintedness is passed through (=preserved) by a function parameter
9 */
10class 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        }