Repository: terryyin/lizard

Issue:
a non-structural failure case in NS metric
Hi,

I am currently running NS metric on my repository and faced a bug return statement.
The following unit test clarifies my claim:

```
def test_non_structure_braces_with_return(self):
    """return statements in non-structural nesting level may confuse the nesting level."""
    result = process_cpp("""
    x c() {
        if (a)
            if (b)
                return false;

        if (c)
            if (d)
                return false;
    }
    """)
    self.assertEqual(3, result[0].max_nested_structures) # should be valued 2
```

commit: 23ec9e8e0091bf24a13d30b72fbe4df5b77b971a

Looking forward to hearing from you regarding this issue.


Discussion:

Patch:
diff --git a/lizard_ext/lizardns.py b/lizard_ext/lizardns.py
index fe24dfc..3330550 100644
--- a/lizard_ext/lizardns.py
+++ b/lizard_ext/lizardns.py
@@ -39,7 +39,7 @@ class LizardExtension(object):  # pylint: disable=R0903
         If the following contract for the nesting level metric does not hold,
         this implementation of nested structure counting is invalid.
 
-        If a control structure has started its block (eg. '{'),
+        If a control structure has started its block (e.g., '{'),
         and its level is **less** than the next structure,
         the next structure is nested.
 
@@ -107,14 +107,13 @@ class LizardExtension(object):  # pylint: disable=R0903
                 structure_stack.pop()
 
         for token in tokens:
+            yield token
             cur_level = reader.context.current_nesting_level
             if token in structures:
                 add_nested_structure(token)
             else:
                 pop_nested_structure()
 
-            yield token
-
 
 def _init_nested_structure_data(self, *_):
     self.max_nested_structures = 0
diff --git a/lizard_languages/clike.py b/lizard_languages/clike.py
index 20b683f..bf97d97 100644
--- a/lizard_languages/clike.py
+++ b/lizard_languages/clike.py
@@ -88,50 +88,69 @@ class CLikeNestingStackStates(CodeStateMachine):
     The handling of these complex cases is unspecified and can be ignored.
     """
 
-    # Beasts that can be defined within one line without braces.
-    __braceless_structures = set(['if', 'else', 'for', 'while', 'do',
-                                  'switch'])
-    __paren_count = 0  # Used only to tackle the beasts.
-    __braceless = None  # Applies only to the beasts.
-    __structure_brace_stack = []  # Boolean stack for structures' brace states.
-
-    def __pop_braceless_structures(self):
-        """Pops structures up to the one with braces."""
+    __structures = set(["if", "else", "for", "while", "do", "switch",
+                        "try", "catch"])
+    # Structures paired on the same nesting level.
+    __paired_structures = {"if": "else", "try": "catch", "catch": "catch",
+                           "do": "while"}
+    __wait_for_pair = False  # Wait for the pair structure to close the level.
+    __structure_brace_stack = []  # Structure and brace states.
+
+    def __pop_without_pair(self):
+        """Continue poping nesting levels without the pair."""
+        self.__wait_for_pair = False
+        while (self.__structure_brace_stack and
+               self.__structure_brace_stack[-1]):
+            structure = self.__structure_brace_stack.pop()
+            self.context.pop_nesting()
+            if structure in self.__paired_structures:
+                self.__wait_for_pair = self.__paired_structures[structure]
+                return
+
+    def __pop_structures(self):
+        """Pops structures up to the one with braces or a waiting pair."""
         self.context.pop_nesting()
-        is_structure = None
+        structure = None
         if self.__structure_brace_stack:
-            is_structure = self.__structure_brace_stack.pop()
+            structure = self.__structure_brace_stack.pop()
 
-        while (is_structure is not None and self.__structure_brace_stack and
-                self.__structure_brace_stack[-1]):
-            self.__structure_brace_stack.pop()
-            self.context.pop_nesting()
+        if structure is None:
+            return
+        if structure in self.__paired_structures:
+            self.__wait_for_pair = self.__paired_structures[structure]
+            return
+        self.__pop_without_pair()
 
     def __else_if_structure(self, token):
         """Handles possible compound 'else if' after 'else' token."""
         self._state = self.__declare_structure
-        if token != "if":
+        if token == "if":
+            self.__structure_brace_stack[-1] = "if"
+        else:
             self._state(token)
 
+    @CodeStateMachine.read_inside_brackets_then("()")
     def __declare_structure(self, token):
         """Ignores structures between parentheses on structure declaration."""
-        if token == "(":
-            self.__paren_count += 1
-        elif token == ")":
-            # assert self.__paren_count > 0
-            self.__paren_count -= 1
-        elif self.__paren_count == 0:
-            self._state = self._state_global
-            if token == "{":
-                self.__braceless = False
-            else:
-                self.__braceless = True
-                self.context.add_bare_nesting()
-                self.__structure_brace_stack.append(True)
+        self.context.add_bare_nesting()
+        self._state = self._state_structure
+        if token != ")":
+            self._state(token)
+
+    def _state_structure(self, token):
+        """Control-flow structure states right before the body."""
+        self._state = self._state_global
+        if token == "{":
+            self.context.add_bare_nesting()
+            self.__structure_brace_stack.append(False)
+        else:
             self._state(token)
 
     def _state_global(self, token):
         """Dual-purpose state for global and structure bodies."""
+        while self.__wait_for_pair and token != self.__wait_for_pair:
+            self.__pop_without_pair()
+
         if token == "template":
             self._state = self._template_declaration
 
@@ -140,16 +159,15 @@ class CLikeNestingStackStates(CodeStateMachine):
 
         elif token == "{":
             self.context.add_bare_nesting()
-            self.__structure_brace_stack.append(self.__braceless)
-            self.__braceless = None
+            self.__structure_brace_stack.append(None)  # Non-structure braces.
 
         elif token == '}' or (token == ";" and self.__structure_brace_stack and
                               self.__structure_brace_stack[-1]):
-            self.__braceless = None
-            self.__pop_braceless_structures()
+            self.__pop_structures()
 
-        elif token in self.__braceless_structures:
-            # assert self.__paren_count == 0
+        elif token in self.__structures:
+            self.__wait_for_pair = False
+            self.__structure_brace_stack.append(token)
             if token == "else":
                 self._state = self.__else_if_structure
             else:


Test patch:
diff --git a/test/testNestedStructures.py b/test/testNestedStructures.py
index 2f77547..5e565fb 100644
--- a/test/testNestedStructures.py
+++ b/test/testNestedStructures.py
@@ -175,6 +175,32 @@ class TestCppNestedStructures(unittest.TestCase):
         self.assertEqual(2, result[0].max_nested_structures)
         self.assertEqual(2, result[1].max_nested_structures)
 
+    def test_braceless_nested_if_try_structures(self):
+        result = process_cpp("""
+        x c() {
+          if (a)
+            try {
+              throw 42;
+            } catch(...) {
+              if (b) return 42;
+            }
+        }
+        """)
+        self.assertEqual(3, result[0].max_nested_structures)
+
+    def test_braceless_nested_for_try_structures(self):
+        result = process_cpp("""
+        x c() {
+          for (;;)
+            try {
+              throw 42;
+            } catch(...) {
+              if (b) return 42;
+            }
+        }
+        """)
+        self.assertEqual(3, result[0].max_nested_structures)
+
     def test_switch_case(self):
         """Switch-Case is one control structure."""
         result = process_cpp("""
