Fixing bug with removing OP_IF branch if the stack top is false

This commit is contained in:
Chris Stewart 2016-01-31 17:04:10 -06:00
parent 4c5cee16a5
commit 7ee5d6f82d
3 changed files with 31 additions and 27 deletions

View File

@ -37,7 +37,6 @@ trait ControlOperationsInterpreter {
def opIf(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head == OP_IF, "Script top was not OP_IF")
val binaryTree = parseBinaryTree(script)
logger.debug("Binary tree: " + binaryTree)
if (stack.head != OP_0) {
//remove OP_ELSE from binary tree
val newTreeWithoutOpElse = removeFirstOpElse(binaryTree)
@ -45,11 +44,12 @@ trait ControlOperationsInterpreter {
(stack.tail,newScript.tail)
} else {
//remove the OP_IF
val scriptWithoutOpIf = removeFirstOpIf(script)
if (scriptWithoutOpIf.headOption == Some(OP_ENDIF)) {
val scriptWithoutOpIf : BinaryTree[ScriptToken] = removeFirstOpIf(binaryTree)
(stack.tail,scriptWithoutOpIf.toList)
/* if (scriptWithoutOpIf.headOption == Some(OP_ENDIF)) {
val scriptWithoutOpEndIf = opEndIf(stack,scriptWithoutOpIf)
(scriptWithoutOpEndIf._1.tail, scriptWithoutOpEndIf._2)
} else (stack.tail,scriptWithoutOpIf)
} else (stack.tail,scriptWithoutOpIf)*/
}
}
@ -140,7 +140,6 @@ trait ControlOperationsInterpreter {
* @return
*/
private def parseOpIf(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
logger.debug("script : " + script)
val _ :: t = script
val lastOpEndIfIndex = findLastOpEndIf(t)
val lastOpElseIndex = findLastOpElse(t)
@ -192,7 +191,6 @@ trait ControlOperationsInterpreter {
* @return
*/
private def parseOpElse(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
logger.debug("Script parse OP_ELSE: " + script)
val _ :: t = script
val nestedOpElseIndex = findFirstOpElse(t)
val lastOpEndIfIndex = findLastOpEndIf(t)
@ -209,7 +207,6 @@ trait ControlOperationsInterpreter {
val nestedOpIfExpression = t.slice(0,nestedOpEndIfIndex.get+1)
val restOfScript = t.slice(nestedOpEndIfIndex.get+1,t.size)
logger.debug("Rest of script for t.count(_ == OP_ENDIF): " + restOfScript)
Node(OP_ELSE,loop(nestedOpIfExpression),loop(restOfScript))
} else if (t.count(_ == OP_IF) == 1) {
//check to see if there is a nested OP_IF inside of the OP_ELSE
@ -221,7 +218,6 @@ trait ControlOperationsInterpreter {
val nestedOpIfExpression = t.slice(0,nestedOpEndIfIndex.get+1)
val restOfScript = t.slice(nestedOpEndIfIndex.get+1,t.size)
logger.debug("Rest of script for t.contains(OP_IF): " + restOfScript)
Node(OP_ELSE,loop(nestedOpIfExpression),loop(restOfScript))
} else if (nestedOpElseIndex.isDefined && lastOpEndIfIndex.isDefined && nestedOpElseIndex.get < lastOpEndIfIndex.get) {
//if we have a nested OP_ELSE before our last OP_ENDIF index
@ -341,7 +337,6 @@ trait ControlOperationsInterpreter {
* @return
*/
def removeFirstOpElse[T](tree : BinaryTree[T]) : BinaryTree[T] = {
logger.debug("Binary tree: " + tree)
if (tree.right.isDefined && tree.right.get.value == Some(OP_ELSE)) {
Node(tree.value.get,tree.left.getOrElse(Empty),tree.right.get.right.getOrElse(Empty))
} else tree
@ -370,6 +365,11 @@ trait ControlOperationsInterpreter {
}
def removeFirstOpIf(tree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
require(tree.value.isDefined && tree.value.get == OP_IF, "Top of the tree must be OP_IF to remove the OP_IF")
tree.right.getOrElse(Empty)
}
/**
* Finds the indexes of our OP_ELSE (if it exists) and our OP_ENDIF
* @param script

View File

@ -112,7 +112,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
val script = List(OP_IF, OP_RESERVED, OP_ENDIF, OP_1)
val (newStack,newScript) = opIf(stack,script)
newStack.isEmpty must be (true)
newScript must be (List(OP_1))
newScript must be (List(OP_ENDIF,OP_1))
}
@ -263,18 +263,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
newScript must be (List(OP_ELSE,OP_1,OP_ENDIF))
}
it must "evaluate this OP_IF OP_ELSE block correctly" in {
//https://gist.github.com/Christewart/381dc1dbbb07e62501c3
val stack = List(OP_0)
val script = List(OP_IF, OP_1, OP_IF, OP_RETURN, OP_ELSE, OP_RETURN, OP_ELSE, OP_RETURN, OP_ENDIF,
OP_ELSE, OP_1, OP_IF, OP_1, OP_ELSE, OP_RETURN, OP_ELSE, OP_1, OP_ENDIF, OP_ELSE, OP_RETURN, OP_ENDIF, OP_ADD, OP_2, OP_EQUAL)
val (newStack,newScript) = opIf(stack,script)
newStack.isEmpty must be (true)
newScript must be (List(OP_ADD, OP_2, OP_EQUAL))
}
it must "evalute nested OP_IFS correctly" in {
val stack = List(OP_1,OP_1)
@ -283,6 +272,22 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
newStack.head must be (OP_1)
newScript must be (List(OP_IF,OP_0,OP_ELSE,OP_1,OP_ENDIF,OP_ENDIF))
}
it must "evaluate a nested OP_IFs OP_ELSES correctly when the stack top is 0" in {
//https://gist.github.com/Christewart/381dc1dbbb07e62501c3
val stack = List(OP_0)
//"0", "IF 1 IF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 1 IF 1 ELSE
// RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL"
val script = List(OP_IF,OP_1, OP_IF,OP_RETURN, OP_ELSE, OP_RETURN, OP_ELSE, OP_RETURN,OP_ENDIF, OP_ELSE, OP_1,OP_IF,OP_1,OP_ELSE,
OP_RETURN,OP_ELSE,OP_1,OP_ENDIF, OP_ELSE,OP_RETURN,OP_ENDIF,OP_ADD,OP_2,OP_EQUAL)
val (newStack,newScript) = opIf(stack,script)
newStack.isEmpty must be (true)
newScript must be (List(OP_ELSE, OP_1,OP_IF,OP_1,OP_ELSE,
OP_RETURN,OP_ELSE,OP_1,OP_ENDIF, OP_ELSE,OP_RETURN,OP_ENDIF,OP_ADD,OP_2,OP_EQUAL))
}
}

View File

@ -44,14 +44,13 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
val source = scala.io.Source.fromFile("src/test/scala/org/scalacoin/script/interpreter/script_valid.json")
//use this to represent a single test case from script_valid.json
/* val lines =
val lines =
"""
|
|[["'' 1",
"IF SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ELSE ELSE SHA1 ENDIF 0x14 0x68ca4fec736264c13b859bac43d5173df6871682 EQUAL", "P2SH,STRICTENC", "Multiple ELSE's are valid and executed inverts on each ELSE encountered"]]
""".stripMargin*/
|[["0", "IF 1 IF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 1 IF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", "P2SH,STRICTENC", "Nested ELSE ELSE"]]
""".stripMargin
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
//val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson
val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]]
val testCases : Seq[CoreTestCase] = testCasesOpt.flatten