Completed
Pull Request — master (#204)
by thomas
231:00 queued 227:15
created

Parser   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 90.32%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 30
c 7
b 1
f 1
lcom 1
cbo 4
dl 0
loc 237
ccs 84
cts 93
cp 0.9032
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getPosition() 0 4 1
A unpackSize() 0 11 2
A validateSize() 0 9 2
C doNext() 0 34 8
A rewind() 0 4 1
A current() 0 10 2
A key() 0 4 1
A next() 0 10 2
A valid() 0 4 2
A parse() 0 21 4
A decode() 0 9 2
A getHumanReadable() 0 11 2
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Parser;
4
5
use BitWasp\Bitcoin\Script\Opcodes;
6
use BitWasp\Bitcoin\Math\Math;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
use BitWasp\Buffertools\Buffer;
9
10
class Parser implements \Iterator
11
{
12
    /**
13
     * @var Math
14
     */
15
    private $math;
16
17
    /**
18
     * @var Buffer
19
     */
20
    private $empty;
21
22
    /**
23
     * @var int
24
     */
25
    private $position = 0;
26
27
    /**
28
     * @var int
29
     */
30
    private $end = 0;
31
32
    /**
33
     * @var int
34
     */
35
    private $execPtr = 0;
36
37
    /**
38
     * @var string
39
     */
40
    private $data = '';
41
42
    /**
43
     * @var Operation[]
44
     */
45
    private $array = array();
46
47
    /**
48
     * ScriptParser constructor.
49
     * @param Math $math
50
     * @param ScriptInterface $script
51
     */
52 1124
    public function __construct(Math $math, ScriptInterface $script)
53
    {
54 1124
        $this->math = $math;
55 1124
        $buffer = $script->getBuffer();
56 1124
        $this->data = $buffer->getBinary();
57 1124
        $this->end = $buffer->getSize();
0 ignored issues
show
Documentation Bug introduced by
It seems like $buffer->getSize() can also be of type string. However, the property $end is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
58 1124
        $this->script = $script;
0 ignored issues
show
Bug introduced by
The property script does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
59 1124
        $this->empty = new Buffer('', 0, $math);
60 1124
    }
61
62
    /**
63
     * @return int
64
     */
65
    public function getPosition()
66
    {
67
        return $this->position;
68
    }
69
70
    /**
71
     * @param string $packFormat
72
     * @param integer $strSize
73
     * @return array|bool
74
     */
75 54
    private function unpackSize($packFormat, $strSize)
76
    {
77 54
        if ($this->end - $this->position < $strSize) {
78
            return false;
79
        }
80 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
81 54
        $size = $size[1];
82 54
        $this->position += $strSize;
83
84 54
        return $size;
85
    }
86
87
    /**
88
     * @param int $size
89
     * @return bool
90
     */
91 770
    private function validateSize($size)
92
    {
93 770
        $pdif = ($this->end - $this->position);
94 770
        $a = $pdif < 0;
95 770
        $b = $pdif < $size;
96 770
        $r = !($a || $b);
97
98 770
        return $r;
99
    }
100
101
    /**
102
     * @param int $ptr
103
     * @return Operation
104
     */
105 1040
    private function doNext($ptr)
106
    {
107 1040
        if ($this->math->cmp($this->position, $this->end) >= 0) {
108
            throw new \RuntimeException('Position exceeds end of script!');
109
        }
110
111 1040
        $opCode = ord($this->data[$this->position++]);
112 1040
        $pushData = $this->empty;
113 1040
        $dataSize = 0;
114
115 1040
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
116 770
            if ($opCode < Opcodes::OP_PUSHDATA1) {
117 746
                $dataSize = $opCode;
118 770
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
119 30
                $dataSize = $this->unpackSize('C', 1);
120 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
121 12
                $dataSize = $this->unpackSize('v', 2);
122 12
            } else {
123 12
                $dataSize = $this->unpackSize('V', 4);
124
            }
125
126 770
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
127
                throw new \RuntimeException('Failed to unpack data from Script');
128
            }
129
130 770
            $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
131
132 770
            $this->position += $dataSize;
133 770
        }
134
135 1040
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
136
137 1040
        return $this->array[$ptr];
138
    }
139
140
    /**
141
     *
142
     */
143 1082
    public function rewind()
144
    {
145 1082
        $this->execPtr = 0;
146 1082
    }
147
148
    /**
149
     * @return Operation
150
     */
151 1040
    public function current()
152
    {
153 1040
        if (isset($this->array[$this->execPtr])) {
154
            $exec = $this->array[$this->execPtr];
155
        } else {
156 1040
            $exec = $this->doNext($this->execPtr);
157
        }
158
159 1040
        return $exec;
160
    }
161
162
    /**
163
     * @return int
164
     */
165
    public function key()
166
    {
167
        return $this->execPtr;
168
    }
169
170
    /**
171
     * @return Operation
172
     */
173 1064
    public function next()
174
    {
175 1064
        $ptr = $this->execPtr;
176 1064
        if (isset($this->array[$ptr])) {
177 1028
            $this->execPtr++;
178 1028
            return $this->array[$ptr];
179
        }
180
181 36
        return null;
182
    }
183
184
    /**
185
     * @return bool
186
     */
187 1082
    public function valid()
188
    {
189 1082
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
190
    }
191
192
    /**
193
     * returns a mix of Buffer objects and strings
194
     *
195
     * @return Buffer[]|string[]
196
     */
197 96
    public function parse()
198
    {
199 96
        $data = array();
200
201 96
        $it = $this;
202 96
        foreach ($it as $exec) {
203 90
            $opCode = $exec->getOp();
204 90
            if ($opCode == 0) {
205 18
                $push = Buffer::hex('00', 1, $this->math);
206 90
            } elseif ($opCode <= 78) {
207 78
                $push = $exec->getData();
208 78
            } else {
209
                // None of these are pushdatas, so just an opcode
210 48
                $push = $this->script->getOpcodes()->getOp($opCode);
211
            }
212
213 84
            $data[] = $push;
214 90
        }
215
216 90
        return $data;
217
    }
218
219
    /**
220
     * @return Operation[]
221
     */
222 962
    public function decode()
223
    {
224 962
        $result = [];
225 962
        foreach ($this as $operation) {
226 932
            $result[] = $operation;
227 962
        }
228
229 962
        return $result;
230
    }
231
232
    /**
233
     * @return string
234
     */
235 672
    public function getHumanReadable()
236
    {
237 672
        return implode(' ', array_map(
238 672
            function (Operation $operation) {
239 672
                return $operation->isPush()
240 672
                    ? $operation->getData()->getHex()
241 672
                    : $this->script->getOpcodes()->getOp($operation->getOp());
242 672
            },
243 672
            $this->decode()
244 672
        ));
245
    }
246
}
247