// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gn/scope.h" #include #include "base/logging.h" #include "gn/parse_tree.h" #include "gn/source_file.h" #include "gn/template.h" namespace { // FLags set in the mode_flags_ of a scope. If a bit is set, it applies // recursively to all dependent scopes. const unsigned kProcessingBuildConfigFlag = 1; const unsigned kProcessingImportFlag = 2; // Returns true if this variable name should be considered private. Private // values start with an underscore, and are not imported from "gni" files // when processing an import. bool IsPrivateVar(std::string_view name) { return name.empty() || name[0] == '_'; } } // namespace // Defaults to all false, which are the things least likely to cause errors. Scope::MergeOptions::MergeOptions() : clobber_existing(false), skip_private_vars(false), mark_dest_used(false) {} Scope::MergeOptions::~MergeOptions() = default; Scope::ProgrammaticProvider::~ProgrammaticProvider() { 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), settings_(settings), mode_flags_(0), item_collector_(nullptr) {} Scope::Scope(Scope* parent) : const_containing_(nullptr), mutable_containing_(parent), settings_(parent->settings()), mode_flags_(0), item_collector_(nullptr), build_dependency_files_(parent->build_dependency_files_) {} Scope::Scope(const Scope* parent) : const_containing_(parent), mutable_containing_(nullptr), settings_(parent->settings()), mode_flags_(0), item_collector_(nullptr), build_dependency_files_(parent->build_dependency_files_) {} Scope::~Scope() = default; void Scope::DetachFromContaining() { const_containing_ = nullptr; mutable_containing_ = nullptr; } bool Scope::HasValues(SearchNested search_nested) const { DCHECK(search_nested == SEARCH_CURRENT); return !values_.empty(); } const Value* Scope::GetValue(std::string_view ident, bool counts_as_used) { const Scope* found_in_scope = nullptr; return GetValueWithScope(ident, counts_as_used, &found_in_scope); } const Value* Scope::GetValueWithScope(std::string_view ident, bool counts_as_used, const Scope** found_in_scope) { // First check for programmatically-provided values. for (auto* provider : programmatic_providers_) { const Value* v = provider->GetProgrammaticValue(ident); if (v) { *found_in_scope = nullptr; return v; } } RecordMap::iterator found = values_.find(ident); if (found != values_.end()) { if (counts_as_used) found->second.used = true; *found_in_scope = this; return &found->second.value; } // Search in the parent scope. if (const_containing_) return const_containing_->GetValueWithScope(ident, found_in_scope); if (mutable_containing_) { return mutable_containing_->GetValueWithScope(ident, counts_as_used, found_in_scope); } return nullptr; } Value* Scope::GetMutableValue(std::string_view ident, SearchNested search_mode, bool counts_as_used) { // Don't do programmatic values, which are not mutable. RecordMap::iterator found = values_.find(ident); if (found != values_.end()) { if (counts_as_used) found->second.used = true; return &found->second.value; } // Search in the parent mutable scope if requested, but not const one. if (search_mode == SEARCH_NESTED && mutable_containing_) { return mutable_containing_->GetMutableValue(ident, Scope::SEARCH_NESTED, counts_as_used); } return nullptr; } std::string_view Scope::GetStorageKey(std::string_view ident) const { RecordMap::const_iterator found = values_.find(ident); if (found != values_.end()) return found->first; // Search in parent scope. if (containing()) return containing()->GetStorageKey(ident); return std::string_view(); } const Value* Scope::GetValue(std::string_view ident) const { const Scope* found_in_scope = nullptr; return GetValueWithScope(ident, &found_in_scope); } const Value* Scope::GetValueWithScope(std::string_view ident, const Scope** found_in_scope) const { RecordMap::const_iterator found = values_.find(ident); if (found != values_.end()) { *found_in_scope = this; return &found->second.value; } if (containing()) return containing()->GetValueWithScope(ident, found_in_scope); return nullptr; } Value* Scope::SetValue(std::string_view ident, Value v, const ParseNode* set_node) { Record& r = values_[ident]; // Clears any existing value. r.value = std::move(v); r.value.set_origin(set_node); return &r.value; } void Scope::RemoveIdentifier(std::string_view ident) { RecordMap::iterator found = values_.find(ident); if (found != values_.end()) values_.erase(found); } void Scope::RemovePrivateIdentifiers() { // Do it in two phases to avoid mutating while iterating. Our hash map is // currently backed by several different vendor-specific implementations and // I'm not sure if all of them support mutating while iterating. Since this // is not perf-critical, do the safe thing. std::vector to_remove; for (const auto& cur : values_) { if (IsPrivateVar(cur.first)) to_remove.push_back(cur.first); } for (const auto& cur : to_remove) values_.erase(cur); } bool Scope::AddTemplate(const std::string& name, const Template* templ) { if (GetTemplate(name)) return false; templates_[name] = templ; return true; } const Template* Scope::GetTemplate(const std::string& name) const { TemplateMap::const_iterator found = templates_.find(name); if (found != templates_.end()) return found->second.get(); if (containing()) return containing()->GetTemplate(name); return nullptr; } void Scope::MarkUsed(std::string_view ident) { RecordMap::iterator found = values_.find(ident); if (found == values_.end()) { NOTREACHED(); return; } found->second.used = true; } void Scope::MarkAllUsed() { for (auto& cur : values_) cur.second.used = true; } void Scope::MarkAllUsed(const std::set& excluded_values) { for (auto& cur : values_) { if (!excluded_values.empty() && excluded_values.find(std::string(cur.first)) != excluded_values.end()) { continue; // Skip this excluded value. } cur.second.used = true; } } void Scope::MarkUnused(std::string_view ident) { RecordMap::iterator found = values_.find(ident); if (found == values_.end()) { NOTREACHED(); return; } found->second.used = false; } bool Scope::IsSetButUnused(std::string_view ident) const { RecordMap::const_iterator found = values_.find(ident); if (found != values_.end()) { if (!found->second.used) { return true; } } return false; } bool Scope::CheckForUnusedVars(Err* err) const { for (const auto& pair : values_) { if (!pair.second.used) { std::string help = "You set the variable \"" + std::string(pair.first) + "\" here and it was unused before it went\nout of scope."; // Gather the template invocations that led up to this scope. auto entries = GetTemplateInvocationEntries(); if (entries.size() != 0) { help.append("\n\nVia these template invocations:\n"); for (const auto& entry : entries) { help.append(" " + entry.Describe() + "\n"); } } const BinaryOpNode* binary = pair.second.value.origin()->AsBinaryOp(); if (binary && binary->op().type() == Token::EQUAL) { // Make a nicer error message for normal var sets. *err = Err(binary->left()->GetRange(), "Assignment had no effect.", help); } else { // This will happen for internally-generated variables. *err = Err(pair.second.value.origin(), "Assignment had no effect.", help); } return false; } } return true; } void Scope::GetCurrentScopeValues(KeyValueMap* output) const { for (const auto& pair : values_) (*output)[pair.first] = pair.second.value; } bool Scope::CheckCurrentScopeValuesEqual(const Scope* other) const { // If there are containing scopes, equality shouldn't work. if (containing()) { return false; } if (values_.size() != other->values_.size()) { return false; } for (const auto& pair : values_) { const Value* v = other->GetValue(pair.first); if (!v || *v != pair.second.value) { return false; } } return true; } bool Scope::NonRecursiveMergeTo(Scope* dest, const MergeOptions& options, const ParseNode* node_for_err, const char* desc_for_err, Err* err) const { // Values. for (const auto& pair : values_) { const std::string_view current_name = pair.first; if (options.skip_private_vars && IsPrivateVar(current_name)) continue; // Skip this private var. if (!options.excluded_values.empty() && options.excluded_values.find(std::string(current_name)) != options.excluded_values.end()) { continue; // Skip this excluded value. } const Value& new_value = pair.second.value; if (!options.clobber_existing) { const Value* existing_value = dest->GetValue(current_name); if (existing_value && new_value != *existing_value) { // Value present in both the source and the dest. std::string desc_string(desc_for_err); *err = Err(node_for_err, "Value collision.", "This " + desc_string + " contains \"" + std::string(current_name) + "\""); err->AppendSubErr( Err(pair.second.value, "defined here.", "Which would clobber the one in your current scope")); err->AppendSubErr( Err(*existing_value, "defined here.", "Executing " + desc_string + " should not conflict with anything " "in the current\nscope unless the values are identical.")); return false; } } dest->values_[current_name] = pair.second; if (options.mark_dest_used) dest->MarkUsed(current_name); } // Target defaults are owning pointers. for (const auto& pair : target_defaults_) { const std::string& current_name = pair.first; if (!options.excluded_values.empty() && options.excluded_values.find(current_name) != options.excluded_values.end()) { continue; // Skip the excluded value. } if (!options.clobber_existing) { const Scope* dest_defaults = dest->GetTargetDefaults(current_name); if (dest_defaults) { if (RecordMapValuesEqual(pair.second->values_, dest_defaults->values_)) { // Values of the two defaults are equivalent, just ignore the // collision. continue; } else { // TODO(brettw) it would be nice to know the origin of a // set_target_defaults so we can give locations for the colliding // target defaults. std::string desc_string(desc_for_err); *err = Err(node_for_err, "Target defaults collision.", "This " + desc_string + " contains target defaults for\n" "\"" + current_name + "\" which would clobber one for the\n" "same target type in your current scope. It's " "unfortunate that " "I'm too stupid\nto tell you the location of where " "the target " "defaults were set. Usually\nthis happens in the " "BUILDCONFIG.gn " "file or in a related .gni file.\n"); return false; } } } std::unique_ptr& dest_scope = dest->target_defaults_[current_name]; dest_scope = std::make_unique(settings_); pair.second->NonRecursiveMergeTo(dest_scope.get(), options, node_for_err, "", err); } // Templates. for (const auto& pair : templates_) { const std::string& current_name = pair.first; if (options.skip_private_vars && IsPrivateVar(current_name)) continue; // Skip this private template. if (!options.excluded_values.empty() && options.excluded_values.find(current_name) != options.excluded_values.end()) { continue; // Skip the excluded value. } if (!options.clobber_existing) { const Template* existing_template = dest->GetTemplate(current_name); // Since templates are refcounted, we can check if it's the same one by // comparing pointers. if (existing_template && pair.second.get() != existing_template) { // Rule present in both the source and the dest, and they're not the // same one. std::string desc_string(desc_for_err); *err = Err(node_for_err, "Template collision.", "This " + desc_string + " contains a template \"" + current_name + "\""); err->AppendSubErr( Err(pair.second->GetDefinitionRange(), "defined here.", "Which would clobber the one in your current scope")); err->AppendSubErr(Err(existing_template->GetDefinitionRange(), "defined here.", "Executing " + desc_string + " should not conflict with anything " "in the current\nscope.")); return false; } } // Be careful to delete any pointer we're about to clobber. dest->templates_[current_name] = pair.second; } // Propagate build dependency files, dest->AddBuildDependencyFiles(build_dependency_files_); return true; } std::unique_ptr Scope::MakeClosure() const { std::unique_ptr result; if (const_containing_) { // We reached the top of the mutable scope stack. The result scope just // references the const scope (which will never change). result = std::make_unique(const_containing_); } else if (mutable_containing_) { // There are more nested mutable scopes. Recursively go up the stack to // get the closure. result = mutable_containing_->MakeClosure(); } else { // This is a standalone scope, just copy it. result = std::make_unique(settings_); } // Want to clobber since we've flattened some nested scopes, and our parent // scope may have a duplicate value set. MergeOptions options; options.clobber_existing = true; // Add in our variables and we're done. Err err; NonRecursiveMergeTo(result.get(), options, nullptr, "", &err); DCHECK(!err.has_error()); return result; } Scope* Scope::MakeTargetDefaults(const std::string& target_type) { std::unique_ptr& dest = target_defaults_[target_type]; dest = std::make_unique(settings_); return dest.get(); } const Scope* Scope::GetTargetDefaults(const std::string& target_type) const { NamedScopeMap::const_iterator found = target_defaults_.find(target_type); if (found != target_defaults_.end()) return found->second.get(); if (containing()) return containing()->GetTargetDefaults(target_type); return nullptr; } void Scope::SetProcessingBuildConfig() { DCHECK((mode_flags_ & kProcessingBuildConfigFlag) == 0); mode_flags_ |= kProcessingBuildConfigFlag; } void Scope::ClearProcessingBuildConfig() { DCHECK(mode_flags_ & kProcessingBuildConfigFlag); mode_flags_ &= ~(kProcessingBuildConfigFlag); } bool Scope::IsProcessingBuildConfig() const { if (mode_flags_ & kProcessingBuildConfigFlag) return true; if (containing()) return containing()->IsProcessingBuildConfig(); return false; } void Scope::SetProcessingImport() { DCHECK((mode_flags_ & kProcessingImportFlag) == 0); mode_flags_ |= kProcessingImportFlag; } void Scope::ClearProcessingImport() { DCHECK(mode_flags_ & kProcessingImportFlag); mode_flags_ &= ~(kProcessingImportFlag); } bool Scope::IsProcessingImport() const { if (mode_flags_ & kProcessingImportFlag) return true; if (containing()) return containing()->IsProcessingImport(); return false; } const SourceDir& Scope::GetSourceDir() const { if (!source_dir_.is_null()) return source_dir_; if (containing()) return containing()->GetSourceDir(); return source_dir_; } void Scope::AddBuildDependencyFile(const SourceFile& build_dependency_file) { build_dependency_files_.insert(build_dependency_file); } void Scope::AddBuildDependencyFiles( const SourceFileSet& build_dependency_files) { build_dependency_files_.insert(build_dependency_files.begin(), build_dependency_files.end()); } Scope::ItemVector* Scope::GetItemCollector() { if (item_collector_) return item_collector_; if (mutable_containing()) return mutable_containing()->GetItemCollector(); return nullptr; } void Scope::SetProperty(const void* key, void* value) { if (!value) { DCHECK(properties_.find(key) != properties_.end()); properties_.erase(key); } else { properties_[key] = value; } } void* Scope::GetProperty(const void* key, const Scope** found_on_scope) const { PropertyMap::const_iterator found = properties_.find(key); if (found != properties_.end()) { if (found_on_scope) *found_on_scope = this; return found->second; } if (containing()) return containing()->GetProperty(key, found_on_scope); return nullptr; } void Scope::AddProvider(ProgrammaticProvider* p) { programmatic_providers_.insert(p); } void Scope::RemoveProvider(ProgrammaticProvider* p) { DCHECK(programmatic_providers_.find(p) != programmatic_providers_.end()); programmatic_providers_.erase(p); } void Scope::SetTemplateInvocationEntry(std::string template_name, std::string target_name, Location location) { template_invocation_entry_ = std::make_unique( TemplateInvocationEntry{std::move(template_name), std::move(target_name), std::move(location)}); } const Scope::TemplateInvocationEntry* Scope::FindTemplateInvocationEntry() const { if (template_invocation_entry_) return template_invocation_entry_.get(); if (const Scope* scope = containing()) return scope->FindTemplateInvocationEntry(); return nullptr; } void Scope::AppendTemplateInvocationEntries( std::vector* out) const { const Value* invoker = GetValue("invoker"); if (invoker && invoker->type() == Value::SCOPE) invoker->scope_value()->AppendTemplateInvocationEntries(out); const TemplateInvocationEntry* entry = FindTemplateInvocationEntry(); if (entry) out->push_back(*entry); } std::vector Scope::GetTemplateInvocationEntries() const { std::vector result; AppendTemplateInvocationEntries(&result); return result; } // static bool Scope::RecordMapValuesEqual(const RecordMap& a, const RecordMap& b) { if (a.size() != b.size()) return false; for (const auto& pair : a) { const auto& found_b = b.find(pair.first); if (found_b == b.end()) return false; // Item in 'a' but not 'b'. if (pair.second.value != found_b->second.value) return false; // Values for variable in 'a' and 'b' are different. } return true; }