Completed
Pull Request — master (#492)
by thomas
106:30 queued 33:27
created

BranchInterpreter::evaluateUsingStack()   C

Complexity

Conditions 17
Paths 41

Size

Total Lines 66
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 40
nc 41
nop 2
dl 0
loc 66
rs 5.8371
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 ScriptBranch[]
25
     */
26
    public function getScriptBranches(ScriptInterface $script)
27
    {
28
        $ast = $this->getAstForLogicalOps($script);
29
        $paths = $ast->flags();
30
        $results = [];
31
32
        if (count($paths) > 1) {
33
            foreach ($paths as $path) {
34
                $results[] = $this->getBranchForPath($script, $path);
35
            }
36
        } else {
37
            $results[] = $this->getBranchForPath($script, []);
38
        }
39
40
        return $results;
41
    }
42
43
    /**
44
     * Build tree of dependent logical ops
45
     * @param ScriptInterface $script
46
     * @return LogicOpNode
47
     */
48
    public function getAstForLogicalOps(ScriptInterface $script)
49
    {
50
        $root = new LogicOpNode(null);
51
        $nextId = 1;
52
        $current = $root;
53
        $segments = [$root];
54
55
        foreach ($script->getScriptParser()->decode() as $op) {
56
            switch ($op->getOp()) {
57
                case Opcodes::OP_IF:
58
                    list ($node0, $node1) = $current->split();
59
                    $segments[$nextId++] = $node0;
60
                    $segments[$nextId++] = $node1;
61
                    $current = $node1;
62
                    break;
63
                case Opcodes::OP_NOTIF:
64
                    list ($node0, $node1) = $current->split();
65
                    $segments[$nextId++] = $node0;
66
                    $segments[$nextId++] = $node1;
67
                    $current = $node0;
68
                    break;
69
                case Opcodes::OP_ENDIF:
70
                    $current = $current->getParent();
71
                    break;
72
                case Opcodes::OP_ELSE:
73
                    if ($current->getValue() === false) {
74
                        throw new \RuntimeException("Unbalanced conditional");
75
                    }
76
                    $current = $current->getParent()->getChild(0);
77
                    break;
78
            }
79
        }
80
81
        return $root;
82
    }
83
84
    /**
85
     * Given a script and path, attempt to produce a ScriptBranch instance
86
     *
87
     * @param ScriptInterface $script
88
     * @param bool[] $path
89
     * @return ScriptBranch
90
     */
91
    public function getBranchForPath(ScriptInterface $script, array $path)
92
    {
93
        $stack = new Stack();
94
        foreach (array_reverse($path) as $setting) {
95
            $stack->push($setting);
96
        }
97
98
        $segments = $this->evaluateUsingStack($script, $stack);
99
        return new ScriptBranch($script, $path, $segments);
100
    }
101
102
    /**
103
     * @param Stack $vfStack
104
     * @param bool $value
105
     * @return bool
106
     */
107
    private function checkExec(Stack $vfStack, $value)
108
    {
109
        $ret = 0;
110
        foreach ($vfStack as $item) {
111
            if ($item === $value) {
112
                $ret++;
113
            }
114
        }
115
116
        return $ret;
117
    }
118
119
    /**
120
     * @param ScriptInterface $script
121
     * @param Stack $mainStack
122
     * @return PathTrace
123
     */
124
    public function evaluateUsingStack(ScriptInterface $script, Stack $mainStack)
125
    {
126
        $vfStack = new Stack();
127
        $parser = $script->getScriptParser();
128
        $tracer = new PathTracer();
129
130
        foreach ($parser as $i => $operation) {
131
            $opCode = $operation->getOp();
132
            $fExec = !$this->checkExec($vfStack, false);
133
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
                    case Opcodes::OP_NOTIF:
142
                        // <expression> if [statements] [else [statements]] endif
143
                        $value = false;
144
                        if ($fExec) {
145
                            if ($mainStack->isEmpty()) {
146
                                throw new \RuntimeException('Unbalanced conditional');
147
                            }
148
149
                            $value = $mainStack->pop();
150
                            if ($opCode === Opcodes::OP_NOTIF) {
151
                                $value = !$value;
152
                            }
153
                        }
154
                        $vfStack->push($value);
155
                        break;
156
157
                    case Opcodes::OP_ELSE:
158
                        if ($vfStack->isEmpty()) {
159
                            throw new \RuntimeException('Unbalanced conditional');
160
                        }
161
                        $vfStack->push(!$vfStack->pop());
162
                        break;
163
164
                    case Opcodes::OP_ENDIF:
165
                        if ($vfStack->isEmpty()) {
166
                            throw new \RuntimeException('Unbalanced conditional');
167
                        }
168
                        $vfStack->pop();
169
170
                        break;
171
                }
172
173
                $tracer->operation($operation);
174
            } else if ($fExec) {
175
                // Fill up trace with executed opcodes
176
                $tracer->operation($operation);
177
            }
178
        }
179
180
        if (count($vfStack) !== 0) {
181
            throw new \RuntimeException('Unbalanced conditional at script end');
182
        }
183
184
        if (count($mainStack) !== 0) {
185
            throw new \RuntimeException('Values remaining after script execution - invalid branch data');
186
        }
187
188
        return $tracer->done();
189
    }
190
}
191