Checker: Support IF, ELIF, ELSE, FI

It is now possible to add conditional statements in tests in the
following form:

/// CHECK-IF:   condition1
///             CHECK: foobar01
/// CHECK-ELIF: condition2
///             CHECK: foobar02
/// CHECK-ELSE:
///             CHECK: foobar03
/// CHECK-FI:

- Conditions are Python statements evaluated with `eval`.
- They can contain references to previously defined variables
  (<<MyVar>>).
- Nested branching is supported.

Credits: the initial implementation of the patch was written by David
Brazdil (dbrazdil@google.com). It incuded support for IF, ELSE and FI.
Furthermore, this patch includes a test case
(2231-checker-heap-poisoning) mostly written by Roland Levillain
(rpl@google.com).
The CL adds support for ELIF, CHECK-NEXT and CHECK-DAG in branches,
tests and documentation.

Author:    Fabio Rinaldi
Committer: Artem Serov

Test: art/tools/checker/run_unit_tests.py
Test: test.py --target --optimizing with tweaks to env
      ART_HEAP_POISONING (set it to True or False) and
      ART_READ_BARRIER_TYPE (set it equal or not equal to 'TABLELOOKUP')
Test: test.py --host --optimizing with the same tweaks
Bug: 147876827
Change-Id: I73f87781b9e7862d5735c6160ac351610fc9bd92
diff --git a/test/2231-checker-heap-poisoning/expected.txt b/test/2231-checker-heap-poisoning/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/2231-checker-heap-poisoning/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/2231-checker-heap-poisoning/info.txt b/test/2231-checker-heap-poisoning/info.txt
new file mode 100644
index 0000000..81d7381
--- /dev/null
+++ b/test/2231-checker-heap-poisoning/info.txt
@@ -0,0 +1 @@
+Exercise Checker support for branching with heap poisoning.
diff --git a/test/2231-checker-heap-poisoning/src/Main.java b/test/2231-checker-heap-poisoning/src/Main.java
new file mode 100644
index 0000000..46b0f7c
--- /dev/null
+++ b/test/2231-checker-heap-poisoning/src/Main.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+public class Main {
+  Object field;
+
+  /// CHECK-START: java.lang.Object Main.testGetField() builder (after)
+  /// CHECK:       <<This:l\d+>>  ParameterValue
+  /// CHECK:                      InstanceFieldGet [<<This>>] field_name:Main.field
+
+  /// CHECK-START-ARM: java.lang.Object Main.testGetField() disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:  <<This:l\d+>>  ParameterValue
+  ///           CHECK:  <<Ref:l\d+>>   InstanceFieldGet [<<This>>] field_name:Main.field
+  ///           CHECK:                 ldr <<RefReg:r([0-8]|10|11)>>, [r1, #8]
+  ///           CHECK:                 rsbs <<RefReg>>, #0
+  ///           CHECK:                 Return [<<Ref>>]
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: rsbs {{r\d+}}, #0
+  /// CHECK-FI:
+
+  /// CHECK-START-ARM64: java.lang.Object Main.testGetField() disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:  <<This:l\d+>>  ParameterValue
+  ///           CHECK:  <<Ref:l\d+>>   InstanceFieldGet [<<This>>] field_name:Main.field
+  ///           CHECK:                 ldr w0, [x1, #8]
+  ///           CHECK:                 neg w0, w0
+  ///           CHECK:                 Return [<<Ref>>]
+  ///           CHECK:                 ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{w\d+}}, {{w\d+}}
+  /// CHECK-FI:
+
+  /// CHECK-START-X86: java.lang.Object Main.testGetField() disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:  <<This:l\d+>>  ParameterValue
+  ///           CHECK:  <<Ref:l\d+>>   InstanceFieldGet [<<This>>] field_name:Main.field
+  ///           CHECK:                 mov eax, [ecx + 8]
+  ///           CHECK:                 neg eax
+  ///           CHECK:                 Return [<<Ref>>]
+  ///           CHECK:                 ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{[a-z]+}}
+  /// CHECK-FI:
+
+  /// CHECK-START-X86_64: java.lang.Object Main.testGetField() disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:  <<This:l\d+>>  ParameterValue
+  ///           CHECK:  <<Ref:l\d+>>   InstanceFieldGet [<<This>>] field_name:Main.field
+  ///           CHECK:                 mov eax, [rsi + 8]
+  ///           CHECK:                 neg eax
+  ///           CHECK:                 Return [<<Ref>>]
+  ///           CHECK:                 ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{[a-z]+}}
+  /// CHECK-FI:
+
+  Object testGetField() {
+    return field;
+  }
+
+  /// CHECK-START: void Main.testSetField(java.lang.Object) builder (after)
+  /// CHECK:       <<This:l\d+>>  ParameterValue
+  /// CHECK:       <<Arg:l\d+>>   ParameterValue
+  /// CHECK:                      InstanceFieldSet [<<This>>,<<Arg>>] field_name:Main.field
+
+  /// CHECK-START-ARM: void Main.testSetField(java.lang.Object) disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:      <<This:l\d+>>  ParameterValue
+  ///           CHECK:      <<Arg:l\d+>>   ParameterValue
+  ///           CHECK:                     InstanceFieldSet [<<This>>,<<Arg>>] field_name:Main.field
+  ///           CHECK-NEXT:                mov <<Temp:r([0-8]|10|11)>>, r2
+  ///           CHECK-NEXT:                rsbs <<Temp>>, #0
+  ///           CHECK-NEXT:                str <<Temp>>, [r1, #8]
+  ///           CHECK:                     ReturnVoid
+  ///           CHECK-NEXT:                bx lr
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: rsbs {{r\d+}}, #0
+  /// CHECK-FI:
+
+  /// CHECK-START-ARM64: void Main.testSetField(java.lang.Object) disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:      <<This:l\d+>>  ParameterValue
+  ///           CHECK:      <<Arg:l\d+>>   ParameterValue
+  ///           CHECK:                     InstanceFieldSet [<<This>>,<<Arg>>] field_name:Main.field
+  ///           CHECK-NEXT:                mov <<Temp:w1[67]>>, w2
+  ///           CHECK-NEXT:                neg <<Temp>>, <<Temp>>
+  ///           CHECK-NEXT:                str <<Temp>>, [x1, #8]
+  ///           CHECK:                     ReturnVoid
+  ///           CHECK-NEXT:                ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{w\d+}}, {{w\d+}}
+  /// CHECK-FI:
+
+  /// CHECK-START-X86: void Main.testSetField(java.lang.Object) disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:      <<This:l\d+>>  ParameterValue
+  ///           CHECK:      <<Arg:l\d+>>   ParameterValue
+  ///           CHECK:                     ParallelMove
+  ///           CHECK-NEXT:                mov eax, ecx
+  ///           CHECK:                     InstanceFieldSet [<<This>>,<<Arg>>] field_name:Main.field
+  ///           CHECK-NEXT:                mov <<Temp:e([acdb]x|bp|si|di)>>, edx
+  ///           CHECK-NEXT:                neg <<Temp>>
+  ///           CHECK-NEXT:                mov [eax + 8], <<Temp>>
+  ///           CHECK:                     ReturnVoid
+  ///           CHECK-NEXT:                ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{[a-z]+}}
+  /// CHECK-FI:
+
+  /// CHECK-START-X86_64: void Main.testSetField(java.lang.Object) disassembly (after)
+  /// CHECK-IF: os.environ.get('ART_HEAP_POISONING') == 'true'
+  ///      CHECK-IF: os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///           CHECK:      <<This:l\d+>>  ParameterValue
+  ///           CHECK:      <<Arg:l\d+>>   ParameterValue
+  ///           CHECK:                     InstanceFieldSet [<<This>>,<<Arg>>] field_name:Main.field
+  ///           CHECK-NEXT:                mov <<Temp:e([acdb]x|bp|si|di)>>, edx
+  ///           CHECK-NEXT:                neg <<Temp>>
+  ///           CHECK-NEXT:                mov [rsi + 8], <<Temp>>
+  ///           CHECK:                     ReturnVoid
+  ///           CHECK-NEXT:                ret
+  ///      CHECK-FI:
+  /// CHECK-ELSE:
+  ///      CHECK-NOT: neg {{[a-z]+}}
+  /// CHECK-FI:
+
+  void testSetField(Object o) {
+    field = o;
+  }
+
+  public static void main(String[] args) {
+    Main m = new Main();
+    Object o = m.testGetField();
+    m.testSetField(o);
+    System.out.println("passed");
+  }
+}
diff --git a/tools/checker/README b/tools/checker/README
index e5b0211..51be828 100644
--- a/tools/checker/README
+++ b/tools/checker/README
@@ -14,7 +14,8 @@
 be listed with the '--list-passes' command-line flag).
 
 Matching of check lines is carried out in the order of appearance in the
