Add some stronger checks to TreeNode<>

Self-insertion was not previously detected, nor removal from
null |this| in all cases.

Change-Id: I839d795f0eed1751857bab8432960b9b3f54c372
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/53651
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index f74c3d3..88337ff 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -161,6 +161,7 @@
     "retained_tree_node_unittest.cpp",
     "shared_copy_on_write_unittest.cpp",
     "string_pool_template_unittest.cpp",
+    "tree_node_unittest.cpp",
     "unowned_ptr_unittest.cpp",
     "weak_ptr_unittest.cpp",
     "widestring_unittest.cpp",
diff --git a/core/fxcrt/retained_tree_node_unittest.cpp b/core/fxcrt/retained_tree_node_unittest.cpp
index c41b5ef..3fabb80 100644
--- a/core/fxcrt/retained_tree_node_unittest.cpp
+++ b/core/fxcrt/retained_tree_node_unittest.cpp
@@ -45,6 +45,7 @@
   {
     RetainPtr<ObservableRetainedTreeNodeForTest> ptr =
         pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
+    EXPECT_FALSE(ptr->HasChild(ptr.Get()));
     watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
     EXPECT_TRUE(watcher.Get());
   }
@@ -60,6 +61,8 @@
         pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
     watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
     parent->AppendFirstChild(ptr);
+    EXPECT_FALSE(parent->HasChild(parent.Get()));
+    EXPECT_TRUE(parent->HasChild(ptr.Get()));
     EXPECT_TRUE(watcher.Get());
   }
   EXPECT_TRUE(watcher.Get());
@@ -89,6 +92,8 @@
         pdfium::MakeRetain<ObservableRetainedTreeNodeForTest>();
     watcher = ObservableRetainedTreeNodeForTest::ObservedPtr(ptr.Get());
     parent->AppendLastChild(ptr);
+    EXPECT_FALSE(parent->HasChild(parent.Get()));
+    EXPECT_TRUE(parent->HasChild(ptr.Get()));
     EXPECT_TRUE(watcher.Get());
   }
   {
diff --git a/core/fxcrt/tree_node.h b/core/fxcrt/tree_node.h
index 7bded93..7f1f46a 100644
--- a/core/fxcrt/tree_node.h
+++ b/core/fxcrt/tree_node.h
@@ -23,13 +23,19 @@
   T* GetNextSibling() const { return m_pNextSibling; }
   T* GetPrevSibling() const { return m_pPrevSibling; }
 
+  bool HasChild(const T* child) const {
+    return child != this && child->m_pParent == this;
+  }
+
   void AppendFirstChild(T* child) {
     BecomeParent(child);
     if (m_pFirstChild) {
+      CHECK(m_pLastChild);
       m_pFirstChild->m_pPrevSibling = child;
       child->m_pNextSibling = m_pFirstChild;
       m_pFirstChild = child;
     } else {
+      CHECK(!m_pLastChild);
       m_pFirstChild = child;
       m_pLastChild = child;
     }
@@ -38,10 +44,12 @@
   void AppendLastChild(T* child) {
     BecomeParent(child);
     if (m_pLastChild) {
+      CHECK(m_pFirstChild);
       m_pLastChild->m_pNextSibling = child;
       child->m_pPrevSibling = m_pLastChild;
       m_pLastChild = child;
     } else {
+      CHECK(!m_pFirstChild);
       m_pFirstChild = child;
       m_pLastChild = child;
     }
@@ -52,14 +60,16 @@
       AppendLastChild(child);
       return;
     }
-    CHECK(other->m_pParent == this);
     BecomeParent(child);
+    CHECK(HasChild(other));
     child->m_pNextSibling = other;
     child->m_pPrevSibling = other->m_pPrevSibling;
-    if (other->m_pPrevSibling)
-      other->m_pPrevSibling->m_pNextSibling = child;
-    else
+    if (m_pFirstChild == other) {
+      CHECK(!other->m_pPrevSibling);
       m_pFirstChild = child;
+    } else {
+      other->m_pPrevSibling->m_pNextSibling = child;
+    }
     other->m_pPrevSibling = child;
   }
 
@@ -68,29 +78,33 @@
       AppendFirstChild(child);
       return;
     }
-    CHECK(other->m_pParent == this);
     BecomeParent(child);
+    CHECK(HasChild(other));
     child->m_pNextSibling = other->m_pNextSibling;
     child->m_pPrevSibling = other;
