[templates] Add print_stack_trace() function

Add a 'print_stack_trace()' function which will print out
the stack of template invocation calls that led up to the
function call.

Change-Id: I1c19032196ea28b0ba9776b3be1e14cbab7c5bdb
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13820
Reviewed-by: David Turner <digit@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Aaron Wood <aaronwood@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 2aba497..6ffec1d 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -53,6 +53,7 @@
     *   [not_needed: Mark variables from scope as not needed.](#func_not_needed)
     *   [pool: Defines a pool object.](#func_pool)
     *   [print: Prints to the console.](#func_print)
+    *   [print_stack_trace: Prints a stack trace.](#func_print_stack_trace)
     *   [process_file_template: Do template expansion over a list of files.](#func_process_file_template)
     *   [read_file: Read a file into a variable.](#func_read_file)
     *   [rebase_path: Rebase a file or directory to another location.](#func_rebase_path)
@@ -2970,6 +2971,35 @@
 
   print(sources, deps)
 ```
+### <a name="func_print_stack_trace"></a>**print_stack_trace**: Prints a stack trace.
+
+```
+  Prints the current file location, and all template invocations that led up to
+  this location, to the console.
+```
+
+#### **Examples**
+
+```
+  template("foo"){
+    print_stack_trace()
+  }
+  template("bar"){
+    foo(target_name + ".foo") {
+      baz = invoker.baz
+    }
+  }
+  bar("lala") {
+    baz = 42
+  }
+
+  will print out the following:
+
+  print_stack_trace() initiated at  //build.gn:2
+    bar("lala")  //BUILD.gn:9
+    foo("lala.foo")  //BUILD.gn:5
+    print_stack_trace()  //BUILD.gn:2
+```
 ### <a name="func_process_file_template"></a>**process_file_template**: Do template expansion over a list of files.
 
 ```
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index 6294f79..2b33190 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -968,6 +968,65 @@
   return Value();
 }
 
+// print_stack_trace -----------------------------------------------------------
+
+const char kPrintStackTrace[] = "print_stack_trace";
+const char kPrintStackTrace_HelpShort[] =
+    "print_stack_trace: Prints a stack trace.";
+const char kPrintStackTrace_Help[] =
+    R"(print_stack_trace: Prints a stack trace.
+
+  Prints the current file location, and all template invocations that led up to
+  this location, to the console.
+
+Examples
+
+  template("foo"){
+    print_stack_trace()
+  }
+  template("bar"){
+    foo(target_name + ".foo") {
+      baz = invoker.baz
+    }
+  }
+  bar("lala") {
+    baz = 42
+  }
+
+  will print out the following:
+
+  print_stack_trace() initiated at  //build.gn:2
+    bar("lala")  //BUILD.gn:9
+    foo("lala.foo")  //BUILD.gn:5
+    print_stack_trace()  //BUILD.gn:2
+
+)";
+
+Value RunPrintStackTrace(Scope* scope,
+                         const FunctionCallNode* function,
+                         const std::vector<Value>& args,
+                         Err* err) {
+  std::string location_str = function->GetRange().begin().Describe(false);
+  std::string output = "print_stack_trace() initiated at:  " + location_str;
+  output.push_back('\n');
+
+  for (const auto& entry : scope->GetTemplateInvocationEntries()) {
+    output.append("  " + entry.Describe() + "\n");
+  }
+  output.append("  print_stack_trace()  " + location_str + "\n");
+
+  const BuildSettings::PrintCallback& cb =
+      scope->settings()->build_settings()->print_callback();
+  if (cb) {
+    cb(output);
+  } else {
+    printf("%s", output.c_str());
+    fflush(stdout);
+  }
+
+  return Value();
+}
+
 // split_list ------------------------------------------------------------------
 
 const char kSplitList[] = "split_list";
@@ -1393,6 +1452,7 @@
     INSERT_FUNCTION(NotNeeded, false)
     INSERT_FUNCTION(Pool, false)
     INSERT_FUNCTION(Print, false)
+    INSERT_FUNCTION(PrintStackTrace, false)
     INSERT_FUNCTION(ProcessFileTemplate, false)
     INSERT_FUNCTION(ReadFile, false)
     INSERT_FUNCTION(RebasePath, false)
diff --git a/src/gn/functions_unittest.cc b/src/gn/functions_unittest.cc
index 40ac5d9..783a22a 100644
--- a/src/gn/functions_unittest.cc
+++ b/src/gn/functions_unittest.cc
@@ -457,3 +457,147 @@
   ASSERT_FALSE(err.has_error())
       << err.message() << err.location().Describe(true);
 }
+
+TEST(Template, PrintStackTraceWithOneTemplate) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  print(invoker.foo_value)\n"
+      "  print_stack_trace()\n"
+      "}\n"
+      "foo(\"lala\") {\n"
+      "  foo_value = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+    "lala\n"
+    "42\n"
+    "print_stack_trace() initiated at:  //test:4\n"
+    "  foo(\"lala\")  //test:6\n"
+    "  print_stack_trace()  //test:4\n",
+    setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNoTemplates) {
+  TestWithScope setup;
+  TestParseInput input("print_stack_trace()\n");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+    "print_stack_trace() initiated at:  //test:1\n"
+    "  print_stack_trace()  //test:1\n",
+    setup.print_output());
+}
+
+
+TEST(Template, PrintStackTraceWithNestedTemplates) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  print(invoker.foo_value)\n"
+      "  print_stack_trace()\n"
+      "}\n"
+      "template(\"baz\") {\n"
+      "  foo(\"${target_name}.foo\") {\n"
+      "    foo_value = invoker.bar\n"
+      "  }\n"
+      "}\n"
+      "baz(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+    "lala.foo\n"
+    "42\n"
+    "print_stack_trace() initiated at:  //test:4\n"
+    "  baz(\"lala\")  //test:11\n"
+    "  foo(\"lala.foo\")  //test:7\n"
+    "  print_stack_trace()  //test:4\n",
+    setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNonTemplateScopes) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  if (defined(invoker.foo_value)) {\n"
+      "    print(invoker.foo_value)\n"
+      "    print_stack_trace()\n"
+      "  }\n"
+      "}\n"
+      "template(\"baz\") {\n"
+      "  foo(\"${target_name}.foo\") {\n"
+      "    foo_value = invoker.bar\n"
+      "  }\n"
+      "}\n"
+      "baz(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+    "lala.foo\n"
+    "42\n"
+    "print_stack_trace() initiated at:  //test:5\n"
+    "  baz(\"lala\")  //test:13\n"
+    "  foo(\"lala.foo\")  //test:9\n"
+    "  print_stack_trace()  //test:5\n",
+    setup.print_output());
+}
+
+TEST(Template, PrintStackTraceWithNonTemplateScopesBetweenTemplateInvocations) {
+  TestWithScope setup;
+  TestParseInput input(
+      "template(\"foo\") {\n"
+      "  print(target_name)\n"
+      "  if (defined(invoker.foo_value)) {\n"
+      "    print(invoker.foo_value)\n"
+      "    print_stack_trace()\n"
+      "  }\n"
+      "}\n"
+      "template(\"baz\") {\n"
+      "  if (invoker.bar == 42) {\n"
+      "    foo(\"${target_name}.foo\") {\n"
+      "      foo_value = invoker.bar\n"
+      "    }\n"
+      "  }\n"
+      "}\n"
+      "baz(\"lala\") {\n"
+      "  bar = 42\n"
+      "}");
+  ASSERT_FALSE(input.has_error());
+  Err err;
+  input.parsed()->Execute(setup.scope(), &err);
+
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  EXPECT_EQ(
+    "lala.foo\n"
+    "42\n"
+    "print_stack_trace() initiated at:  //test:5\n"
+    "  baz(\"lala\")  //test:15\n"
+    "  foo(\"lala.foo\")  //test:10\n"
+    "  print_stack_trace()  //test:5\n",
+    setup.print_output());
+}
diff --git a/src/gn/scope.cc b/src/gn/scope.cc
index 05b0995..e51282a 100644
--- a/src/gn/scope.cc
+++ b/src/gn/scope.cc
@@ -39,6 +39,13 @@
   scope_->RemoveProvider(this);
 }
 
