Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreservedTaintedness
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 9
2450
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 emptySingleton
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 withOffsetTaintedness
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 withKeysOffsets
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 asMergedWith
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
 asTaintednessForArgument
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 asTaintednessForBackpropError
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 asTaintednessForVarBackpropError
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 isEmpty
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
72
 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    public function __construct( ParamLinksOffsets $offsets ) {
26        $this->ownOffsets = $offsets;
27    }
28
29    public static function emptySingleton(): self {
30        static $singleton;
31        if ( !$singleton ) {
32            $singleton = new self( ParamLinksOffsets::getInstance( SecurityCheckPlugin::NO_TAINT ) );
33        }
34        return $singleton;
35    }
36
37    /**
38     * Set the taintedness for $offset to $value, in place
39     *
40     * @param Node|mixed $offset Node or a scalar value, already resolved
41     * @param self $value
42     */
43    public function withOffsetTaintedness( mixed $offset, self $value ): self {
44        $ret = clone $this;
45        if ( is_scalar( $offset ) ) {
46            $ret->dimTaint[$offset] = $value;
47        } else {
48            $ret->unknownDimsTaint = $ret->unknownDimsTaint
49                ? $ret->unknownDimsTaint->asMergedWith( $value )
50                : $value;
51        }
52        return $ret;
53    }
54
55    public function withKeysOffsets( ParamLinksOffsets $offsets ): self {
56        $ret = clone $this;
57        $ret->keysOffsets = $offsets;
58        return $ret;
59    }
60
61    /**
62     * @param self $other
63     * @suppress PhanUnreferencedPublicMethod Kept for consistency
64     */
65    public function asMergedWith( self $other ): self {
66        $emptySingleton = self::emptySingleton();
67        if ( $this === $emptySingleton ) {
68            return $other;
69        }
70        if ( $other === $emptySingleton ) {
71            return $this;
72        }
73
74        $ret = clone $this;
75        $ret->ownOffsets = $ret->ownOffsets->asMergedWith( $other->ownOffsets );
76
77        if ( $other->keysOffsets && !$ret->keysOffsets ) {
78            $ret->keysOffsets = $other->keysOffsets;
79        } elseif ( $other->keysOffsets ) {
80            $ret->keysOffsets = $ret->keysOffsets->asMergedWith( $other->keysOffsets );
81        }
82
83        if ( $other->unknownDimsTaint && !$ret->unknownDimsTaint ) {
84            $ret->unknownDimsTaint = $other->unknownDimsTaint;
85        } elseif ( $other->unknownDimsTaint ) {
86            $ret->unknownDimsTaint = $ret->unknownDimsTaint->asMergedWith( $other->unknownDimsTaint );
87        }
88        foreach ( $other->dimTaint as $key => $val ) {
89            if ( !array_key_exists( $key, $ret->dimTaint ) ) {
90                $ret->dimTaint[$key] = $val;
91            } else {
92                $ret->dimTaint[$key] = $ret->dimTaint[$key]->asMergedWith( $val );
93            }
94        }
95
96        return $ret;
97    }
98
99    public function asTaintednessForArgument( Taintedness $argTaint ): Taintedness {
100        $safeTaint = Taintedness::safeSingleton();
101        if ( $argTaint === $safeTaint || $this === self::emptySingleton() ) {
102            return $safeTaint;
103        }
104
105        $ret = $this->ownOffsets->appliedToTaintedness( $argTaint );
106
107        $dimTaint = [];
108        foreach ( $this->dimTaint as $k => $val ) {
109            $dimTaint[$k] = $val->asTaintednessForArgument( $argTaint );
110        }
111        $unknownDimsTaint = $this->unknownDimsTaint?->asTaintednessForArgument( $argTaint );
112        $keysTaint = $this->keysOffsets
113            ? $this->keysOffsets->appliedToTaintedness( $argTaint )->get()
114            : SecurityCheckPlugin::NO_TAINT;
115        return $ret->asMergedWith( Taintedness::newFromShape( $dimTaint, $unknownDimsTaint, $keysTaint ) );
116    }
117
118    public function asTaintednessForBackpropError( Taintedness $sinkTaint ): Taintedness {
119        $safeTaint = Taintedness::safeSingleton();
120        if ( $sinkTaint === $safeTaint || $this === self::emptySingleton() ) {
121            return $safeTaint;
122        }
123
124        $ret = $this->ownOffsets->appliedToTaintednessForBackprop( $sinkTaint );
125
126        foreach ( $this->dimTaint as $val ) {
127            $ret = $ret->asMergedWith( $val->asTaintednessForBackpropError( $sinkTaint ) );
128        }
129        if ( $this->unknownDimsTaint ) {
130            $ret = $ret->asMergedWith(
131                $this->unknownDimsTaint->asTaintednessForBackpropError( $sinkTaint )
132            );
133        }
134        if ( $this->keysOffsets ) {
135            $ret = $ret->asMergedWith(
136                $this->keysOffsets->appliedToTaintednessForBackprop( $sinkTaint )
137            );
138        }
139        return $ret;
140    }
141
142    public function asTaintednessForVarBackpropError( Taintedness $newTaint ): Taintedness {
143        $safeTaint = Taintedness::safeSingleton();
144        if ( $newTaint === $safeTaint || $this === self::emptySingleton() ) {
145            return $safeTaint;
146        }
147
148        $ret = $this->ownOffsets->appliedToTaintednessForBackprop( $newTaint );
149
150        $dimTaint = [];
151        foreach ( $this->dimTaint as $key => $val ) {
152            $dimTaint[$key] = $val->asTaintednessForVarBackpropError(
153                $newTaint->getTaintednessForOffsetOrWhole( $key )
154            );
155        }
156        $unknownDimsTaint = $this->unknownDimsTaint?->asTaintednessForVarBackpropError(
157            $newTaint->getTaintednessForOffsetOrWhole( null )
158        );
159        $keysTaint = $this->keysOffsets
160            ? $this->keysOffsets->appliedToTaintednessForBackprop( $newTaint )->get()
161            : SecurityCheckPlugin::NO_TAINT;
162        return $ret->asMergedWith( Taintedness::newFromShape( $dimTaint, $unknownDimsTaint, $keysTaint ) );
163    }
164
165    public function isEmpty(): bool {
166        if ( !$this->ownOffsets->isEmpty() || ( $this->keysOffsets && !$this->keysOffsets->isEmpty() ) ) {
167            return false;
168        }
169        foreach ( $this->dimTaint as $val ) {
170            if ( !$val->isEmpty() ) {
171                return false;
172            }
173        }
174        if ( $this->unknownDimsTaint && !$this->unknownDimsTaint->isEmpty() ) {
175            return false;
176        }
177        return true;
178    }
179
180    /**
181     * @codeCoverageIgnore
182     */
183    public function toShortString(): string {
184        $ret = "{Own: " . $this->ownOffsets->__toString();
185        if ( $this->keysOffsets ) {
186            $ret .= '; Keys: ' . $this->keysOffsets->__toString();
187        }
188        $keyParts = [];
189        if ( $this->dimTaint ) {
190            foreach ( $this->dimTaint as $key => $taint ) {
191                $keyParts[] = "$key => " . $taint->toShortString();
192            }
193        }
194        if ( $this->unknownDimsTaint ) {
195            $keyParts[] = 'Unknown => ' . $this->unknownDimsTaint->toShortString();
196        }
197        if ( $keyParts ) {
198            $ret .= '; Elements: {' . implode( '; ', $keyParts ) . '}';
199        }
200        $ret .= '}';
201        return $ret;
202    }
203
204    /**
205     * @codeCoverageIgnore
206     */
207    public function __toString(): string {
208        return $this->toShortString();
209    }
210}