mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 06:16:30 +03:00
577 lines
19 KiB
C++
577 lines
19 KiB
C++
|
// 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 "tools/gn/scope.h"
|
||
|
|
||
|
#include <memory>
|
||
|
|
||
|
#include "base/logging.h"
|
||
|
#include "tools/gn/parse_tree.h"
|
||
|
#include "tools/gn/source_file.h"
|
||
|
#include "tools/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(const base::StringPiece& 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);
|
||
|
}
|
||
|
|
||
|
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(const base::StringPiece& ident,
|
||
|
bool counts_as_used) {
|
||
|
const Scope* found_in_scope = nullptr;
|
||
|
return GetValueWithScope(ident, counts_as_used, &found_in_scope);
|
||
|
}
|
||
|
|
||
|
const Value* Scope::GetValueWithScope(const base::StringPiece& 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(const base::StringPiece& 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;
|
||
|
}
|
||
|
|
||
|
base::StringPiece Scope::GetStorageKey(const base::StringPiece& 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 base::StringPiece();
|
||
|
}
|
||
|
|
||
|
const Value* Scope::GetValue(const base::StringPiece& ident) const {
|
||
|
const Scope *found_in_scope = nullptr;
|
||
|
return GetValueWithScope(ident, &found_in_scope);
|
||
|
}
|
||
|
|
||
|
const Value* Scope::GetValueWithScope(const base::StringPiece& 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(const base::StringPiece& 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(const base::StringPiece& 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<base::StringPiece> 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(const base::StringPiece& 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<std::string>& excluded_values) {
|
||
|
for (auto& cur : values_) {
|
||
|
if (!excluded_values.empty() &&
|
||
|
excluded_values.find(cur.first.as_string()) != excluded_values.end()) {
|
||
|
continue; // Skip this excluded value.
|
||
|
}
|
||
|
cur.second.used = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Scope::MarkUnused(const base::StringPiece& ident) {
|
||
|
RecordMap::iterator found = values_.find(ident);
|
||
|
if (found == values_.end()) {
|
||
|
NOTREACHED();
|
||
|
return;
|
||
|
}
|
||
|
found->second.used = false;
|
||
|
}
|
||
|
|
||
|
bool Scope::IsSetButUnused(const base::StringPiece& 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 \"" + pair.first.as_string() +
|
||
|
"\" here and it was unused before it went\nout of scope.";
|
||
|
|
||
|
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::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 base::StringPiece& 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(current_name.as_string()) !=
|
||
|
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 \"" +
|
||
|
current_name.as_string() + "\"");
|
||
|
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<Scope>& dest_scope = dest->target_defaults_[current_name];
|
||
|
dest_scope = std::make_unique<Scope>(settings_);
|
||
|
pair.second->NonRecursiveMergeTo(dest_scope.get(), options, node_for_err,
|
||
|
"<SHOULDN'T HAPPEN>", err);
|
||
|
}
|
||
|
|
||
|
// Sources assignment filter.
|
||
|
if (sources_assignment_filter_) {
|
||
|
if (!options.clobber_existing) {
|
||
|
if (dest->GetSourcesAssignmentFilter()) {
|
||
|
// Sources assignment filter present in both the source and the dest.
|
||
|
std::string desc_string(desc_for_err);
|
||
|
*err = Err(node_for_err, "Assignment filter collision.",
|
||
|
"The " + desc_string +
|
||
|
" contains a sources_assignment_filter "
|
||
|
"which\nwould clobber the one in your current scope.");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
dest->sources_assignment_filter_ =
|
||
|
std::make_unique<PatternList>(*sources_assignment_filter_);
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
// Propogate build dependency files,
|
||
|
dest->build_dependency_files_.insert(build_dependency_files_.begin(),
|
||
|
build_dependency_files_.end());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<Scope> Scope::MakeClosure() const {
|
||
|
std::unique_ptr<Scope> 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<Scope>(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<Scope>(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, "<SHOULDN'T HAPPEN>",
|
||
|
&err);
|
||
|
DCHECK(!err.has_error());
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Scope* Scope::MakeTargetDefaults(const std::string& target_type) {
|
||
|
std::unique_ptr<Scope>& dest = target_defaults_[target_type];
|
||
|
dest = std::make_unique<Scope>(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;
|
||
|
}
|
||
|
|
||
|
const PatternList* Scope::GetSourcesAssignmentFilter() const {
|
||
|
if (sources_assignment_filter_)
|
||
|
return sources_assignment_filter_.get();
|
||
|
if (containing())
|
||
|
return containing()->GetSourcesAssignmentFilter();
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|