[GN fuzzer] Stack overflow fix.

Fuzzathon 2017.

Bug: 648076,749793,773426,768111,754972,734401,734200
Change-Id: Ic608c5a374252809443a879ad4e2ddf8f6184697
Reviewed-on: https://chromium-review.googlesource.com/736159
Commit-Queue: Penny MacNeil <pennymac@chromium.org>
Reviewed-by: Nico Weber <thakis@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Reviewed-by: Max Moroz <mmoroz@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#512626}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 697f302d91e7ad4466416ab22c290c7fea9c6b93
diff --git a/tools/gn/parser_fuzzer.cc b/tools/gn/parser_fuzzer.cc
index c7b4325..e40da78 100644
--- a/tools/gn/parser_fuzzer.cc
+++ b/tools/gn/parser_fuzzer.cc
@@ -9,6 +9,51 @@
 #include "tools/gn/source_file.h"
 #include "tools/gn/tokenizer.h"
 
+namespace {
+
+enum { kMaxContentDepth = 256, kMaxDodgy = 256 };
+
+// Some auto generated input is too unreasonable for fuzzing GN.
+// We see stack overflow when the parser hits really deeply "nested" input.
+// (I.E.: certain input that causes nested parsing function calls).
+//
+// Abstract max limits are undesirable in the release GN code, so some sanity
+// checks in the fuzzer to prevent stack overflow are done here.
+// - 1) Too many opening bracket, paren, or brace in a row.
+// - 2) Too many '!', '<' or '>' operators in a row.
+bool SanityCheckContent(const std::vector<Token>& tokens) {
+  int depth = 0;
+  int dodgy_count = 0;
+  for (const auto& token : tokens) {
+    switch (token.type()) {
+      case Token::LEFT_PAREN:
+      case Token::LEFT_BRACKET:
+      case Token::LEFT_BRACE:
+        ++depth;
+        break;
+      case Token::RIGHT_PAREN:
+      case Token::RIGHT_BRACKET:
+      case Token::RIGHT_BRACE:
+        --depth;
+        break;
+      case Token::BANG:
+      case Token::LESS_THAN:
+      case Token::GREATER_THAN:
+        ++dodgy_count;
+        break;
+      default:
+        break;
+    }
+    // Bail out as soon as a boundary is hit, inside the loop.
+    if (depth >= kMaxContentDepth || dodgy_count >= kMaxDodgy)
+      return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
 extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
   SourceFile source;
   InputFile input(source);
@@ -16,6 +61,8 @@
 
   Err err;
   std::vector<Token> tokens = Tokenizer::Tokenize(&input, &err);
+  if (!SanityCheckContent(tokens))
+    return 0;
 
   if (!err.has_error())
     Parser::Parse(tokens, &err);