Completed
Pull Request — master (#526)
by thomas
83:42 queued 12:15
created

BranchInterpreter::getAstForLogicalOps()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 23
nc 14
nop 1
dl 0
loc 33
ccs 18
cts 18
cp 1
crap 9
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Path;
4
5
use BitWasp\Bitcoin\Script\Interpreter\Stack;
6
use BitWasp\Bitcoin\Script\Opcodes;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
9
class BranchInterpreter
10
{
11
12
    /**
13
     * @var array
14
     */
15
    private $disabledOps = [
16
        Opcodes::OP_CAT,    Opcodes::OP_SUBSTR, Opcodes::OP_LEFT,  Opcodes::OP_RIGHT,
17
        Opcodes::OP_INVERT, Opcodes::OP_AND,    Opcodes::OP_OR,    Opcodes::OP_XOR,
18
        Opcodes::OP_2MUL,   Opcodes::OP_2DIV,   Opcodes::OP_MUL,   Opcodes::OP_DIV,
19
        Opcodes::OP_MOD,    Opcodes::OP_LSHIFT, Opcodes::OP_RSHIFT
20
    ];
21
22
    /**
23
     * @param ScriptInterface $script
24
     * @return ParsedScript
25
     */
26 18
    public function getScriptTree(ScriptInterface $script)
27
    {
28 18
        $ast = $this->getAstForLogicalOps($script);
29 18
        $scriptPaths = $ast->flags();
30 18
31
        $scriptBranches = [];
32 18
        if (count($scriptPaths) > 1) {
33 10
            foreach ($scriptPaths as $path) {
34 10
                $scriptBranches[] = $this->getBranchForPath($script, $path);
35
            }
36
        } else {
37 8
            $scriptBranches[] = $this->getBranchForPath($script, []);
38
        }
39
40 18
        return new ParsedScript($script, $ast, $scriptBranches);
41
    }
42
43
    /**
44
     * Build tree of dependent logical ops
45
     * @param ScriptInterface $script
46
     * @return LogicOpNode
47 136
     */
48
    public function getAstForLogicalOps(ScriptInterface $script)
49 136
    {
50 136
        $root = new LogicOpNode(null);
51 136
        $current = $root;
52
53 136
        foreach ($script->getScriptParser()->decode() as $op) {
54 22
            switch ($op->getOp()) {
55 22
                case Opcodes::OP_IF:
56
                case Opcodes::OP_NOTIF:
57
                    $split = $current->split();
58 114
                    $current = $split[$op->getOp() & 1];
59
                    break;
60
                case Opcodes::OP_ENDIF:
61 136
                    if (null === $current->getParent()) {
62
                        throw new \RuntimeException("Unexpected ENDIF, current scope had no parent");
63
                    }
64
                    $current = $current->getParent();
65
                    break;
66
                case Opcodes::OP_ELSE:
67
                    if (null === $current->getParent()) {
68
                        throw new \RuntimeException("Unexpected ELSE, current scope had no parent");
69 154
                    }
70
                    $current = $current->getParent()->getChild(!$current->getValue());
71 154
                    break;
72 154
            }
73 154
        }
74 154
75
        if (!$current->isRoot()) {
76 154
            throw new \RuntimeException("Unbalanced conditional - vfStack not empty at script termination");
77 154
        }
78 154
79 28
        return $root;
80 28
    }
81 28
82 28
    /**
83 28
     * Given a script and path, attempt to produce a ScriptBranch instance
84 154
     *
85 12
     * @param ScriptInterface $script
86 12
     * @param bool[] $path
87 12
     * @return ScriptBranch
88 12
     */
89 12
    public function getBranchForPath(ScriptInterface $script, array $path)
90 154
    {
91 32
        // parses the opcodes which were actually run
92 32
        $segments = $this->evaluateUsingStack($script, $path);
0 ignored issues
show
Documentation introduced by
$path is of type array<integer,boolean>, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
93 154
94 28
        return new ScriptBranch($script, $path, $segments);
95 154
    }
96
97
    /**
98
     * @param Stack $vfStack
99 154
     * @param bool $value
100
     * @return bool
101
     */
102
    private function checkExec(Stack $vfStack, $value)
103
    {
104
        $ret = 0;
105
        foreach ($vfStack as $item) {
106
            if ($item === $value) {
107
                $ret++;
108
            }
109 154
        }
110
111
        return $ret;
112 154
    }
113
114
    /**
115
     * @param ScriptInterface $script
116 154
     * @param int[] $logicalPath
117
     * @return array - array of Operation[] representing script segments
118
     */
119
    public function evaluateUsingStack(ScriptInterface $script, array $logicalPath)
120
    {
121
        $mainStack = new Stack();
122
        foreach (array_reverse($logicalPath) as $setting) {
123
            $mainStack->push($setting);
124 154
        }
125
126 154
        $vfStack = new Stack();
127 154
        $parser = $script->getScriptParser();
128 32
        $tracer = new PathTracer();
129 32
130
        foreach ($parser as $i => $operation) {
131
            $opCode = $operation->getOp();
132
            $fExec = !$this->checkExec($vfStack, false);
133 154
134
            if (in_array($opCode, $this->disabledOps, true)) {
135
                throw new \RuntimeException('Disabled Opcode');
136
            }
137
138
            if (Opcodes::OP_IF <= $opCode && $opCode <= Opcodes::OP_ENDIF) {
139
                switch ($opCode) {
140
                    case Opcodes::OP_IF:
141 154
                    case Opcodes::OP_NOTIF:
142
                        // <expression> if [statements] [else [statements]] endif
143 154
                        $value = false;
144 154
                        if ($fExec) {
145 32
                            if ($mainStack->isEmpty()) {
146
                                $op = $script->getOpcodes()->getOp($opCode & 1 ? Opcodes::OP_IF : Opcodes::OP_NOTIF);
147
                                throw new \RuntimeException("Unbalanced conditional at {$op} - not included in logicalPath");
148 154
                            }
149 154
150 154
                            $value = $mainStack->pop();
151
                            if ($opCode === Opcodes::OP_NOTIF) {
152 154
                                $value = !$value;
153 154
                            }
154 154
                        }
155
                        $vfStack->push($value);
156 154
                        break;
157
158
                    case Opcodes::OP_ELSE:
159
                        if ($vfStack->isEmpty()) {
160 154
                            throw new \RuntimeException('Unbalanced conditional at OP_ELSE');
161
                        }
162 32
                        $vfStack->push(!$vfStack->pop());
163 32
                        break;
164
165 32
                    case Opcodes::OP_ENDIF:
166 32
                        if ($vfStack->isEmpty()) {
167 32
                            throw new \RuntimeException('Unbalanced conditional at OP_ENDIF');
168
                        }
169
                        $vfStack->pop();
170
171 32
                        break;
172 32
                }
173 12
174
                $tracer->operation($operation);
175
            } else if ($fExec) {
176 32
                // Fill up trace with executed opcodes
177 32
                $tracer->operation($operation);
178
            }
179 32
        }
180 28
181
        if (count($vfStack) !== 0) {
182
            throw new \RuntimeException('Unbalanced conditional at script end');
183 28
        }
184 28
185
        if (count($mainStack) !== 0) {
186 32
            throw new \RuntimeException('Values remaining after script execution - invalid branch data');
187 32
        }
188
189
        return $tracer->done();
190 32
    }
191
}
192