+std::string Scope::TemplateInvocationEntry::Describe() const {
+  std::string ret = template_name;
+  ret += "(\"" + target_name + "\")  ";
+  ret += location.Describe(false);
+  return ret;
+}
+
 Scope::Scope(const Settings* settings)
     : const_containing_(nullptr),
       mutable_containing_(nullptr),
@@ -557,6 +564,42 @@
   programmatic_providers_.erase(p);
 }
 
+void Scope::SetTemplateInvocationEntry(std::string template_name,
+                                       std::string target_name,
+                                       Location location) {
+  template_invocation_entry_ = std::make_unique<TemplateInvocationEntry>(
+      TemplateInvocationEntry{std::move(template_name), std::move(target_name),
+                              std::move(location)});
+}
+
+void Scope::AppendTemplateInvocationEntries(
+    std::vector<TemplateInvocationEntry>* out) const {
+
+  // Bare scopes and if/for_each() scopes need to walk up their containing
+  // scopes to find previous template invocations.  A scope like this within
+  // a template invocation will have an "invoker" value, but that invoker will
+  // not have an entry, and so both are checked to ensure that the full stack of
+  // invocations is captured.
+  if (containing())
+    containing()->AppendTemplateInvocationEntries(out);
+
+  // Template scopes need to walk up invoker to find previous template
+  // invocations
+  const Value* invoker = GetValue("invoker");
+  if (invoker && invoker->type() == Value::SCOPE)
+    invoker->scope_value()->AppendTemplateInvocationEntries(out);
+
+  if (template_invocation_entry_)
+    out->push_back(*template_invocation_entry_);
+}
+
+std::vector<Scope::TemplateInvocationEntry>
+Scope::GetTemplateInvocationEntries() const {
+  std::vector<Scope::TemplateInvocationEntry> result;
+  AppendTemplateInvocationEntries(&result);
+  return result;
+}
+
 // static
 bool Scope::RecordMapValuesEqual(const RecordMap& a, const RecordMap& b) {
   if (a.size() != b.size())
diff --git a/src/gn/scope.h b/src/gn/scope.h
index 5412a41..e82c56f 100644
--- a/src/gn/scope.h
+++ b/src/gn/scope.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <string>
 #include <string_view>
 #include <unordered_map>
 #include <utility>
@@ -15,6 +16,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "gn/err.h"
+#include "gn/location.h"
 #include "gn/pattern.h"
 #include "gn/source_dir.h"
 #include "gn/source_file.h"
@@ -94,6 +96,21 @@
     std::set<std::string> excluded_values;
   };
 
