Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParamLinksOffsets
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 9
2162
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
 getInstance
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 asMergedWith
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 withoutShape
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 withOffsetPushed
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 asMovedToKeys
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 appliedToTaintedness
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 appliedToTaintednessForBackprop
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 isEmpty
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
56
 __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    public function __construct( int $flags ) {
24        $this->ownFlags = $flags;
25    }
26
27    public static function getInstance( int $flags ): self {
28        static $singletons = [];
29        if ( !isset( $singletons[$flags] ) ) {
30            $singletons[$flags] = new self( $flags );
31        }
32        return $singletons[$flags];
33    }
34
35    public function asMergedWith( self $other ): self {
36        if ( $this === $other ) {
37            return $this;
38        }
39
40        $ret = clone $this;
41
42        $ret->ownFlags |= $other->ownFlags;
43        if ( $other->unknown && !$ret->unknown ) {
44            $ret->unknown = $other->unknown;
45        } elseif ( $other->unknown ) {
46            $ret->unknown = $ret->unknown->asMergedWith( $other->unknown );
47        }
48        foreach ( $other->dims as $key => $val ) {
49            if ( !isset( $ret->dims[$key] ) ) {
50                $ret->dims[$key] = $val;
51            } else {
52                $ret->dims[$key] = $ret->dims[$key]->asMergedWith( $val );
53            }
54        }
55        $ret->keysFlags |= $other->keysFlags;
56
57        return $ret;
58    }
59
60    public function withoutShape( self $other ): self {
61        $ret = clone $this;
62
63        $ret->ownFlags &= ~$other->ownFlags;
64        foreach ( $other->dims as $key => $val ) {
65            if ( isset( $ret->dims[$key] ) ) {
66                $ret->dims[$key] = $ret->dims[$key]->withoutShape( $val );
67            }
68        }
69        if ( $other->unknown && $ret->unknown ) {
70            $ret->unknown = $ret->unknown->withoutShape( $other->unknown );
71        }
72        $ret->keysFlags &= ~$other->keysFlags;
73        return $ret;
74    }
75
76    /**
77     * Pushes $offsets to all leaves.
78     * @param Node|string|int|bool|float|null $offset
79     */
80    public function withOffsetPushed( mixed $offset ): self {
81        $ret = clone $this;
82
83        foreach ( $ret->dims as $key => $val ) {
84            $ret->dims[$key] = $val->withOffsetPushed( $offset );
85        }
86        if ( $ret->unknown ) {
87            $ret->unknown = $ret->unknown->withOffsetPushed( $offset );
88        }
89
90        if ( $ret->ownFlags === SecurityCheckPlugin::NO_TAINT ) {
91            return $ret;
92        }
93
94        $ownFlags = $ret->ownFlags;
95        $ret->ownFlags = SecurityCheckPlugin::NO_TAINT;
96        if ( is_scalar( $offset ) && !isset( $ret->dims[$offset] ) ) {
97            $ret->dims[$offset] = self::getInstance( $ownFlags );
98        } elseif ( !is_scalar( $offset ) && !$ret->unknown ) {
99            $ret->unknown = self::getInstance( $ownFlags );
100        }
101
102        return $ret;
103    }
104
105    public function asMovedToKeys(): self {
106        $ret = new self( SecurityCheckPlugin::NO_TAINT );
107
108        foreach ( $this->dims as $k => $val ) {
109            $ret->dims[$k] = $val->asMovedToKeys();
110        }
111        if ( $this->unknown ) {
112            $ret->unknown = $this->unknown->asMovedToKeys();
113        }
114
115        $ret->keysFlags = $this->ownFlags;
116
117        return $ret;
118    }
119
120    public function appliedToTaintedness( Taintedness $taintedness ): Taintedness {
121        if ( $this->ownFlags ) {
122            $ret = $taintedness->withOnly( $this->ownFlags );
123        } else {
124            $ret = Taintedness::safeSingleton();
125        }
126        foreach ( $this->dims as $k => $val ) {
127            $ret = $ret->asMergedWith(
128                $val->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( $k ) )
129            );
130        }
131        if ( $this->unknown ) {
132            $ret = $ret->asMergedWith(
133                $this->unknown->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( null ) )
134            );
135        }
136        $ret = $ret->with( $taintedness->asKeyForForeach()->withOnly( $this->keysFlags )->get() );
137        return $ret;
138    }
139
140    public function appliedToTaintednessForBackprop( Taintedness $taintedness ): Taintedness {
141        if ( $this->ownFlags ) {
142            $ret = $taintedness->withOnly( $this->ownFlags );
143        } else {
144            $ret = Taintedness::safeSingleton();
145        }
146
147        $dimTaint = [];
148        foreach ( $this->dims as $k => $val ) {
149            $dimTaint[$k] = $val->appliedToTaintednessForBackprop( $taintedness->getTaintednessForOffsetOrWhole( $k ) );
150        }
151        $unknownDimsTaint = $this->unknown?->appliedToTaintednessForBackprop(
152            $taintedness->getTaintednessForOffsetOrWhole( null )
153        );
154        $keysTaint = $taintedness->asKeyForForeach()->withOnly( $this->keysFlags )->get();
155
156        return $ret->asMergedWith( Taintedness::newFromShape( $dimTaint, $unknownDimsTaint, $keysTaint ) );
157    }
158
159    public function isEmpty(): bool {
160        if ( $this->ownFlags || $this->keysFlags ) {
161            return false;
162        }
163        foreach ( $this->dims as $val ) {
164            if ( !$val->isEmpty() ) {
165                return false;
166            }
167        }
168
169        if ( $this->unknown && !$this->unknown->isEmpty() ) {
170            return false;
171        }
172
173        return true;
174    }
175
176    /**
177     * @codeCoverageIgnore
178     */
179    public function __toString(): string {
180        $ret = '<(own): ' . SecurityCheckPlugin::taintToString( $this->ownFlags );
181
182        if ( $this->keysFlags ) {
183            $ret .= ', keys: ' . SecurityCheckPlugin::taintToString( $this->keysFlags );
184        }
185
186        if ( $this->dims || $this->unknown ) {
187            $ret .= ', dims: [';
188            $dimBits = [];
189            foreach ( $this->dims as $k => $val ) {
190                $dimBits[] = "$k => " . $val->__toString();
191            }
192            if ( $this->unknown ) {
193                $dimBits[] = '(unknown): ' . $this->unknown->__toString();
194            }
195            $ret .= implode( ', ', $dimBits ) . ']';
196        }
197        return $ret . '>';
198    }
199}