@@ -228,6 +254,74 @@ class TestCppNestedStructures(unittest.TestCase):
         """)
         self.assertEqual(3, result[0].max_nested_structures)
 
+    def test_braceless_consecutive_if_structures(self):
+        """Braceless structures one after another."""
+        result = process_cpp("""
+        x c() {
+          if (a)
+            if (b)
+                foobar();
+          if (c)
+            if (d)
+                baz();
+        }
+        """)
+        self.assertEqual(2, result[0].max_nested_structures)
+
+    def test_braceless_consecutive_for_if_structures(self):
+        """Braceless structures one after another."""
+        result = process_cpp("""
+        x c() {
+          for (;;)
+            for (;;)
+                foobar();
+          if (c)
+            if (d)
+                baz();
+        }
+        """)
+        self.assertEqual(2, result[0].max_nested_structures)
+
+    def test_braceless_consecutive_if_structures_with_return(self):
+        """Braceless structures one after another."""
+        result = process_cpp("""
+        x c() {
+          if (a)
+            if (b)
+                return true;
+          if (c)
+            if (d)
+                return false;
+        }
+        """)
+        self.assertEqual(2, result[0].max_nested_structures)
+
+    def test_braceless_nested_if_else_structures(self):
+        result = process_cpp("""
+        x c() {
+          if (a)
+            if (b) {
+              return b;
+            } else {
+              if (b) return 42;
+            }
+        }
+        """)
+        self.assertEqual(3, result[0].max_nested_structures)
+
+    def test_braceless_nested_if_else_if_structures(self):
+        result = process_cpp("""
+        x c() {
+          if (a)
+            if (b) {
+              return b;
+            } else if (c) {
+              if (b) return 42;
+            }
+        }
+        """)
+        self.assertEqual(3, result[0].max_nested_structures)
+
     @unittest.skip("Unspecified. Not Implemented. Convoluted.")
     def test_struct_inside_declaration(self):
         """Extra complexity class/struct should be ignored."""