Merge remote-tracking branch 'aosp/upstream' into kv

* aosp/upstream:
  Unspecify VarVisibilityFunc param name s
  Add func KATI_visibility_prefix

Change-Id: I2db3c738d41b38189cf4356631a36411c4f72812
diff --git a/src/expr.cc b/src/expr.cc
index e3a8629..102bfdf 100644
--- a/src/expr.cc
+++ b/src/expr.cc
@@ -146,6 +146,7 @@
     Var* v = ev->LookupVarForEval(name_);
     v->Used(ev, name_);
     v->Eval(ev, s);
+    v->CheckCurrentReferencingFile(ev->loc(), name_.c_str());
     ev->VarEvalComplete(name_);
   }
 
@@ -176,6 +177,7 @@
     Var* v = ev->LookupVarForEval(sym);
     v->Used(ev, sym);
     v->Eval(ev, s);
+    v->CheckCurrentReferencingFile(ev->loc(), name.c_str());
     ev->VarEvalComplete(sym);
   }
 
diff --git a/src/find.cc b/src/find.cc
index 4e81851..0a43527 100644
--- a/src/find.cc
+++ b/src/find.cc
@@ -28,7 +28,7 @@
 #include <string_view>
 #include <vector>
 
-//#undef NOLOG
+// #undef NOLOG
 
 #include "fileutil.h"
 #include "log.h"
diff --git a/src/func.cc b/src/func.cc
index 75a7cb6..b3e544c 100644
--- a/src/func.cc
+++ b/src/func.cc
@@ -27,6 +27,7 @@
 #include <algorithm>
 #include <iterator>
 #include <memory>
+#include <sstream>
 #include <unordered_map>
 
 #include "eval.h"
@@ -510,7 +511,7 @@
   }
 }
 
-//#define TEST_FIND_EMULATOR
+// #define TEST_FIND_EMULATOR
 
 // A hack for Android build. We need to evaluate things like $((3+4))
 // when we emit ninja file, because the result of such expressions
@@ -656,6 +657,59 @@
   ShellStatusVar::SetValue(returnCode);
 }
 
+void VarVisibilityFunc(const std::vector<Value*>& args,
+                       Evaluator* ev,
+                       std::string*) {
+  std::string arg = args[0]->Eval(ev);
+  std::vector<std::string> prefixes;
+
+  std::stringstream ss(args[1]->Eval(ev));
+  std::string prefix;
+  while (ss >> prefix) {
+    if (HasPrefix(prefix, "/")) {
+      ERROR_LOC(ev->loc(), "Visibility prefix should not start with /");
+    }
+    if (HasPrefix(prefix, "../")) {
+      ERROR_LOC(ev->loc(), "Visibility prefix should not start with ../");
+    }
+
+    std::string normalizedPrefix = prefix;
+    NormalizePath(&normalizedPrefix);
+    if (prefix != normalizedPrefix) {
+      ERROR_LOC(ev->loc(),
+                "Visibility prefix %s is not normalized. Normalized prefix: %s",
+                prefix.c_str(), normalizedPrefix.c_str());
+    }
+
+    // one visibility prefix cannot be the prefix of another visibility prefix
+    for (std::vector<std::string>::iterator it = prefixes.begin();
+         it != prefixes.end(); ++it) {
+      if (HasPathPrefix(*it, prefix)) {
+        ERROR_LOC(ev->loc(),
+                  "Visibility prefix %s is the prefix of another visibility "
+                  "prefix %s",
+                  prefix.c_str(), it->c_str());
+      } else if (HasPathPrefix(prefix, *it)) {
+        ERROR_LOC(ev->loc(),
+                  "Visibility prefix %s is the prefix of another visibility "
+                  "prefix %s",
+                  it->c_str(), prefix.c_str());
+      }
+    }
+
+    prefixes.push_back(prefix);
+  }
+
+  Symbol sym = Intern(arg);
+  Var* v = ev->PeekVar(sym);
+  // If variable is not defined, create an empty variable.
+  if (!v->IsDefined()) {
+    v = new SimpleVar(VarOrigin::FILE, ev->CurrentFrame(), ev->loc());
+    sym.SetGlobalVar(v, false, nullptr);
+  }
+  v->SetVisibilityPrefix(prefixes, sym.c_str());
+}
+
 void CallFunc(const std::vector<Value*>& args, Evaluator* ev, std::string* s) {
   static const Symbol tmpvar_names[] = {
       Intern("0"), Intern("1"), Intern("2"), Intern("3"), Intern("4"),
@@ -1149,6 +1203,7 @@
     ENTRY("KATI_shell_no_rerun", &ShellFuncNoRerun, 1, 1, false, false),
     ENTRY("KATI_foreach_sep", &ForeachWithSepFunc, 4, 4, false, false),
     ENTRY("KATI_file_no_rerun", &FileFuncNoRerun, 2, 1, false, false),
+    ENTRY("KATI_visibility_prefix", &VarVisibilityFunc, 2, 1, false, false),
 };
 
 }  // namespace