-source file. There are five types of check lines:
+source file. There are five types of check lines. Branching instructions are
+also supported and documented later in this file.
  - CHECK:      Must match an output line which appears in the output group
                later than lines matched against any preceeding checks. Output
                lines must therefore match the check lines in the same order.
@@ -83,3 +84,53 @@
 match. An example line looks like:
 
   /// CHECK-START-{X86_64,ARM,ARM64}: int MyClass.MyMethod() constant_folding (after)
+
+
+Branching is possible thanks to the following statements:
+ - CHECK-IF:
+ - CHECK-ELIF:
+ - CHECK-ELSE:
+ - CHECK-FI:
+
+CHECK-IF and CHECK-ELIF take a Python expression as input that will be evaluated by `eval`.
+Like CHECK-EVAL, they support only referencing of variables, defining new variables as part
+of the statement input is not allowed. Any other surrounding text will be passed to Python's `eval`
+as is. CHECK-ELSE and CHECK-FI must not have any input.
+
+Example:
+  /// CHECK-START: int MyClass.MyMethod() constant_folding (after)
+  /// CHECK:        {{i\d+}} IntConstant <<MyConst:(0|1|2)>>
+  /// CHECK-IF:     <<MyConst>> == 0
+  ///               CHECK-NEXT:            FooBar01
+  /// CHECK-ELIF:   <<MyConst>> == 1
+  ///               CHECK-NOT:             FooBar01
+  /// CHECK-FI:
+
+Branch blocks can contain any statement, including CHECK-NEXT and CHECK-DAG.
+Notice the CHECK-NEXT statement within the IF branch. When a CHECK-NEXT is encountered,
+Checker expects that the previously executed statement was either a CHECK or a CHECK-NEXT.
+This condition is enforced at runtime, and an error is thrown if it's not respected.
+
+Statements inside branches can define new variables. If a new variable gets defined inside a branch
+(of any depth, since nested branching is allowed), that variable will become global within the scope
+of the defining group. In other words, it will be valid everywhere after its definition within the
+block defined by the CHECK-START statement. The absence of lexical scoping for Checker variables
+seems a bit inelegant at first, but is probably more practical.
+
+Example:
+  /// CHECK-START: void MyClass.FooBar() liveness (after)
+  /// CHECK-IF:     os.environ.get('ART_READ_BARRIER_TYPE') != 'TABLELOOKUP'
+  ///               CHECK:                 <<MyID:i\d+>> IntConstant 3
+  /// CHECK-ELSE:
+  ///               CHECK:                 <<MyID:i\d+>> IntConstant 5
+  /// CHECK-FI:
+  /// CHECK-NEXT:   Return [<<MyID>>]
+
+Notice that the variable MyID remained valid outside the branch where it was defined.
+Furthermore, in this example, the definition of MyID depends on which branch gets selected at
+runtime. Attempting to re-define a variable or referencing an undefined variable is not allowed,
+Checker will throw a runtime error.
+The example above also shows how we can use environment variables to perform custom checks.
+
+It is possible to combine IF, (multiple) ELIF and ELSE statements together. Nested branching is
+also supported.
diff --git a/tools/checker/file_format/checker/parser.py b/tools/checker/file_format/checker/parser.py
index a1c8523..c42d7de 100644
--- a/tools/checker/file_format/checker/parser.py
+++ b/tools/checker/file_format/checker/parser.py
@@ -115,6 +115,27 @@
   if evalLine is not None:
     return (evalLine, TestStatement.Variant.Eval, lineNo), None, None
 
