1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/trace_event/trace_config_category_filter.h"
6 
7 #include "base/memory/ptr_util.h"
8 #include "base/strings/pattern.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_tokenizer.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/trace_event/trace_event.h"
14 
15 namespace base {
16 namespace trace_event {
17 
18 namespace {
19 const char kIncludedCategoriesParam[] = "included_categories";
20 const char kExcludedCategoriesParam[] = "excluded_categories";
21 }
22 
23 TraceConfigCategoryFilter::TraceConfigCategoryFilter() = default;
24 
25 TraceConfigCategoryFilter::TraceConfigCategoryFilter(
26     const TraceConfigCategoryFilter& other) = default;
27 
28 TraceConfigCategoryFilter::~TraceConfigCategoryFilter() = default;
29 
30 TraceConfigCategoryFilter& TraceConfigCategoryFilter::operator=(
31     const TraceConfigCategoryFilter& rhs) = default;
32 
InitializeFromString(const StringPiece & category_filter_string)33 void TraceConfigCategoryFilter::InitializeFromString(
34     const StringPiece& category_filter_string) {
35   std::vector<StringPiece> split = SplitStringPiece(
36       category_filter_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
37   for (const StringPiece& category : split) {
38     // Ignore empty categories.
39     if (category.empty())
40       continue;
41     if (category.front() == '-') {
42       // Excluded categories start with '-'.
43       // Remove '-' from category string.
44       excluded_categories_.push_back(category.substr(1).as_string());
45     } else if (category.starts_with(TRACE_DISABLED_BY_DEFAULT(""))) {
46       disabled_categories_.push_back(category.as_string());
47     } else {
48       included_categories_.push_back(category.as_string());
49     }
50   }
51 }
52 
InitializeFromConfigDict(const DictionaryValue & dict)53 void TraceConfigCategoryFilter::InitializeFromConfigDict(
54     const DictionaryValue& dict) {
55   const ListValue* category_list = nullptr;
56   if (dict.GetList(kIncludedCategoriesParam, &category_list))
57     SetCategoriesFromIncludedList(*category_list);
58   if (dict.GetList(kExcludedCategoriesParam, &category_list))
59     SetCategoriesFromExcludedList(*category_list);
60 }
61 
IsCategoryGroupEnabled(const StringPiece & category_group_name) const62 bool TraceConfigCategoryFilter::IsCategoryGroupEnabled(
63     const StringPiece& category_group_name) const {
64   bool had_enabled_by_default = false;
65   DCHECK(!category_group_name.empty());
66   CStringTokenizer category_group_tokens(category_group_name.begin(),
67                                          category_group_name.end(), ",");
68   while (category_group_tokens.GetNext()) {
69     StringPiece category_group_token = category_group_tokens.token_piece();
70     // Don't allow empty tokens, nor tokens with leading or trailing space.
71     DCHECK(IsCategoryNameAllowed(category_group_token))
72         << "Disallowed category string";
73     if (IsCategoryEnabled(category_group_token))
74       return true;
75 
76     if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
77       had_enabled_by_default = true;
78   }
79   // Do a second pass to check for explicitly disabled categories
80   // (those explicitly enabled have priority due to first pass).
81   category_group_tokens.Reset();
82   bool category_group_disabled = false;
83   while (category_group_tokens.GetNext()) {
84     StringPiece category_group_token = category_group_tokens.token_piece();
85     for (const std::string& category : excluded_categories_) {
86       if (MatchPattern(category_group_token, category)) {
87         // Current token of category_group_name is present in excluded_list.
88         // Flag the exclusion and proceed further to check if any of the
89         // remaining categories of category_group_name is not present in the
90         // excluded_ list.
91         category_group_disabled = true;
92         break;
93       }
94       // One of the category of category_group_name is not present in
95       // excluded_ list. So, if it's not a disabled-by-default category,
96       // it has to be included_ list. Enable the category_group_name
97       // for recording.
98       if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
99         category_group_disabled = false;
100     }
101     // One of the categories present in category_group_name is not present in
102     // excluded_ list. Implies this category_group_name group can be enabled
103     // for recording, since one of its groups is enabled for recording.
104     if (!category_group_disabled)
105       break;
106   }
107   // If the category group is not excluded, and there are no included patterns
108   // we consider this category group enabled, as long as it had categories
109   // other than disabled-by-default.
110   return !category_group_disabled && had_enabled_by_default &&
111          included_categories_.empty();
112 }
113 
IsCategoryEnabled(const StringPiece & category_name) const114 bool TraceConfigCategoryFilter::IsCategoryEnabled(
115     const StringPiece& category_name) const {
116   // Check the disabled- filters and the disabled-* wildcard first so that a
117   // "*" filter does not include the disabled.
118   for (const std::string& category : disabled_categories_) {
119     if (MatchPattern(category_name, category))
120       return true;
121   }
122 
123   if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*")))
124     return false;
125 
126   for (const std::string& category : included_categories_) {
127     if (MatchPattern(category_name, category))
128       return true;
129   }
130 
131   return false;
132 }
133 
Merge(const TraceConfigCategoryFilter & config)134 void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) {
135   // Keep included patterns only if both filters have an included entry.
136   // Otherwise, one of the filter was specifying "*" and we want to honor the
137   // broadest filter.
138   if (!included_categories_.empty() && !config.included_categories_.empty()) {
139     included_categories_.insert(included_categories_.end(),
140                                 config.included_categories_.begin(),
141                                 config.included_categories_.end());
142   } else {
143     included_categories_.clear();
144   }
145 
146   disabled_categories_.insert(disabled_categories_.end(),
147                               config.disabled_categories_.begin(),
148                               config.disabled_categories_.end());
149   excluded_categories_.insert(excluded_categories_.end(),
150                               config.excluded_categories_.begin(),
151                               config.excluded_categories_.end());
152 }
153 
Clear()154 void TraceConfigCategoryFilter::Clear() {
155   included_categories_.clear();
156   disabled_categories_.clear();
157   excluded_categories_.clear();
158 }
159 
ToDict(DictionaryValue * dict) const160 void TraceConfigCategoryFilter::ToDict(DictionaryValue* dict) const {
161   StringList categories(included_categories_);
162   categories.insert(categories.end(), disabled_categories_.begin(),
163                     disabled_categories_.end());
164   AddCategoriesToDict(categories, kIncludedCategoriesParam, dict);
165   AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict);
166 }
167 
ToFilterString() const168 std::string TraceConfigCategoryFilter::ToFilterString() const {
169   std::string filter_string;
170   WriteCategoryFilterString(included_categories_, &filter_string, true);
171   WriteCategoryFilterString(disabled_categories_, &filter_string, true);
172   WriteCategoryFilterString(excluded_categories_, &filter_string, false);
173   return filter_string;
174 }
175 
SetCategoriesFromIncludedList(const ListValue & included_list)176 void TraceConfigCategoryFilter::SetCategoriesFromIncludedList(
177     const ListValue& included_list) {
178   included_categories_.clear();
179   for (size_t i = 0; i < included_list.GetSize(); ++i) {
180     std::string category;
181     if (!included_list.GetString(i, &category))
182       continue;
183     if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")),
184                          TRACE_DISABLED_BY_DEFAULT("")) == 0) {
185       disabled_categories_.push_back(category);
186     } else {
187       included_categories_.push_back(category);
188     }
189   }
190 }
191 
SetCategoriesFromExcludedList(const ListValue & excluded_list)192 void TraceConfigCategoryFilter::SetCategoriesFromExcludedList(
193     const ListValue& excluded_list) {
194   excluded_categories_.clear();
195   for (size_t i = 0; i < excluded_list.GetSize(); ++i) {
196     std::string category;
197     if (excluded_list.GetString(i, &category))
198       excluded_categories_.push_back(category);
199   }
200 }
201 
AddCategoriesToDict(const StringList & categories,const char * param,DictionaryValue * dict) const202 void TraceConfigCategoryFilter::AddCategoriesToDict(
203     const StringList& categories,
204     const char* param,
205     DictionaryValue* dict) const {
206   if (categories.empty())
207     return;
208 
209   auto list = std::make_unique<ListValue>();
210   for (const std::string& category : categories)
211     list->AppendString(category);
212   dict->Set(param, std::move(list));
213 }
214 
WriteCategoryFilterString(const StringList & values,std::string * out,bool included) const215 void TraceConfigCategoryFilter::WriteCategoryFilterString(
216     const StringList& values,
217     std::string* out,
218     bool included) const {
219   bool prepend_comma = !out->empty();
220   int token_cnt = 0;
221   for (const std::string& category : values) {
222     if (token_cnt > 0 || prepend_comma)
223       StringAppendF(out, ",");
224     StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str());
225     ++token_cnt;
226   }
227 }
228 
229 // static
IsCategoryNameAllowed(StringPiece str)230 bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) {
231   return !str.empty() && str.front() != ' ' && str.back() != ' ';
232 }
233 
234 }  // namespace trace_event
235 }  // namespace base
236