diff --git a/src/strutil.cc b/src/strutil.cc
index 149c729..08c04cf 100644
--- a/src/strutil.cc
+++ b/src/strutil.cc
@@ -110,6 +110,11 @@
   return size_diff >= 0 && str.substr(0, prefix.size()) == prefix;
 }
 
+bool HasPathPrefix(std::string_view str, std::string_view prefix) {
+  return HasPrefix(str, prefix) &&
+         (str.size() == prefix.size() || str.at(prefix.size()) == '/');
+}
+
 bool HasSuffix(std::string_view str, std::string_view suffix) {
   ssize_t size_diff = str.size() - suffix.size();
   return size_diff >= 0 && str.substr(size_diff) == suffix;
diff --git a/src/strutil.h b/src/strutil.h
index bd71c1b..0045875 100644
--- a/src/strutil.h
+++ b/src/strutil.h
@@ -80,6 +80,11 @@
 
 bool HasPrefix(std::string_view str, std::string_view prefix);
 
+// Checks path-like prefixes. str and prefix and both considered as strings
+// that represent "path".
+// e.g. str "foo/bar" has path prefix "foo" but doesn't have path prefix "fo".
+bool HasPathPrefix(std::string_view str, std::string_view prefix);
+
 bool HasSuffix(std::string_view str, std::string_view suffix);
 
 bool HasWord(std::string_view str, std::string_view w);
diff --git a/src/symtab.cc b/src/symtab.cc
index 0ba1213..57c1417 100644
--- a/src/symtab.cc
+++ b/src/symtab.cc
@@ -14,7 +14,7 @@
 
 // +build ignore
 
-//#define ENABLE_TID_CHECK
+// #define ENABLE_TID_CHECK
 
 #include "symtab.h"
 
diff --git a/src/var.cc b/src/var.cc
index 0f1cb05..9907409 100644
--- a/src/var.cc
+++ b/src/var.cc
@@ -85,6 +85,41 @@
   }
 }
 