+  # 'CHECK-IF' lines mark the beginning of a block that will be executed
+  # only if the Python expression that follows evaluates to true.
+  ifLine = __extractLine(prefix + "-IF", line)
+  if ifLine is not None:
+    return (ifLine, TestStatement.Variant.If, lineNo), None, None
+
+  # 'CHECK-ELIF' lines mark the beginning of an `else if` branch of a CHECK-IF block.
+  elifLine = __extractLine(prefix + "-ELIF", line)
+  if elifLine is not None:
+    return (elifLine, TestStatement.Variant.Elif, lineNo), None, None
+
+  # 'CHECK-ELSE' lines mark the beginning of the `else` branch of a CHECK-IF block.
+  elseLine = __extractLine(prefix + "-ELSE", line)
+  if elseLine is not None:
+    return (elseLine, TestStatement.Variant.Else, lineNo), None, None
+
+  # 'CHECK-FI' lines mark the end of a CHECK-IF block.
+  fiLine = __extractLine(prefix + "-FI", line)
+  if fiLine is not None:
+    return (fiLine, TestStatement.Variant.Fi, lineNo), None, None
+
   Logger.fail("Checker statement could not be parsed: '" + line + "'", fileName, lineNo)
 
 def __isMatchAtStart(match):
@@ -134,13 +155,15 @@
       comment symbol and the CHECK-* keyword.
   """
   statement = TestStatement(parent, variant, line, lineNo)
-  isEvalLine = (variant == TestStatement.Variant.Eval)
+
+  if statement.isNoContentStatement() and line:
+    Logger.fail("Expected empty statement: '" + line + "'", statement.fileName, statement.lineNo)
 
   # Loop as long as there is something to parse.
   while line:
     # Search for the nearest occurrence of the special markers.
-    if isEvalLine:
-      # The following constructs are not supported in CHECK-EVAL lines
+    if statement.isEvalContentStatement():
+      # The following constructs are not supported in CHECK-EVAL, -IF and -ELIF lines
       matchWhitespace = None
       matchPattern = None
       matchVariableDefinition = None
@@ -185,7 +208,7 @@
                                 line)
       text = line[0:firstMatch]
       line = line[firstMatch:]
-      if isEvalLine:
+      if statement.isEvalContentStatement():
         statement.addExpression(TestExpression.createPlainText(text))
       else:
         statement.addExpression(TestExpression.createPatternFromPlainText(text))
diff --git a/tools/checker/file_format/checker/struct.py b/tools/checker/file_format/checker/struct.py
index 643beca..540badc 100644
--- a/tools/checker/file_format/checker/struct.py
+++ b/tools/checker/file_format/checker/struct.py
@@ -56,13 +56,6 @@
     return self.parent.fileName
 
   def addStatement(self, new_statement):
-    if new_statement.variant == TestStatement.Variant.NextLine:
-      if not self.statements or \
-         (self.statements[-1].variant != TestStatement.Variant.InOrder and \
-          self.statements[-1].variant != TestStatement.Variant.NextLine):
-        Logger.fail("A next-line statement can only be placed after an "
-                    "in-order statement or another next-line statement.",
-                    new_statement.fileName, new_statement.lineNo)
     self.statements.append(new_statement)
 
   def __eq__(self, other):
@@ -75,7 +68,7 @@
 
   class Variant(object):
     """Supported types of statements."""
-    InOrder, NextLine, DAG, Not, Eval = range(5)
+    InOrder, NextLine, DAG, Not, Eval, If, Elif, Else, Fi = range(9)
 
   def __init__(self, parent, variant, originalText, lineNo):
     assert isinstance(parent, TestCase)
@@ -92,6 +85,21 @@
   def fileName(self):
     return self.parent.fileName
 
+  def isPatternMatchContentStatement(self):
+    return self.variant in [ TestStatement.Variant.InOrder,
+                             TestStatement.Variant.NextLine,
+                             TestStatement.Variant.DAG,
+                             TestStatement.Variant.Not ]
+
+  def isEvalContentStatement(self):
+    return self.variant in [ TestStatement.Variant.Eval,
+                             TestStatement.Variant.If,
+                             TestStatement.Variant.Elif ]
+
+  def isNoContentStatement(self):
+    return self.variant in [ TestStatement.Variant.Else,
+                             TestStatement.Variant.Fi ]
+
   def addExpression(self, new_expression):
     assert isinstance(new_expression, TestExpression)
     if self.variant == TestStatement.Variant.Not:
diff --git a/tools/checker/file_format/checker/test.py b/tools/checker/file_format/checker/test.py
index 4686f33..221c5fb 100644
--- a/tools/checker/file_format/checker/test.py
+++ b/tools/checker/file_format/checker/test.py
@@ -202,7 +202,10 @@
         content = statementEntry[0]
         variant = statementEntry[1]
         statement = TestStatement(testCase, variant, content, 0)
-        statement.addExpression(TestExpression.createPatternFromPlainText(content))
+        if statement.isEvalContentStatement():
+          statement.addExpression(TestExpression.createPlainText(content))
+        elif statement.isPatternMatchContentStatement():
+          statement.addExpression(TestExpression.createPatternFromPlainText(content))
     return testFile
 
   def assertParsesTo(self, checkerText, expectedData):
@@ -252,6 +255,11 @@
         /// CHECK-NOT:  bar
         /// CHECK-DAG:  abc
         /// CHECK-DAG:  def
+        /// CHECK-EVAL: x > y
+        /// CHECK-IF:   x < y
+        /// CHECK-ELIF: x == y
+        /// CHECK-ELSE:
+        /// CHECK-FI:
       """,
       [ ( "Example Group", [ ("foo1", TestStatement.Variant.InOrder),
                              ("foo2", TestStatement.Variant.InOrder),
@@ -259,35 +267,25 @@
                              ("foo4", TestStatement.Variant.NextLine),
                              ("bar", TestStatement.Variant.Not),
                              ("abc", TestStatement.Variant.DAG),
-                             ("def", TestStatement.Variant.DAG) ] ) ])
+                             ("def", TestStatement.Variant.DAG),
+                             ("x > y", TestStatement.Variant.Eval),
+                             ("x < y", TestStatement.Variant.If),
+                             ("x == y", TestStatement.Variant.Elif),
+                             (None, TestStatement.Variant.Else),
+                             (None, TestStatement.Variant.Fi) ] ) ])
 
