Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.68% covered (success)
98.68%
75 / 76
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParamLinksOffsets
98.68% covered (success)
98.68%
75 / 76
87.50% covered (warning)
87.50%
7 / 8
42
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
 getInstance
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 asMergedWith
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 withOffsetPushed
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
8
 asMovedToKeys
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 appliedToTaintedness
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 appliedToTaintednessForBackprop
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
 isEmpty
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 __toString
n/a
0 / 0
n/a
0 / 0
6
1<?php declare( strict_types=1 );
2
3namespace SecurityCheckPlugin;
4
5use ast\Node;
6
7/**
8 * Tree-like object of possible offset combinations for partial links to a parameter
9 */
10class 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}