Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.37% covered (warning)
79.37%
50 / 63
85.71% covered (warning)
85.71%
12 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParamLinksOffsets
79.37% covered (warning)
79.37%
50 / 63
85.71% covered (warning)
85.71%
12 / 14
49.03
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
 newAll
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
 getFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mergeWith
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 pushOffset
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
7.05
 asMovedToKeys
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getFlagsRecursively
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 __clone
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getDims
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUnknown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getKeysFlags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appliedToTaintedness
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 __toString
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
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    /**
31     * @return self
32     */
33    public static function newAll(): self {
34        return new self( SecurityCheckPlugin::ALL_TAINT );
35    }
36
37    /**
38     * @return self
39     */
40    public static function newEmpty(): self {
41        return new self( SecurityCheckPlugin::NO_TAINT );
42    }
43
44    /**
45     * @note This method should be avoided where possible
46     * @return int
47     */
48    public function getFlags(): int {
49        return $this->ownFlags;
50    }
51
52    /**
53     * @param self $other
54     */
55    public function mergeWith( self $other ): void {
56        $this->ownFlags |= $other->ownFlags;
57        if ( $other->unknown && !$this->unknown ) {
58            $this->unknown = $other->unknown;
59        } elseif ( $other->unknown ) {
60            $this->unknown->mergeWith( $other->unknown );
61        }
62        foreach ( $other->dims as $key => $val ) {
63            if ( !isset( $this->dims[$key] ) ) {
64                $this->dims[$key] = clone $val;
65            } else {
66                $this->dims[$key]->mergeWith( $val );
67            }
68        }
69        $this->keysFlags |= $other->keysFlags;
70    }
71
72    /**
73     * Pushes $offsets to all leaves.
74     * @param Node|string|int|null $offset
75     */
76    public function pushOffset( $offset ): void {
77        foreach ( $this->dims as $val ) {
78            $val->pushOffset( $offset );
79        }
80        if ( $this->unknown ) {
81            $this->unknown->pushOffset( $offset );
82        }
83        $ownFlags = $this->ownFlags;
84        $this->ownFlags = SecurityCheckPlugin::NO_TAINT;
85        if ( is_scalar( $offset ) && !isset( $this->dims[$offset] ) ) {
86            $this->dims[$offset] = new self( $ownFlags );
87        } elseif ( !is_scalar( $offset ) && !$this->unknown ) {
88            $this->unknown = new self( $ownFlags );
89        }
90    }
91
92    /**
93     * @return self
94     */
95    public function asMovedToKeys(): self {
96        $ret = new self( SecurityCheckPlugin::NO_TAINT );
97        $ret->keysFlags = $this->ownFlags;
98        return $ret;
99    }
100
101    /**
102     * @note This should only be used by SingleMethodLinks::getAllPreservedFlags
103     * @return int
104     */
105    public function getFlagsRecursively(): int {
106        $ret = $this->ownFlags;
107        foreach ( $this->dims as $dimOffsets ) {
108            $ret |= $dimOffsets->getFlagsRecursively();
109        }
110        if ( $this->unknown ) {
111            $ret |= $this->unknown->getFlagsRecursively();
112        }
113        $ret |= $this->keysFlags;
114        return $ret;
115    }
116
117    public function __clone() {
118        foreach ( $this->dims as $k => $v ) {
119            $this->dims[$k] = clone $v;
120        }
121        if ( $this->unknown ) {
122            $this->unknown = clone $this->unknown;
123        }
124    }
125
126    /**
127     * Should only be used in Taintedness::asMovedAtRelevantOffsets
128     * @return ParamLinksOffsets[]
129     */
130    public function getDims(): array {
131        return $this->dims;
132    }
133
134    /**
135     * Should only be used in Taintedness::asMovedAtRelevantOffsetsForBackprop
136     * @return ParamLinksOffsets|null
137     */
138    public function getUnknown(): ?ParamLinksOffsets {
139        return $this->unknown;
140    }
141
142    /**
143     * Should only be used in Taintedness::asMovedAtRelevantOffsetsForBackprop
144     * @return int
145     */
146    public function getKeysFlags(): int {
147        return $this->keysFlags;
148    }
149
150    /**
151     * @param Taintedness $taintedness
152     * @return Taintedness
153     */
154    public function appliedToTaintedness( Taintedness $taintedness ): Taintedness {
155        if ( $this->ownFlags ) {
156            $ret = $taintedness->withOnly( $this->ownFlags );
157        } else {
158            $ret = new Taintedness( SecurityCheckPlugin::NO_TAINT );
159        }
160        foreach ( $this->dims as $k => $val ) {
161            $ret->mergeWith( $val->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( $k ) ) );
162        }
163        if ( $this->unknown ) {
164            $ret->mergeWith(
165                $this->unknown->appliedToTaintedness( $taintedness->getTaintednessForOffsetOrWhole( null ) )
166            );
167        }
168        // XXX Should we do something to the keys here?
169        return $ret;
170    }
171
172    /**
173     * @return string
174     */
175    public function __toString(): string {
176        $ret = '(own): ' . SecurityCheckPlugin::taintToString( $this->ownFlags );
177
178        if ( $this->keysFlags ) {
179            $ret .= ', keys: ' . SecurityCheckPlugin::taintToString( $this->keysFlags );
180        }
181
182        if ( $this->dims || $this->unknown ) {
183            $ret .= ', dims: [';
184            $dimBits = [];
185            foreach ( $this->dims as $k => $val ) {
186                $dimBits[] = "$k => " . $val->__toString();
187            }
188            if ( $this->unknown ) {
189                $dimBits[] = '(unknown): ' . $this->unknown->__toString();
190            }
191            $ret .= implode( ', ', $dimBits ) . ']';
192        }
193        return $ret;
194    }
195}