-  def test_MisplacedNext(self):
+  def test_NoContentStatements(self):
     with self.assertRaises(CheckerException):
       self.parse(
         """
           /// CHECK-START: Example Group
-          /// CHECK-DAG:  foo
-          /// CHECK-NEXT: bar
+          /// CHECK-ELSE:    foo
         """)
     with self.assertRaises(CheckerException):
       self.parse(
         """
           /// CHECK-START: Example Group
-          /// CHECK-NOT:  foo
-          /// CHECK-NEXT: bar
-        """)
-    with self.assertRaises(CheckerException):
-      self.parse(
-        """
-          /// CHECK-START: Example Group
-          /// CHECK-EVAL: foo
-          /// CHECK-NEXT: bar
-        """)
-    with self.assertRaises(CheckerException):
-      self.parse(
-        """
-          /// CHECK-START: Example Group
-          /// CHECK-NEXT: bar
+          /// CHECK-FI:      foo
         """)
 
 class CheckerParser_SuffixTests(unittest.TestCase):
@@ -298,6 +296,11 @@
                   /// CHECK-NEXT:  bar
                   /// CHECK-NOT:   baz
                   /// CHECK-DAG:   yoyo
+                  /// CHECK-EVAL: x > y
+                  /// CHECK-IF:   x < y
+                  /// CHECK-ELIF: x == y
+                  /// CHECK-ELSE:
+                  /// CHECK-FI:
                 """
 
   arch_block = """