+void Var::SetVisibilityPrefix(const std::vector<std::string>& prefixes,
+                              const char* name) {
+  const std::vector<std::string>& current_prefixes = VisibilityPrefix();
+  if (current_prefixes.size() == 0) {
+    visibility_prefix_ = prefixes;
+  } else if (current_prefixes != prefixes) {
+    ERROR("Visibility prefix conflict on variable: %s", name);
+  }
+}
+
+void Var::CheckCurrentReferencingFile(Loc loc, const char* name) {
+  const std::vector<std::string>& prefixes = VisibilityPrefix();
+  if (prefixes.size() == 0) {
+    return;
+  }
+  bool valid = false;
+  for (const std::string& prefix : prefixes) {
+    if (HasPathPrefix(loc.filename, prefix)) {
+      valid = true;
+      break;
+    }
+  }
+  if (!valid) {
+    std::string prefixesString;
+    for (const std::string& p : prefixes) {
+      prefixesString += (p + "\n");
+    }
+    prefixesString.pop_back();
+    ERROR(
+        "%s is not a valid file to reference variable %s. Line #%d.\nValid "
+        "file prefixes:\n%s",
+        loc.filename, name, loc.lineno, prefixesString.c_str());
+  }
+}
+
 const char* Var::diagnostic_message_text() const {
   auto it = diagnostic_messages_.find(this);
   return it == diagnostic_messages_.end() ? "" : it->second.c_str();
diff --git a/src/var.h b/src/var.h
index 0e0972f..a1c9096 100644
--- a/src/var.h
+++ b/src/var.h
@@ -73,6 +73,13 @@
   bool SelfReferential() const { return self_referential_; }
   void SetSelfReferential() { self_referential_ = true; }
 
+  const std::vector<std::string>& VisibilityPrefix() const {
+    return visibility_prefix_;
+  }
+  void SetVisibilityPrefix(const std::vector<std::string>& prefixes,
+                           const char* name);
+  void CheckCurrentReferencingFile(Loc loc, const char* name);
+
   const std::string& DeprecatedMessage() const;
 
   // This variable was used (either written or read from)
@@ -101,6 +108,8 @@
   const char* diagnostic_message_text() const;
 
   static std::unordered_map<const Var*, std::string> diagnostic_messages_;
+
+  std::vector<std::string> visibility_prefix_;
 };
 
 class SimpleVar : public Var {
diff --git a/testcase/var_visibility_prefix_conflict.mk b/testcase/var_visibility_prefix_conflict.mk
new file mode 100644
index 0000000..265d020
--- /dev/null
+++ b/testcase/var_visibility_prefix_conflict.mk
@@ -0,0 +1,16 @@
+FOO := foo
+BAR := bar
+PREFIX := pone/ptwo
+
+$(KATI_visibility_prefix FOO, pone/ptwo baz)
+$(KATI_visibility_prefix FOO, $(PREFIX) baz)
+
+$(KATI_visibility_prefix BAR, pone/ptwo baz)
+$(KATI_visibility_prefix BAR, baz $(PREFIX))
+
+ifndef KATI
+$(info Visibility prefix conflict on variable: BAR)
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_implicit_define.mk b/testcase/var_visibility_prefix_implicit_define.mk
new file mode 100644
index 0000000..144fdf6
--- /dev/null
+++ b/testcase/var_visibility_prefix_implicit_define.mk
@@ -0,0 +1,6 @@
+$(KATI_visibility_prefix FOO, Makefile)
+
+BAR := $(FOO)
+
+test:
+	echo '$(BAR)'
diff --git a/testcase/var_visibility_prefix_invalid_file_four.mk b/testcase/var_visibility_prefix_invalid_file_four.mk
new file mode 100644
index 0000000..de00dab
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_file_four.mk
@@ -0,0 +1,16 @@
+$(KATI_visibility_prefix BAR, bar)
+FOO = $(BAR) # this should be okay, since it's not evaluated
+BAZ := $(FOO)
+
+define ERROR_MSG
+Makefile is not a valid file to reference variable BAR. Line #3.
+Valid file prefixes:
+bar
+endef
+
+ifndef KATI
+$(info $(ERROR_MSG))
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_file_one.mk b/testcase/var_visibility_prefix_invalid_file_one.mk
new file mode 100644
index 0000000..ea97905
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_file_one.mk
@@ -0,0 +1,23 @@
+FOO := foo
+BAR := bar
+$(KATI_visibility_prefix FOO, Makefile)
+$(KATI_visibility_prefix BAR, )
+$(KATI_visibility_prefix BAZ, baz)
+
+VAR0 := $(FOO)
+VAR1 := $(BAR)
+VAR2 := $$(BAZ)
+VAR3 := $($(BAZ))
+
+define ERROR_MSG
+Makefile is not a valid file to reference variable BAZ. Line #10.
+Valid file prefixes:
+baz
+endef
+
+ifndef KATI
+$(info $(ERROR_MSG))
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_file_two.mk b/testcase/var_visibility_prefix_invalid_file_two.mk
new file mode 100644
index 0000000..28f84a6
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_file_two.mk
@@ -0,0 +1,16 @@
+$(KATI_visibility_prefix BAR, bar)
+FOO := BAR
+BAZ := $($(FOO))
+
+define ERROR_MSG
+Makefile is not a valid file to reference variable BAR. Line #3.
+Valid file prefixes:
+bar
+endef
+
+ifndef KATI
+$(info $(ERROR_MSG))
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_prefix_four.mk b/testcase/var_visibility_prefix_invalid_prefix_four.mk
new file mode 100644
index 0000000..b8a45b3
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_prefix_four.mk
@@ -0,0 +1,8 @@
+$(KATI_visibility_prefix FOO, foo/)
+
+ifndef KATI
+$(info Makefile:1: Visibility prefix foo/ is not normalized. Normalized prefix: foo)
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_prefix_one.mk b/testcase/var_visibility_prefix_invalid_prefix_one.mk
new file mode 100644
index 0000000..65a2f3a
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_prefix_one.mk
@@ -0,0 +1,8 @@
+$(KATI_visibility_prefix FOO, /foo)
+
+ifndef KATI
+$(info Makefile:1: Visibility prefix should not start with /)
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_prefix_three.mk b/testcase/var_visibility_prefix_invalid_prefix_three.mk
new file mode 100644
index 0000000..3e599f0
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_prefix_three.mk
@@ -0,0 +1,9 @@
+$(KATI_visibility_prefix FOO, foo fo) # no error, different path prefixes
+$(KATI_visibility_prefix Bar, bar/baz bar)
+
+ifndef KATI
+$(info Makefile:2: Visibility prefix bar is the prefix of another visibility prefix bar/baz)
+endif
+
+test:
+	@:
diff --git a/testcase/var_visibility_prefix_invalid_prefix_two.mk b/testcase/var_visibility_prefix_invalid_prefix_two.mk
new file mode 100644
index 0000000..a208c94
--- /dev/null
+++ b/testcase/var_visibility_prefix_invalid_prefix_two.mk
@@ -0,0 +1,8 @@
+$(KATI_visibility_prefix FOO, foo ../foo)
+
+ifndef KATI
+$(info Makefile:1: Visibility prefix should not start with ../)
+endif
+
+test:
+	@: