Completed
Push — master ( c7419f...b9ebc8 )
by thomas
35:04 queued 31:19
created

Parser::doNext()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.0468

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 36
ccs 22
cts 24
cp 0.9167
rs 4.9091
cc 9
eloc 22
nc 14
nop 1
crap 9.0468
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 1113
    public function __construct(Math $math, ScriptInterface $script)
53
    {
54 1113
        $this->math = $math;
55 1113
        $buffer = $script->getBuffer();
56 1113
        $this->data = $buffer->getBinary();
57 1113
        $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 1113
        $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 1113
        $this->empty = new Buffer('', 0, $math);
60 1113
    }
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
81 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
82 54
        $size = $size[1];
83 54
        $this->position += $strSize;
84
85 54
        return $size;
86
    }
87
88
    /**
89
     * @param int $size
90
     * @return bool
91
     */
92 771
    private function validateSize($size)
93
    {
94 771
        $pdif = ($this->end - $this->position);
95 771
        $a = $pdif < 0;
96 771
        $b = $pdif < $size;
97 771
        $r = !($a || $b);
98
99 771
        return $r;
100
    }
101
102
    /**
103
     * @param int $ptr
104
     * @return Operation
105
     */
106 1035
    private function doNext($ptr)
107
    {
108 1035
        if ($this->math->cmp($this->position, $this->end) >= 0) {
109
            throw new \RuntimeException('Position exceeds end of script!');
110
        }
111
112 1035
        $opCode = ord($this->data[$this->position++]);
113 1035
        $pushData = $this->empty;
114 1035
        $dataSize = 0;
115
116 1035
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
117 771
            if ($opCode < Opcodes::OP_PUSHDATA1) {
118 747
                $dataSize = $opCode;
119 771
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
120 30
                $dataSize = $this->unpackSize('C', 1);
121 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
122 12
                $dataSize = $this->unpackSize('v', 2);
123 12
            } else {
124 12
                $dataSize = $this->unpackSize('V', 4);
125
            }
126
127 771
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
128
                throw new \RuntimeException('Failed to unpack data from Script');
129
            }
130
131 771
            if ($dataSize > 0) {
132 633
                $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
133 633
            }
134
135 771
            $this->position += $dataSize;
136 771
        }
137
138 1035
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
139
140 1035
        return $this->array[$ptr];
141
    }
142
143
    /**
144
     *
145
     */
146 1071
    public function rewind()
147
    {
148 1071
        $this->execPtr = 0;
149 1071
    }
150
151
    /**
152
     * @return Operation
153
     */
154 1035
    public function current()
155
    {
156 1035
        if (isset($this->array[$this->execPtr])) {
157
            $exec = $this->array[$this->execPtr];
158
        } else {
159 1035
            $exec = $this->doNext($this->execPtr);
160
        }
161
162 1035
        return $exec;
163
    }
164
165
    /**
166
     * @return int
167
     */
168
    public function key()
169
    {
170
        return $this->execPtr;
171
    }
172
173
    /**
174
     * @return Operation
175
     */
176 1065
    public function next()
177
    {
178 1065
        $ptr = $this->execPtr;
179 1065
        if (isset($this->array[$ptr])) {
180 1029
            $this->execPtr++;
181 1029
            return $this->array[$ptr];
182
        }
183
184 36
        return null;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190 1071
    public function valid()
191
    {
192 1071
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
193
    }
194
195
    /**
196
     * @return Operation[]
197
     */
198 999
    public function decode()
199
    {
200 999
        $result = [];
201 999
        foreach ($this as $operation) {
202 969
            $result[] = $operation;
203 999
        }
204
205 999
        return $result;
206
    }
207
208
    /**
209
     * @return string
210
     */
211 672
    public function getHumanReadable()
212
    {
213 672
        return implode(' ', array_map(
214 672
            function (Operation $operation) {
215 672
                return $operation->isPush()
216 672
                    ? $operation->getData()->getHex()
217 672
                    : $this->script->getOpcodes()->getOp($operation->getOp());
218 672
            },
219 672
            $this->decode()
220 672
        ));
221
    }
222
}
223