@@ -306,6 +309,11 @@
                   /// CHECK-NEXT:  bar
                   /// CHECK-NOT:   baz
                   /// CHECK-DAG:   yoyo
+                  /// CHECK-EVAL: x > y
+                  /// CHECK-IF:   x < y
+                  /// CHECK-ELIF: x == y
+                  /// CHECK-ELSE:
+                  /// CHECK-FI:
                 """
 
   def parse(self, checkerText):
@@ -315,7 +323,7 @@
     for arch in [None] + archs_list:
       checkerFile = self.parse(self.noarch_block)
       self.assertEqual(len(checkerFile.testCases), 1)
-      self.assertEqual(len(checkerFile.testCases[0].statements), 4)
+      self.assertEqual(len(checkerFile.testCases[0].statements), 9)
 
   def test_IgnoreNonTargetArch(self):
     for targetArch in archs_list:
@@ -332,7 +340,7 @@
       checkerFile = self.parse(checkerText)
       self.assertEqual(len(checkerFile.testCases), 1)
       self.assertEqual(len(checkerFile.testCasesForArch(arch)), 1)
-      self.assertEqual(len(checkerFile.testCases[0].statements), 4)
+      self.assertEqual(len(checkerFile.testCases[0].statements), 9)
 
   def test_NoDebugAndArch(self):
     testCase = self.parse("""
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 1db4737..37d0195 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -28,6 +28,134 @@
     self.lineNo = lineNo
     self.variables = variables
 
+class BadStructureException(Exception):
+  def __init__(self, msg, lineNo):
+    self.msg = msg
+    self.lineNo = lineNo
+
+class IfStack:
+  """
+  The purpose of this class is to keep track of which branch the cursor is in.
+  This will let us know if the line read by the cursor should be processed or not.
+  Furthermore, this class contains the methods to handle the CHECK-[IF, ELIF, ELSE, FI]
+  statements, and consequently update the stack with new information.
+
+  The following elements can appear on the stack:
+  - BranchTaken: a branch is taken if its condition evaluates to true and
+    its parent branch was also previously taken.
+  - BranchNotTakenYet: the branch's parent was taken, but this branch wasn't as its
+    condition did not evaluate to true.
+  - BranchNotTaken: a branch is not taken when its parent was either NotTaken or NotTakenYet.
+    It doesn't matter if the condition would evaluate to true, that's not even checked.
+
+  CHECK-IF is the only instruction that pushes a new element on the stack. CHECK-ELIF
+  and CHECK-ELSE will update the top of the stack to keep track of what's been seen.
+  That means that we can check if the line currently pointed to by the cursor should be
+  processed just by looking at the top of the stack.
+  CHECK-FI will pop the last element.
+
+  `BranchTaken`, `BranchNotTaken`, `BranchNotTakenYet` are implemented as positive integers.
+  Negated values of `BranchTaken` and `BranchNotTaken` may be appear; `-BranchTaken` and
+  `-BranchNotTaken` have the same meaning as `BranchTaken` and `BranchNotTaken`
+  (respectively), but they indicate that we went past the ELSE branch. Knowing that, we can
+  output a precise error message if the user creates a malformed branching structure.
+  """
+
+  BranchTaken, BranchNotTaken, BranchNotTakenYet = range(1, 4)
+
+  def __init__(self):
+    self.stack = []
+
+  def CanExecute(self):
+    """
+    Returns true if we're not in any branch, or the branch we're
+    currently in was taken.
+    """
+    if self.__isEmpty():
+      return True
+    return abs(self.__peek()) == IfStack.BranchTaken
+
+  def Handle(self, statement, variables):
+    """
+    This function is invoked if the cursor is pointing to a
+    CHECK-[IF, ELIF, ELSE, FI] line.
+    """
+    variant = statement.variant
+    if variant is TestStatement.Variant.If:
+      self.__if(statement, variables)
+    elif variant is TestStatement.Variant.Elif:
+      self.__elif(statement, variables)
+    elif variant is TestStatement.Variant.Else:
+      self.__else(statement)
+    else:
+      assert variant is TestStatement.Variant.Fi
+      self.__fi(statement)
+
+  def Eof(self):
+    """
+    The last line the cursor points to is always EOF.
+    """
+    if not self.__isEmpty():
+      raise BadStructureException("Missing CHECK-FI", -1)
+
+  def __isEmpty(self):
+    return len(self.stack) == 0
+
+  def __if(self, statement, variables):
+    if not self.__isEmpty() and abs(self.__peek()) in [ IfStack.BranchNotTaken,
+                                                        IfStack.BranchNotTakenYet ]:
+      self.__push(IfStack.BranchNotTaken)
+    elif EvaluateLine(statement, variables):
+      self.__push(IfStack.BranchTaken)
+    else:
+      self.__push(IfStack.BranchNotTakenYet)
+
+  def __elif(self, statement, variables):
+    if self.__isEmpty():
+      raise BadStructureException("CHECK-ELIF must be after CHECK-IF or CHECK-ELIF",
+                                  statement.lineNo)
+    if self.__peek() < 0:
+      raise BadStructureException("CHECK-ELIF cannot be after CHECK-ELSE", statement.lineNo)
+    if self.__peek() == IfStack.BranchTaken:
+      self.__setLast(IfStack.BranchNotTaken)
+    elif self.__peek() == IfStack.BranchNotTakenYet:
+      if EvaluateLine(statement, variables):
+        self.__setLast(IfStack.BranchTaken)
+      # else, the CHECK-ELIF condition is False, so do nothing: the last element on the stack is
+      # already set to BranchNotTakenYet.
+    else:
+      assert self.__peek() == IfStack.BranchNotTaken
+
+  def __else(self, statement):
+    if self.__isEmpty():
+      raise BadStructureException("CHECK-ELSE must be after CHECK-IF or CHECK-ELIF",
+                                  statement.lineNo)
+    if self.__peek() < 0:
+      raise BadStructureException("Consecutive CHECK-ELSE statements", statement.lineNo)
+    if self.__peek() in [ IfStack.BranchTaken, IfStack.BranchNotTaken ]:
+      # Notice that we're setting -BranchNotTaken rather that BranchNotTaken as we went past the
+      # ELSE branch.
+      self.__setLast(-IfStack.BranchNotTaken)
+    else:
+      assert self.__peek() == IfStack.BranchNotTakenYet
+      # Setting -BranchTaken rather BranchTaken for the same reason.
+      self.__setLast(-IfStack.BranchTaken)
+
+  def __fi(self, statement):
+    if self.__isEmpty():
+      raise BadStructureException("CHECK-FI does not have a matching CHECK-IF", statement.lineNo)
+    self.stack.pop()
+
+  def __peek(self):
+    assert not self.__isEmpty()
+    return self.stack[-1]
+
+  def __push(self, element):
+    self.stack.append(element)
+
+  def __setLast(self, element):
+    self.stack[-1] = element
+
 def findMatchingLine(statement, c1Pass, scope, variables, excludeLines=[]):
   """ Finds the first line in `c1Pass` which matches `statement`.
 
@@ -54,6 +182,8 @@
     self.variables = ImmutableDict(variables)
     self.dagQueue = []
     self.notQueue = []
+    self.ifStack = IfStack()
+    self.lastVariant = None
 
   def moveCursor(self, match):
     assert self.cursor <= match.scope.end
@@ -127,6 +257,11 @@
 
     Raises MatchFailedException if the current line does not match.
     """
