Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.00% covered (warning)
64.00%
32 / 50
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreservedTaintedness
64.00% covered (warning)
64.00%
32 / 50
60.00% covered (warning)
60.00%
6 / 10
68.24
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
 newEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOffsetTaintedness
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setKeysOffsets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mergeWith
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
9.04
 asMergedWith
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 asTaintednessForArgument
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 toShortString
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 __clone
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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 newEmpty(): self {
36        return new self( ParamLinksOffsets::newEmpty() );
37    }
38
39    /**
40     * Set the taintedness for $offset to $value, in place
41     *
42     * @param Node|mixed $offset Node or a scalar value, already resolved
43     * @param self $value
44     */
45    public function setOffsetTaintedness( $offset, self $value ): void {
46        if ( is_scalar( $offset ) ) {
47            $this->dimTaint[$offset] = $value;
48        } else {
49            $this->unknownDimsTaint ??= self::newEmpty();
50            $this->unknownDimsTaint->mergeWith( $value );
51        }
52    }
53
54    /**
55     * @param ParamLinksOffsets $offsets
56     */
57    public function setKeysOffsets( ParamLinksOffsets $offsets ): void {
58        $this->keysOffsets = $offsets;
59    }
60
61    /**
62     * @param self $other
63     */
64    public function mergeWith( self $other ): void {
65        $this->ownOffsets->mergeWith( $other->ownOffsets );
66        if ( $other->keysOffsets && !$this->keysOffsets ) {
67            $this->keysOffsets = $other->keysOffsets;
68        } elseif ( $other->keysOffsets ) {
69            $this->keysOffsets->mergeWith( $other->keysOffsets );
70        }
71
72        if ( $other->unknownDimsTaint && !$this->unknownDimsTaint ) {
73            $this->unknownDimsTaint = $other->unknownDimsTaint;
74        } elseif ( $other->unknownDimsTaint ) {
75            $this->unknownDimsTaint->mergeWith( $other->unknownDimsTaint );
76        }
77        foreach ( $other->dimTaint as $key => $val ) {
78            if ( !array_key_exists( $key, $this->dimTaint ) ) {
79                $this->dimTaint[$key] = clone $val;
80            } else {
81                $this->dimTaint[$key]->mergeWith( $val );
82            }
83        }
84    }
85
86    /**
87     * @param self $other
88     * @return self
89     * @suppress PhanUnreferencedPublicMethod Kept for consistency
90     */
91    public function asMergedWith( self $other ): self {
92        $ret = clone $this;
93        $ret->mergeWith( $other );
94        return $ret;
95    }
96
97    /**
98     * @param Taintedness $argTaint
99     * @return Taintedness
100     */
101    public function asTaintednessForArgument( Taintedness $argTaint ): Taintedness {
102        $ret = $this->ownOffsets->appliedToTaintedness( $argTaint );
103
104        foreach ( $this->dimTaint as $k => $val ) {
105            $ret->setOffsetTaintedness( $k, $val->asTaintednessForArgument( $argTaint ) );
106        }
107        if ( $this->unknownDimsTaint ) {
108            $ret->setOffsetTaintedness( null, $this->unknownDimsTaint->asTaintednessForArgument( $argTaint ) );
109        }
110        if ( $this->keysOffsets ) {
111            $ret->addKeysTaintedness( $this->keysOffsets->appliedToTaintedness( $argTaint )->get() );
112        }
113        return $ret;
114    }
115
116    /**
117     * Get a stringified representation of this taintedness suitable for the debug annotation
118     *
119     * @return string
120     */
121    public function toShortString(): string {
122        $ret = "{Own: " . $this->ownOffsets->__toString();
123        if ( $this->keysOffsets ) {
124            $ret .= '; Keys: ' . $this->keysOffsets->__toString();
125        }
126        $keyParts = [];
127        if ( $this->dimTaint ) {
128            foreach ( $this->dimTaint as $key => $taint ) {
129                $keyParts[] = "$key => " . $taint->toShortString();
130            }
131        }
132        if ( $this->unknownDimsTaint ) {
133            $keyParts[] = 'Unknown => ' . $this->unknownDimsTaint->toShortString();
134        }
135        if ( $keyParts ) {
136            $ret .= '; Elements: {' . implode( '; ', $keyParts ) . '}';
137        }
138        $ret .= '}';
139        return $ret;
140    }
141
142    /**
143     * Make sure to clone member variables, too.
144     */
145    public function __clone() {
146        $this->ownOffsets = clone $this->ownOffsets;
147        if ( $this->unknownDimsTaint ) {
148            $this->unknownDimsTaint = clone $this->unknownDimsTaint;
149        }
150        foreach ( $this->dimTaint as $k => $v ) {
151            $this->dimTaint[$k] = clone $v;
152        }
153    }
154
155    /**
156     * @return string
157     */
158    public function __toString(): string {
159        return $this->toShortString();
160    }
161}