aboutsummaryrefslogtreecommitdiffstats
path: root/src/eval.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval.cc')
-rw-r--r--src/eval.cc556
1 files changed, 556 insertions, 0 deletions
diff --git a/src/eval.cc b/src/eval.cc
new file mode 100644
index 0000000..3772763
--- /dev/null
+++ b/src/eval.cc
@@ -0,0 +1,556 @@
+// Copyright 2015 Google Inc. All rights reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+#include "eval.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "expr.h"
+#include "file.h"
+#include "file_cache.h"
+#include "fileutil.h"
+#include "parser.h"
+#include "rule.h"
+#include "stats.h"
+#include "stmt.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "var.h"
+
+Evaluator::Evaluator()
+ : last_rule_(NULL),
+ current_scope_(NULL),
+ avoid_io_(false),
+ eval_depth_(0),
+ posix_sym_(Intern(".POSIX")),
+ is_posix_(false),
+ export_error_(false) {
+#if defined(__APPLE__)
+ stack_size_ = pthread_get_stacksize_np(pthread_self());
+ stack_addr_ = (char*)pthread_get_stackaddr_np(pthread_self()) - stack_size_;
+#else
+ pthread_attr_t attr;
+ CHECK(pthread_getattr_np(pthread_self(), &attr) == 0);
+ CHECK(pthread_attr_getstack(&attr, &stack_addr_, &stack_size_) == 0);
+ CHECK(pthread_attr_destroy(&attr) == 0);
+#endif
+
+ lowest_stack_ = (char*)stack_addr_ + stack_size_;
+ LOG_STAT("Stack size: %zd bytes", stack_size_);
+}
+
+Evaluator::~Evaluator() {
+ // delete vars_;
+ // for (auto p : rule_vars) {
+ // delete p.second;
+ // }
+}
+
+Var* Evaluator::EvalRHS(Symbol lhs,
+ Value* rhs_v,
+ StringPiece orig_rhs,
+ AssignOp op,
+ bool is_override,
+ bool* needs_assign) {
+ VarOrigin origin =
+ ((is_bootstrap_ ? VarOrigin::DEFAULT
+ : is_commandline_ ? VarOrigin::COMMAND_LINE
+ : is_override ? VarOrigin::OVERRIDE
+ : VarOrigin::FILE));
+
+ Var* result = NULL;
+ Var* prev = NULL;
+ *needs_assign = true;
+
+ switch (op) {
+ case AssignOp::COLON_EQ: {
+ prev = PeekVarInCurrentScope(lhs);
+ result = new SimpleVar(origin, this, rhs_v);
+ break;
+ }
+ case AssignOp::EQ:
+ prev = PeekVarInCurrentScope(lhs);
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ break;
+ case AssignOp::PLUS_EQ: {
+ prev = LookupVarInCurrentScope(lhs);
+ if (!prev->IsDefined()) {
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ } else if (prev->ReadOnly()) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ lhs.c_str()));
+ } else {
+ result = prev;
+ result->AppendVar(this, rhs_v);
+ *needs_assign = false;
+ }
+ break;
+ }
+ case AssignOp::QUESTION_EQ: {
+ prev = LookupVarInCurrentScope(lhs);
+ if (!prev->IsDefined()) {
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ } else {
+ result = prev;
+ *needs_assign = false;
+ }
+ break;
+ }
+ }
+
+ if (prev != NULL) {
+ prev->Used(this, lhs);
+ if (prev->Deprecated() && *needs_assign) {
+ result->SetDeprecated(prev->DeprecatedMessage());
+ }
+ }
+
+ LOG("Assign: %s=%s", lhs.c_str(), result->DebugString().c_str());
+ return result;
+}
+
+void Evaluator::EvalAssign(const AssignStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+ Symbol lhs = stmt->GetLhsSymbol(this);
+ if (lhs.empty())
+ Error("*** empty variable name.");
+
+ if (lhs == kKatiReadonlySym) {
+ string rhs;
+ stmt->rhs->Eval(this, &rhs);
+ for (auto const& name : WordScanner(rhs)) {
+ Var* var = Intern(name).GetGlobalVar();
+ if (!var->IsDefined()) {
+ Error(
+ StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
+ }
+ var->SetReadOnly();
+ }
+ return;
+ }
+
+ bool needs_assign;
+ Var* var =
+ EvalRHS(lhs, stmt->rhs, stmt->orig_rhs, stmt->op,
+ stmt->directive == AssignDirective::OVERRIDE, &needs_assign);
+ if (needs_assign) {
+ bool readonly;
+ lhs.SetGlobalVar(var, stmt->directive == AssignDirective::OVERRIDE,
+ &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ lhs.c_str()));
+ }
+ }
+
+ if (stmt->is_final) {
+ var->SetReadOnly();
+ }
+}
+
+// With rule broken into
+// <before_term> <term> <after_term>
+// parses <before_term> into Symbol instances until encountering ':'
+// Returns the remainder of <before_term>.
+static StringPiece ParseRuleTargets(const Loc& loc,
+ const StringPiece& before_term,
+ vector<Symbol>* targets,
+ bool* is_pattern_rule) {
+ size_t pos = before_term.find(':');
+ if (pos == string::npos) {
+ ERROR_LOC(loc, "*** missing separator.");
+ }
+ StringPiece targets_string = before_term.substr(0, pos);
+ size_t pattern_rule_count = 0;
+ for (auto const& word : WordScanner(targets_string)) {
+ StringPiece target = TrimLeadingCurdir(word);
+ targets->push_back(Intern(target));
+ if (Rule::IsPatternRule(target)) {
+ ++pattern_rule_count;
+ }
+ }
+ // Check consistency: either all outputs are patterns or none.
+ if (pattern_rule_count && (pattern_rule_count != targets->size())) {
+ ERROR_LOC(loc, "*** mixed implicit and normal rules: deprecated syntax");
+ }
+ *is_pattern_rule = pattern_rule_count;
+ return before_term.substr(pos + 1);
+}
+
+void Evaluator::MarkVarsReadonly(Value* vars_list) {
+ string vars_list_string;
+ vars_list->Eval(this, &vars_list_string);
+ for (auto const& name : WordScanner(vars_list_string)) {
+ Var* var = current_scope_->Lookup(Intern(name));
+ if (!var->IsDefined()) {
+ Error(StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
+ }
+ var->SetReadOnly();
+ }
+}
+
+void Evaluator::EvalRuleSpecificAssign(const vector<Symbol>& targets,
+ const RuleStmt* stmt,
+ const StringPiece& after_targets,
+ size_t separator_pos) {
+ StringPiece var_name;
+ StringPiece rhs_string;
+ AssignOp assign_op;
+ ParseAssignStatement(after_targets, separator_pos, &var_name, &rhs_string,
+ &assign_op);
+ Symbol var_sym = Intern(var_name);
+ bool is_final = (stmt->sep == RuleStmt::SEP_FINALEQ);
+ for (Symbol target : targets) {
+ auto p = rule_vars_.emplace(target, nullptr);
+ if (p.second) {
+ p.first->second = new Vars;
+ }
+
+ Value* rhs;
+ if (rhs_string.empty()) {
+ rhs = stmt->rhs;
+ } else if (stmt->rhs) {
+ StringPiece sep(stmt->sep == RuleStmt::SEP_SEMICOLON ? " ; " : " = ");
+ rhs = Value::NewExpr(Value::NewLiteral(rhs_string),
+ Value::NewLiteral(sep), stmt->rhs);
+ } else {
+ rhs = Value::NewLiteral(rhs_string);
+ }
+
+ current_scope_ = p.first->second;
+ if (var_sym == kKatiReadonlySym) {
+ MarkVarsReadonly(rhs);
+ } else {
+ bool needs_assign;
+ Var* rhs_var = EvalRHS(var_sym, rhs, StringPiece("*TODO*"), assign_op,
+ false, &needs_assign);
+ if (needs_assign) {
+ bool readonly;
+ rhs_var->SetAssignOp(assign_op);
+ current_scope_->Assign(var_sym, rhs_var, &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ var_name));
+ }
+ }
+ if (is_final) {
+ rhs_var->SetReadOnly();
+ }
+ }
+ current_scope_ = NULL;
+ }
+}
+
+void Evaluator::EvalRule(const RuleStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& before_term = stmt->lhs->Eval(this);
+ // See semicolon.mk.
+ if (before_term.find_first_not_of(" \t;") == string::npos) {
+ if (stmt->sep == RuleStmt::SEP_SEMICOLON)
+ Error("*** missing rule before commands.");
+ return;
+ }
+
+ vector<Symbol> targets;
+ bool is_pattern_rule;
+ StringPiece after_targets =
+ ParseRuleTargets(loc_, before_term, &targets, &is_pattern_rule);
+ bool is_double_colon = (after_targets[0] == ':');
+ if (is_double_colon) {
+ after_targets = after_targets.substr(1);
+ }
+
+ // Figure out if this is a rule-specific variable assignment.
+ // It is an assignment when either after_targets contains an assignment token
+ // or separator is an assignment token, but only if there is no ';' before the
+ // first assignment token.
+ size_t separator_pos = after_targets.find_first_of("=;");
+ char separator = '\0';
+ if (separator_pos != string::npos) {
+ separator = after_targets[separator_pos];
+ } else if (separator_pos == string::npos &&
+ (stmt->sep == RuleStmt::SEP_EQ ||
+ stmt->sep == RuleStmt::SEP_FINALEQ)) {
+ separator_pos = after_targets.size();
+ separator = '=';
+ }
+
+ // If variable name is not empty, we have rule- or target-specific
+ // variable assignment.
+ if (separator == '=' && separator_pos) {
+ EvalRuleSpecificAssign(targets, stmt, after_targets, separator_pos);
+ return;
+ }
+
+ if (!separator_pos) {
+ // We used to make this a warning and otherwise accept it, but Make 4.1
+ // calls this out as an error, so let's follow.
+ Error("*** empty variable name.");
+ }
+
+ Rule* rule = new Rule();
+ rule->loc = loc_;
+ rule->is_double_colon = is_double_colon;
+ if (is_pattern_rule) {
+ rule->output_patterns.swap(targets);
+ } else {
+ rule->outputs.swap(targets);
+ }
+ rule->ParsePrerequisites(after_targets, separator_pos, stmt);
+
+ if (stmt->sep == RuleStmt::SEP_SEMICOLON) {
+ rule->cmds.push_back(stmt->rhs);
+ }
+
+ for (Symbol o : rule->outputs) {
+ if (o == posix_sym_)
+ is_posix_ = true;
+ }
+
+ LOG("Rule: %s", rule->DebugString().c_str());
+ rules_.push_back(rule);
+ last_rule_ = rule;
+}
+
+void Evaluator::EvalCommand(const CommandStmt* stmt) {
+ loc_ = stmt->loc();
+
+ if (!last_rule_) {
+ vector<Stmt*> stmts;
+ ParseNotAfterRule(stmt->orig, stmt->loc(), &stmts);
+ for (Stmt* a : stmts)
+ a->Eval(this);
+ return;
+ }
+
+ last_rule_->cmds.push_back(stmt->expr);
+ if (last_rule_->cmd_lineno == 0)
+ last_rule_->cmd_lineno = stmt->loc().lineno;
+ LOG("Command: %s", Value::DebugString(stmt->expr).c_str());
+}
+
+void Evaluator::EvalIf(const IfStmt* stmt) {
+ loc_ = stmt->loc();
+
+ bool is_true;
+ switch (stmt->op) {
+ case CondOp::IFDEF:
+ case CondOp::IFNDEF: {
+ string var_name;
+ stmt->lhs->Eval(this, &var_name);
+ Symbol lhs = Intern(TrimRightSpace(var_name));
+ if (lhs.str().find_first_of(" \t") != string::npos)
+ Error("*** invalid syntax in conditional.");
+ Var* v = LookupVarInCurrentScope(lhs);
+ v->Used(this, lhs);
+ is_true = (v->String().empty() == (stmt->op == CondOp::IFNDEF));
+ break;
+ }
+ case CondOp::IFEQ:
+ case CondOp::IFNEQ: {
+ const string&& lhs = stmt->lhs->Eval(this);
+ const string&& rhs = stmt->rhs->Eval(this);
+ is_true = ((lhs == rhs) == (stmt->op == CondOp::IFEQ));
+ break;
+ }
+ default:
+ CHECK(false);
+ abort();
+ }
+
+ const vector<Stmt*>* stmts;
+ if (is_true) {
+ stmts = &stmt->true_stmts;
+ } else {
+ stmts = &stmt->false_stmts;
+ }
+ for (Stmt* a : *stmts) {
+ LOG("%s", a->DebugString().c_str());
+ a->Eval(this);
+ }
+}
+
+void Evaluator::DoInclude(const string& fname) {
+ CheckStack();
+ COLLECT_STATS_WITH_SLOW_REPORT("included makefiles", fname.c_str());
+
+ Makefile* mk = MakefileCacheManager::Get()->ReadMakefile(fname);
+ if (!mk->Exists()) {
+ Error(StringPrintf("%s does not exist", fname.c_str()));
+ }
+
+ Var* var_list = LookupVar(Intern("MAKEFILE_LIST"));
+ var_list->AppendVar(
+ this, Value::NewLiteral(Intern(TrimLeadingCurdir(fname)).str()));
+ for (Stmt* stmt : mk->stmts()) {
+ LOG("%s", stmt->DebugString().c_str());
+ stmt->Eval(this);
+ }
+
+ for (auto& mk : profiled_files_) {
+ stats.MarkInteresting(mk);
+ }
+ profiled_files_.clear();
+}
+
+void Evaluator::EvalInclude(const IncludeStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& pats = stmt->expr->Eval(this);
+ for (StringPiece pat : WordScanner(pats)) {
+ ScopedTerminator st(pat);
+ vector<string>* files;
+ Glob(pat.data(), &files);
+
+ if (stmt->should_exist) {
+ if (files->empty()) {
+ // TODO: Kati does not support building a missing include file.
+ Error(StringPrintf("%s: %s", pat.data(), strerror(errno)));
+ }
+ }
+
+ include_stack_.push_back(stmt->loc());
+ for (const string& fname : *files) {
+ if (!stmt->should_exist && g_flags.ignore_optional_include_pattern &&
+ Pattern(g_flags.ignore_optional_include_pattern).Match(fname)) {
+ continue;
+ }
+ DoInclude(fname);
+ }
+ include_stack_.pop_back();
+ }
+}
+
+void Evaluator::EvalExport(const ExportStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& exports = stmt->expr->Eval(this);
+ for (StringPiece tok : WordScanner(exports)) {
+ size_t equal_index = tok.find('=');
+ StringPiece lhs;
+ if (equal_index == string::npos) {
+ lhs = tok;
+ } else if (equal_index == 0 ||
+ (equal_index == 1 &&
+ (tok[0] == ':' || tok[0] == '?' || tok[0] == '+'))) {
+ // Do not export tokens after an assignment.
+ break;
+ } else {
+ StringPiece rhs;
+ AssignOp op;
+ ParseAssignStatement(tok, equal_index, &lhs, &rhs, &op);
+ }
+ Symbol sym = Intern(lhs);
+ exports_[sym] = stmt->is_export;
+
+ if (export_message_) {
+ const char* prefix = "";
+ if (!stmt->is_export) {
+ prefix = "un";
+ }
+
+ if (export_error_) {
+ Error(StringPrintf("*** %s: %sexport is obsolete%s.", sym.c_str(),
+ prefix, export_message_->c_str()));
+ } else {
+ WARN_LOC(loc(), "%s: %sexport has been deprecated%s.", sym.c_str(),
+ prefix, export_message_->c_str());
+ }
+ }
+ }
+}
+
+Var* Evaluator::LookupVarGlobal(Symbol name) {
+ Var* v = name.GetGlobalVar();
+ if (v->IsDefined())
+ return v;
+ used_undefined_vars_.insert(name);
+ return v;
+}
+
+Var* Evaluator::LookupVar(Symbol name) {
+ if (current_scope_) {
+ Var* v = current_scope_->Lookup(name);
+ if (v->IsDefined())
+ return v;
+ }
+ return LookupVarGlobal(name);
+}
+
+Var* Evaluator::PeekVar(Symbol name) {
+ if (current_scope_) {
+ Var* v = current_scope_->Peek(name);
+ if (v->IsDefined())
+ return v;
+ }
+ return name.PeekGlobalVar();
+}
+
+Var* Evaluator::LookupVarInCurrentScope(Symbol name) {
+ if (current_scope_) {
+ return current_scope_->Lookup(name);
+ }
+ return LookupVarGlobal(name);
+}
+
+Var* Evaluator::PeekVarInCurrentScope(Symbol name) {
+ if (current_scope_) {
+ return current_scope_->Peek(name);
+ }
+ return name.PeekGlobalVar();
+}
+
+string Evaluator::EvalVar(Symbol name) {
+ return LookupVar(name)->Eval(this);
+}
+
+string Evaluator::GetShell() {
+ return EvalVar(kShellSym);
+}
+
+string Evaluator::GetShellFlag() {
+ // TODO: Handle $(.SHELLFLAGS)
+ return is_posix_ ? "-ec" : "-c";
+}
+
+string Evaluator::GetShellAndFlag() {
+ string shell = GetShell();
+ shell += ' ';
+ shell += GetShellFlag();
+ return shell;
+}
+
+void Evaluator::Error(const string& msg) {
+ for (auto& inc : include_stack_) {
+ fprintf(stderr, "In file included from %s:%d:\n", LOCF(inc));
+ }
+ ERROR_LOC(loc_, "%s", msg.c_str());
+}
+
+void Evaluator::DumpStackStats() const {
+ LOG_STAT("Max stack use: %zd bytes at %s:%d",
+ ((char*)stack_addr_ - (char*)lowest_stack_) + stack_size_,
+ LOCF(lowest_loc_));
+}
+
+SymbolSet Evaluator::used_undefined_vars_;