+    if self.lastVariant not in [ TestStatement.Variant.InOrder, TestStatement.Variant.NextLine ]:
+      raise BadStructureException("A next-line statement can only be placed "
+                  "after an in-order statement or another next-line statement.",
+                  statement.lineNo)
+
     scope = MatchScope(self.cursor, self.cursor + 1)
     match = findMatchingLine(statement, self.c1Pass, scope, self.variables)
     self.moveCursor(match)
@@ -142,6 +277,19 @@
   def handle(self, statement):
     variant = None if statement is None else statement.variant
 
+    if variant in [ TestStatement.Variant.If,
+                    TestStatement.Variant.Elif,
+                    TestStatement.Variant.Else,
+                    TestStatement.Variant.Fi ]:
+      self.ifStack.Handle(statement, self.variables)
+      return
+
+    if variant is None:
+      self.ifStack.Eof()
+
+    if not self.ifStack.CanExecute():
+      return
+
     # First non-DAG statement always triggers execution of any preceding
     # DAG statements.
     if variant is not TestStatement.Variant.DAG:
@@ -161,6 +309,8 @@
       assert variant is TestStatement.Variant.Eval
       self.handleEval(statement)
 
+    self.lastVariant = variant
+
 def MatchTestCase(testCase, c1Pass):
   """ Runs a test case against a C1visualizer graph dump.
 
diff --git a/tools/checker/match/line.py b/tools/checker/match/line.py
index 352762d..49b55de 100644
--- a/tools/checker/match/line.py
+++ b/tools/checker/match/line.py
@@ -15,6 +15,7 @@
 from common.logger              import Logger
 from file_format.checker.struct import TestExpression, TestStatement
 
+import os
 import re
 
 def headAndTail(list):
@@ -109,7 +110,7 @@
     return getVariable(expression.name, variables, pos)
 
 def EvaluateLine(checkerLine, variables):
-  assert checkerLine.variant == TestStatement.Variant.Eval
+  assert checkerLine.isEvalContentStatement()
   eval_string = "".join(map(lambda expr: getEvalText(expr, variables, checkerLine),
                             checkerLine.expressions))
   return eval(eval_string)
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
index e0d25ef..01724f0 100644
--- a/tools/checker/match/test.py
+++ b/tools/checker/match/test.py
@@ -18,7 +18,8 @@
 from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
 from file_format.checker.parser      import ParseCheckerStream, ParseCheckerStatement
 from file_format.checker.struct      import CheckerFile, TestCase, TestStatement
-from match.file                      import MatchTestCase, MatchFailedException
+from match.file                      import MatchTestCase, MatchFailedException, \
+                                            BadStructureException
 from match.line                      import MatchLines
 
 import io
@@ -130,6 +131,10 @@
     with self.assertRaises(MatchFailedException):
       self.assertMatches(checkerString, c1String)
 
+  def assertBadStructure(self, checkerString, c1String):
+    with self.assertRaises(BadStructureException):
+      self.assertMatches(checkerString, c1String)
+
   def test_Text(self):
     self.assertMatches("/// CHECK: foo bar", "foo bar")
     self.assertDoesNotMatch("/// CHECK: foo bar", "abc def")
@@ -364,6 +369,39 @@
       foo
       def
     """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK-NOT:  foo