+  // Details about a Scope's creation as a template invocation
+  struct TemplateInvocationEntry {
+    // Produce a printable string that describes the template invocation:
+    //
+    // 'template_name("target_name")  //some/BUILD.gn:<line>'
+    //
+    // The target name is only the string that's passed to the template as the
+    // name, it's not a complete GN label.
+    std::string Describe() const;
+
+    std::string template_name;
+    std::string target_name;
+    Location location;
+  };
+
   // Creates an empty toplevel scope.
   explicit Scope(const Settings* settings);
 
@@ -319,6 +336,15 @@
   void SetProperty(const void* key, void* value);
   void* GetProperty(const void* key, const Scope** found_on_scope) const;
 
+  // Track template invocations for printing or debugging.
+  void SetTemplateInvocationEntry(std::string template_name,
+                                  std::string target_name,
+                                  Location location);
+
+  // Return a vector containing the current stack of template invocations that
+  // lead up to this scope.
+  std::vector<TemplateInvocationEntry> GetTemplateInvocationEntries() const;
+
  private:
   friend class ProgrammaticProvider;
 
@@ -339,6 +365,10 @@
   // of the values may be different).
   static bool RecordMapValuesEqual(const RecordMap& a, const RecordMap& b);
 
+  // Walk up the containing scopes and any "invoker" Value scopes to gather any
+  // previous template invocations.
+  void AppendTemplateInvocationEntries(std::vector<TemplateInvocationEntry>* out) const;
+
   // Scopes can have no containing scope (both null), a mutable containing
   // scope, or a const containing scope. The reason is that when we're doing
   // a new target, we want to refer to the base_config scope which will be read
@@ -356,6 +386,9 @@
 
   RecordMap values_;
 
+  // If this is a template scope, track the template invocation.
+  std::unique_ptr<TemplateInvocationEntry> template_invocation_entry_;
+
   // Note that this can't use string pieces since the names are constructed from
   // Values which might be deallocated before this goes out of scope.
   using NamedScopeMap = std::unordered_map<std::string, std::unique_ptr<Scope>>;
diff --git a/src/gn/template.cc b/src/gn/template.cc
index f0eca28..deafa33 100644
--- a/src/gn/template.cc
+++ b/src/gn/template.cc
@@ -64,6 +64,10 @@
   Scope template_scope(closure_.get());
   template_scope.set_source_dir(scope->GetSourceDir());
 
+  // Track the invocation of the template on the template's scope
+  template_scope.SetTemplateInvocationEntry(
+      template_name, args[0].string_value(), invocation->GetRange().begin());
+
   // Propagate build dependency files from invoker scope (template scope already
   // propagated via parent scope).
   template_scope.AddBuildDependencyFiles(