| // Copyright 2011 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. |
| |
| #include "eval_env.h" |
| |
| #include <assert.h> |
| |
| #include "graph.h" |
| #include "state.h" |
| |
| Rule::Rule(const HashedStrView& name) : name_(name) { |
| pos_.base = new BasePosition {{ &State::kBuiltinScope, 0 }}; // leaked |
| } |
| |
| bool Rule::IsReservedBinding(StringPiece var) { |
| // Cycle detection for rule variable evaluation uses a fixed recursion depth |
| // that's guaranteed to be larger than the number of reserved binding names |
| // listed below. |
| static_assert(EdgeEval::kEvalRecursionLimit == 16, |
| "Unexpected rule variable evaluation recursion limit"); |
| |
| return var == "command" || |
| var == "depfile" || |
| var == "description" || |
| var == "deps" || |
| var == "generator" || |
| var == "pool" || |
| var == "restat" || |
| var == "rspfile" || |
| var == "rspfile_content" || |
| var == "phony_output" || |
| var == "msvc_deps_prefix"; |
| } |
| |
| void Binding::Evaluate(std::string* out_append) { |
| if (is_evaluated_.load()) { |
| out_append->append(final_value_); |
| return; |
| } |
| |
| std::string str; |
| EvaluateBindingInScope(&str, parsed_value_, pos_.scope_pos()); |
| out_append->append(str); |
| |
| // Try to store the result so we can use it again later. If we can't acquire |
| // the lock, then another thread has already acquired it and will set the |
| // final binding value. |
| std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock); |
| if (!lock.owns_lock()) |
| return; |
| |
| // Check the flag again. Another thread could have set it before this thread |
| // acquired the lock, and if it had, then other threads could already be using |
| // this binding's saved value. |
| if (is_evaluated_.load()) |
| return; |
| |
| final_value_ = std::move(str); |
| is_evaluated_.store(true); |
| } |
| |
| template <typename K, typename V> |
| static void ExpandTable(std::unordered_map<K, V>& table, size_t extra_size) { |
| size_t needed_buckets = table.size() + extra_size + 1; |
| if (table.bucket_count() >= needed_buckets) |
| return; |
| table.rehash(needed_buckets * 2); |
| }; |
| |
| void Scope::ReserveTableSpace(size_t new_bindings, size_t new_rules) { |
| ExpandTable(bindings_, new_bindings); |
| ExpandTable(rules_, new_rules); |
| } |
| |
| std::map<std::string, const Rule*> Scope::GetRules() const { |
| std::map<std::string, const Rule*> result; |
| for (std::pair<HashedStrView, Rule*> pair : rules_) { |
| Rule* rule = pair.second; |
| result[rule->name()] = rule; |
| } |
| return result; |
| } |
| |
| Binding* Scope::LookupBindingAtPos(const HashedStrView& var, ScopePosition pos) { |
| Scope* scope = pos.scope; |
| if (scope == nullptr) return nullptr; |
| |
| auto it = scope->bindings_.find(var); |
| if (it == scope->bindings_.end()) |
| return LookupBindingAtPos(var, scope->parent_); |
| |
| struct BindingCmp { |
| bool operator()(Binding* x, DeclIndex y) const { |
| return x->pos_.scope_index() < y; |
| } |
| }; |
| |
| // A binding "Foo = $Foo" is valid; the "$Foo" is not a self-reference but |
| // a reference to the previous binding of Foo. The evaluation of "$Foo" |
| // happens with the same DeclIndex as the "Foo = $Foo" binding, so we want |
| // to find a binding whose ScopePosition is strictly less than the one we're |
| // searching for, not equal to it. |
| std::vector<Binding*>& decls = it->second; |
| auto it2 = std::lower_bound( |
| decls.begin(), decls.end(), |
| pos.index, BindingCmp()); |
| if (it2 == decls.begin()) |
| return LookupBindingAtPos(var, scope->parent_); |
| return *(it2 - 1); |
| } |
| |
| Binding* Scope::LookupBinding(const HashedStrView& var, Scope* scope) { |
| if (scope == nullptr) return nullptr; |
| |
| auto it = scope->bindings_.find(var); |
| if (it == scope->bindings_.end()) { |
| // When we delegate to the parent scope, we match bindings at the end of the |
| // parent's scope, even if they weren't in scope when the subninja scope was |
| // parsed. The behavior matters after parsing is complete and we're |
| // evaluating edge bindings that involve rule variable expansions. |
| return LookupBinding(var, scope->parent_.scope); |
| } |
| |
| std::vector<Binding*>& decls = it->second; |
| assert(!decls.empty()); |
| |
| // Evaluate the binding. |
| return decls.back(); |
| } |
| |
| void Scope::EvaluateVariableAtPos(std::string* out_append, |
| const HashedStrView& var, ScopePosition pos) { |
| if (Binding* binding = LookupBindingAtPos(var, pos)) |
| binding->Evaluate(out_append); |
| } |
| |
| void Scope::EvaluateVariable(std::string* out_append, const HashedStrView& var, |
| Scope* scope) { |
| if (Binding* binding = LookupBinding(var, scope)) |
| binding->Evaluate(out_append); |
| } |
| |
| std::string Scope::LookupVariable(const HashedStrView& var) { |
| std::string result; |
| EvaluateVariable(&result, var, this); |
| return result; |
| } |
| |
| Rule* Scope::LookupRuleAtPos(const HashedStrView& rule_name, |
| ScopePosition pos) { |
| Scope* scope = pos.scope; |
| if (scope == nullptr) return nullptr; |
| auto it = scope->rules_.find(rule_name); |
| if (it != scope->rules_.end()) { |
| Rule* rule = it->second; |
| if (rule->pos_.scope_index() < pos.index) |
| return rule; |
| } |
| return LookupRuleAtPos(rule_name, scope->parent_); |
| } |