+      /// CHECK-EVAL: 1 + 1 == 2
+      /// CHECK:      bar
+    """,
+    """
+      foo
+      abc
+      bar
+    """);
+    self.assertMatches(
+    """
+      /// CHECK-DAG:  bar
+      /// CHECK-DAG:  abc
+      /// CHECK-NOT:  foo
+    """,
+    """
+      foo
+      abc
+      bar
+    """);
+    self.assertDoesNotMatch(
+    """
+      /// CHECK-DAG:  abc
+      /// CHECK-DAG:  foo
+      /// CHECK-NOT:  bar
+    """,
+    """
+      foo
+      abc
+      bar
+    """);
 
   def test_LineOnlyMatchesOnce(self):
     self.assertMatches(
@@ -400,3 +438,481 @@
                      """
     self.assertMatches(twoVarTestCase, "42 41");
     self.assertDoesNotMatch(twoVarTestCase, "42 43")
+
+  def test_MisplacedNext(self):
+    self.assertBadStructure(
+      """
+        /// CHECK-DAG:  foo
+        /// CHECK-NEXT: bar
+      """,
+      """
+      foo
+      bar
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-NOT:  foo
+        /// CHECK-NEXT: bar
+      """,
+      """
+      foo
+      bar
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-EVAL: True
+        /// CHECK-NEXT: bar
+      """,
+      """
+      foo
+      bar
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-NEXT: bar
+      """,
+      """
+      foo
+      bar
+      """)
+
+  def test_EnvVariableEval(self):
+    self.assertMatches(
+    """
+      /// CHECK-IF: os.environ.get('MARTY_MCFLY') != '89mph!'
+      /// CHECK-FI:
+    """,
+    """
+    foo
+    """
+    )
+    self.assertMatches(
+    """
+      /// CHECK-EVAL: os.environ.get('MARTY_MCFLY') != '89mph!'
+    """,
+    """
+    foo
+    """
+    )
+
+  def test_IfStatements(self):
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-NEXT: foo2
+      /// CHECK-FI:
+      /// CHECK-NEXT: foo3
+      /// CHECK-NEXT: bar
+    """,
+    """
+    foo1
+    foo2
+    foo3
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-DAG:    foo2
+      /// CHECK-FI:
+      /// CHECK-DAG:    bar
+      /// CHECK: foo3
+    """,
+    """
+    foo1
+    bar
+    foo2
+    foo3
+    """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT: foo2
+      /// CHECK-FI:
+      /// CHECK-NEXT: foo3
+    """,
+    """
+    foo1
+    foo2
+    foo3
+    """)
+
+  def test_IfElseStatements(self):
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELSE:
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo2
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELSE:
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo3
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELSE:
+      ///   CHECK-DAG:    bar
+      /// CHECK-FI:
+      /// CHECK-DAG:    foo3
+      /// CHECK: foo4
+    """,
+    """
+    foo1
+    foo3
+    bar
+    foo4
+    """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELSE:
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo2
+    bar
+    """)
+
+  def test_IfElifElseStatements(self):
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo4
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo2
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELIF: False
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo4
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo4
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo4
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo3
+    bar
+    """)
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELIF: False
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-ELIF: False
+      ///   CHECK-NEXT:    foo4
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    bar
+    """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: False
+      ///   CHECK-NEXT:    foo2
+      /// CHECK-ELIF: True
+      ///   CHECK-NEXT:    foo3
+      /// CHECK-ELSE:
+      ///   CHECK-NEXT:    foo4
+      /// CHECK-FI:
+      /// CHECK-NEXT:    bar
+    """,
+    """
+    foo1
+    foo2
+    bar
+    """)
+
+  def test_NestedBranching(self):
+    self.assertMatches(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-IF: True
+      ///     CHECK-NEXT:    foo2
+      ///   CHECK-ELSE:
+      ///     CHECK-NEXT:    foo3
+      ///   CHECK-FI:
+      /// CHECK-ELSE:
+      ///   CHECK-IF: True
+      ///     CHECK-NEXT:    foo4
+      ///   CHECK-ELSE:
+      ///     CHECK-NEXT:    foo5
+      ///   CHECK-FI:
+      /// CHECK-FI:
+      /// CHECK-NEXT: foo6
+    """,
+    """
+    foo1
+    foo2
+    foo6
+    """)
+    self.assertMatches(
+    """
+      /// CHECK-IF: True
+      ///   CHECK-IF: False
+      ///     CHECK:    foo1
+      ///   CHECK-ELSE:
+      ///     CHECK:    foo2
+      ///   CHECK-FI:
+      /// CHECK-ELSE:
+      ///   CHECK-IF: True
+      ///     CHECK:    foo3
+      ///   CHECK-ELSE:
+      ///     CHECK:    foo4
+      ///   CHECK-FI:
+      /// CHECK-FI:
+    """,
+    """
+    foo2
+    """)
+    self.assertMatches(
+    """
+      /// CHECK-IF: False
+      ///   CHECK-IF: True
+      ///     CHECK:    foo1
+      ///   CHECK-ELSE:
+      ///     CHECK:    foo2
+      ///   CHECK-FI:
+      /// CHECK-ELSE:
+      ///   CHECK-IF: False
+      ///     CHECK:    foo3
+      ///   CHECK-ELSE:
+      ///     CHECK-IF: False
+      ///       CHECK:    foo4
+      ///     CHECK-ELSE:
+      ///       CHECK: foo5
+      ///     CHECK-FI:
+      ///   CHECK-FI:
+      /// CHECK-FI:
+    """,
+    """
+    foo5
+    """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK: foo1
+      /// CHECK-IF: True
+      ///   CHECK-IF: False
+      ///     CHECK-NEXT:    foo2
+      ///   CHECK-ELSE:
+      ///     CHECK-NEXT:    foo3
+      ///   CHECK-FI:
+      /// CHECK-NEXT: foo6
+    """,
+    """
+    foo1
+    foo2
+    foo6
+    """)
+
+  def test_VariablesInBranches(self):
+    self.assertMatches(
+    """
+      /// CHECK-IF: True
+      ///   CHECK: foo<<VarA:\d+>>
+      /// CHECK-FI:
+      /// CHECK-EVAL: <<VarA>> == 12
+    """,
+    """
+    foo12
+    """)
+    self.assertDoesNotMatch(
+    """
+      /// CHECK-IF: True
+      ///   CHECK: foo<<VarA:\d+>>
+      /// CHECK-FI:
+      /// CHECK-EVAL: <<VarA>> == 99
+    """,
+    """
+    foo12
+    """)
+    self.assertMatches(
+    """
+      /// CHECK-IF: True
+      ///   CHECK: foo<<VarA:\d+>>
+      ///   CHECK-IF: <<VarA>> == 12
+      ///     CHECK: bar<<VarB:M|N>>
+      ///   CHECK-FI:
+      /// CHECK-FI:
+      /// CHECK-EVAL: "<<VarB>>" == "M"
+    """,
+    """
+    foo12
+    barM
+    """)
+    self.assertMatches(
+    """
+      /// CHECK-IF: False
+      ///   CHECK: foo<<VarA:\d+>>
+      /// CHECK-ELIF: True
+      ///   CHECK: foo<<VarA:M|N>>
+      /// CHECK-FI:
+      /// CHECK-EVAL: "<<VarA>>" == "M"
+    """,
+    """
+    fooM
+    """)
+    self.assertMatches(
+    """
+      /// CHECK-IF: False
+      ///   CHECK: foo<<VarA:A|B>>
+      /// CHECK-ELIF: False
+      ///   CHECK: foo<<VarA:A|B>>
+      /// CHECK-ELSE:
+      ///   CHECK-IF: False
+      ///     CHECK: foo<<VarA:A|B>>
+      ///   CHECK-ELSE:
+      ///     CHECK: foo<<VarA:M|N>>
+      ///   CHECK-FI:
+      /// CHECK-FI:
+      /// CHECK-EVAL: "<<VarA>>" == "N"
+    """,
+    """
+    fooN
+    """)
+
+  def test_MalformedBranching(self):
+    self.assertBadStructure(
+      """
+        /// CHECK-IF: True
+        /// CHECK: foo
+      """,
+      """
+      foo
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-ELSE:
+        /// CHECK: foo
+      """,
+      """
+      foo
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-IF: True
+        /// CHECK: foo
+        /// CHECK-ELSE:
+      """,
+      """
+      foo
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-IF: True
+        ///   CHECK: foo
+        /// CHECK-ELIF:
+        ///   CHECK: foo
+        ///   CHECK-IF: True
+        ///     CHECK: foo
+        /// CHECK-FI:
+      """,
+      """
+      foo
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-IF: True
+        ///   CHECK: foo
+        /// CHECK-ELSE:
+        ///   CHECK: foo
+        /// CHECK-ELIF:
+        ///   CHECK: foo
+        /// CHECK-FI:
+      """,
+      """
+      foo
+      """)
+    self.assertBadStructure(
+      """
+        /// CHECK-IF: True
+        ///   CHECK: foo
+        /// CHECK-ELSE:
+        ///   CHECK: foo
+        /// CHECK-ELSE:
+        ///   CHECK: foo
+        /// CHECK-FI:
+      """,
+      """
+      foo
+      """)
\ No newline at end of file