-    if (other->m_pNextSibling)
-      other->m_pNextSibling->m_pPrevSibling = child;
-    else
+    if (m_pLastChild == other) {
+      CHECK(!other->m_pNextSibling);
       m_pLastChild = child;
+    } else {
+      other->m_pNextSibling->m_pPrevSibling = child;
+    }
     other->m_pNextSibling = child;
   }
 
   void RemoveChild(T* child) {
-    CHECK(child->m_pParent == this);
-    if (child->m_pNextSibling)
-      child->m_pNextSibling->m_pPrevSibling = child->m_pPrevSibling;
-    else
+    CHECK(HasChild(child));
+    if (m_pLastChild == child) {
+      CHECK(!child->m_pNextSibling);
       m_pLastChild = child->m_pPrevSibling;
-
-    if (child->m_pPrevSibling)
-      child->m_pPrevSibling->m_pNextSibling = child->m_pNextSibling;
-    else
+    } else {
+      child->m_pNextSibling->m_pPrevSibling = child->m_pPrevSibling;
+    }
+    if (m_pFirstChild == child) {
+      CHECK(!child->m_pPrevSibling);
       m_pFirstChild = child->m_pNextSibling;
-
+    } else {
+      child->m_pPrevSibling->m_pNextSibling = child->m_pNextSibling;
+    }
     child->m_pParent = nullptr;
     child->m_pPrevSibling = nullptr;
     child->m_pNextSibling = nullptr;
@@ -99,11 +113,12 @@
  private:
   // Child left in state where sibling members need subsequent adjustment.
   void BecomeParent(T* child) {
+    CHECK(child != this);  // Detect attempts at self-insertion.
     if (child->m_pParent)
       child->m_pParent->TreeNode<T>::RemoveChild(child);
     child->m_pParent = static_cast<T*>(this);
-    ASSERT(!child->m_pNextSibling);
-    ASSERT(!child->m_pPrevSibling);
+    CHECK(!child->m_pNextSibling);
+    CHECK(!child->m_pPrevSibling);
   }
 
   T* m_pParent = nullptr;       // Raw, intra-tree pointer.
diff --git a/core/fxcrt/tree_node_unittest.cpp b/core/fxcrt/tree_node_unittest.cpp
new file mode 100644
index 0000000..f2334cf
--- /dev/null
+++ b/core/fxcrt/tree_node_unittest.cpp
@@ -0,0 +1,70 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxcrt/tree_node.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/base/ptr_util.h"
+
+namespace fxcrt {
+
+class TestTreeNode : public TreeNode<TestTreeNode> {};
+
+// NOTE: Successful cases are covered via RetainedTreeNode tests.
+// These tests check that we trip CHECKS given bad calls.
+
+TEST(TreeNode, SelfAppendFirstChild) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  EXPECT_DEATH(pNode->AppendFirstChild(pNode.get()), "");
+}
+
+TEST(TreeNode, SelfAppendLastChild) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  EXPECT_DEATH(pNode->AppendLastChild(pNode.get()), "");
+}
+
+TEST(TreeNode, SelfInsertBeforeOther) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  auto pOther = pdfium::MakeUnique<TestTreeNode>();
+  pNode->AppendFirstChild(pOther.get());
+  EXPECT_DEATH(pNode->InsertBefore(pNode.get(), pOther.get()), "");
+}
+
+TEST(TreeNode, InsertOtherBeforeSelf) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  auto pOther = pdfium::MakeUnique<TestTreeNode>();
+  pNode->AppendFirstChild(pOther.get());
+  EXPECT_DEATH(pNode->InsertBefore(pOther.get(), pNode.get()), "");
+}
+
+TEST(TreeNode, SelfInsertAfterOther) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  auto pOther = pdfium::MakeUnique<TestTreeNode>();
+  pNode->AppendFirstChild(pOther.get());
+  EXPECT_DEATH(pNode->InsertBefore(pNode.get(), pOther.get()), "");
+}
+
+TEST(TreeNode, InsertOtherAfterSelf) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  auto pOther = pdfium::MakeUnique<TestTreeNode>();
+  pNode->AppendFirstChild(pOther.get());
+  EXPECT_DEATH(pNode->InsertBefore(pOther.get(), pNode.get()), "");
+}
+
+TEST(TreeNode, RemoveParentless) {
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  EXPECT_DEATH(pNode->GetParent()->RemoveChild(pNode.get()), "");
+}
+
+TEST(TreeNode, RemoveFromWrongParent) {
+  auto pGoodParent = pdfium::MakeUnique<TestTreeNode>();
+  auto pBadParent = pdfium::MakeUnique<TestTreeNode>();
+  auto pNode = pdfium::MakeUnique<TestTreeNode>();
+  pGoodParent->AppendFirstChild(pNode.get());
+  EXPECT_DEATH(pBadParent->RemoveChild(pNode.get()), "");
+}
+
+}  // namespace fxcrt