Completed
Pull Request — master (#192)
by thomas
25:45
created

Parser::doNext()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.0468

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 36
ccs 22
cts 24
cp 0.9167
rs 4.9091
cc 9
eloc 23
nc 11
nop 1
crap 9.0468
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Parser;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Script\Opcodes;
7
use BitWasp\Bitcoin\Math\Math;
8
use BitWasp\Bitcoin\Script\ScriptInterface;
9
use BitWasp\Buffertools\Buffer;
10
11
class Parser implements \Iterator
12
{
13
    /**
14
     * @var int
15
     */
16
    private $position = 0;
17
18
    /**
19
     * @var int
20
     */
21
    private $end = 0;
22
23
    /**
24
     * @var int
25
     */
26
    private $execPtr = 0;
27
28
    /**
29
     * @var string
30
     */
31
    private $data = '';
32
33
    /**
34
     * @var Operation[]
35
     */
36
    private $array = array();
37
38
    /**
39
     * ScriptParser constructor.
40
     * @param Math $math
41
     * @param ScriptInterface $script
42
     */
43 1125
    public function __construct(Math $math, ScriptInterface $script)
0 ignored issues
show
Unused Code introduced by
The parameter $math is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
44
    {
45 1125
        $this->math = Bitcoin::getMath();
0 ignored issues
show
Bug introduced by
The property math 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...
46 1125
        $buffer = $script->getBuffer();
47 1125
        $this->data = $buffer->getBinary();
48 1125
        $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...
49 1125
        $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...
50 1125
    }
51
52
    /**
53
     * @return int
54
     */
55
    public function getPosition()
56
    {
57
        return $this->position;
58
    }
59
60
    /**
61
     * @param string $packFormat
62
     * @param integer $strSize
63
     * @return array|bool
64
     */
65 54
    private function unpackSize($packFormat, $strSize)
66
    {
67 54
        if ($this->end - $this->position < $strSize) {
68
            return false;
69
        }
70 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
71 54
        $size = $size[1];
72 54
        $this->position += $strSize;
73
74 54
        return $size;
75
    }
76
77
    /**
78
     * @param int $size
79
     * @return bool
80
     */
81 633
    private function validateSize($size)
82
    {
83 633
        $pdif = ($this->end - $this->position);
84 633
        $a = $pdif < 0;
85 633
        $b = $pdif < $size;
86 633
        $r = !($a || $b);
87
88 633
        return $r;
89
    }
90
91
    /**
92
     * @param int $ptr
93
     * @return Operation
94
     */
95 1041
    private function doNext($ptr)
96
    {
97 1041
        if ($this->math->cmp($this->position, $this->end) >= 0) {
98
            throw new \RuntimeException('Position exceeds end of script!');
99
        }
100
101 1041
        $opCode = ord($this->data[$this->position++]);
102 1041
        $pushData = null;
103 1041
        $dataSize = 0;
104
105 1041
        if ($opCode === Opcodes::OP_0) {
106 210
            $pushData = new Buffer('', 0);
107 1041
        } elseif ($opCode <= Opcodes::OP_PUSHDATA4) {
108 633
            if ($opCode < Opcodes::OP_PUSHDATA1) {
109 609
                $dataSize = $opCode;
110 633
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
111 30
                $dataSize = $this->unpackSize('C', 1);
112 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
113 12
                $dataSize = $this->unpackSize('v', 2);
114 12
            } else {
115 12
                $dataSize = $this->unpackSize('V', 4);
116
            }
117
118 633
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
119
                throw new \RuntimeException('Failed to unpack data from Script');
120
            }
121
122 633
            $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
123
124 633
            $this->position += $dataSize;
125 633
        }
126
127 1041
        $this->array[$ptr] = $result = new Operation($opCode, $pushData, $dataSize);
128
129 1041
        return $result;
130
    }
131
132
    /**
133
     *
134
     */
135 1083
    public function rewind()
136
    {
137 1083
        $this->execPtr = 0;
138 1083
    }
139
140
    /**
141
     * @return Operation
142
     */
143 1041
    public function current()
144
    {
145 1041
        if (isset($this->array[$this->execPtr])) {
146
            $exec = $this->array[$this->execPtr];
147
        } else {
148 1041
            $exec = $this->doNext($this->execPtr);
149
        }
150
151 1041
        return $exec;
152
    }
153
154
    /**
155
     * @return int
156
     */
157
    public function key()
158
    {
159
        return $this->execPtr;
160
    }
161
162
    /**
163
     * @return Operation
164
     */
165 1065
    public function next()
166
    {
167 1065
        $ptr = $this->execPtr;
168 1065
        if (isset($this->array[$ptr])) {
169 1029
            $this->execPtr++;
170 1029
            return $this->array[$ptr];
171
        }
172
173 36
        return null;
174
    }
175
176
    /**
177
     * @return bool
178
     */
179 1083
    public function valid()
180
    {
181 1083
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
182
    }
183
184
    /**
185
     * returns a mix of Buffer objects and strings
186
     *
187
     * @return Buffer[]|string[]
188
     */
189 1011
    public function parse()
190
    {
191 1011
        $data = array();
192
193 1011
        $it = $this;
194 1011
        foreach ($it as $exec) {
195 975
            $opCode = $exec->getOp();
196 975
            if ($opCode == 0) {
197 180
                $push = Buffer::hex('00', 1, $this->math);
198 975
            } elseif ($opCode <= 78) {
199 585
                $push = $exec->getData();
200 585
            } else {
201
                // None of these are pushdatas, so just an opcode
202 909
                $push = $this->script->getOpcodes()->getOp($opCode);
203
            }
204
205 969
            $data[] = $push;
206 1005
        }
207
208 1005
        return $data;
209
    }
210
211
    /**
212
     * @return string
213
     */
214 672
    public function getHumanReadable()
215
    {
216 672
        $parse = $this->parse();
217
218 672
        $array = array_map(
219 672
            function ($value) {
220 672
                return ($value instanceof Buffer)
221 672
                    ? $value->getHex()
222 672
                    : $value;
223 672
            },
224
            $parse
225 672
        );
226
227 672
        return implode(' ', $array);
228
    }
229
}
230