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 Value & dict)53 void TraceConfigCategoryFilter::InitializeFromConfigDict(const Value& dict) {
54   const Value* included_category_list =
55       dict.FindListKey(kIncludedCategoriesParam);
56   if (included_category_list)
57     SetCategoriesFromIncludedList(*included_category_list);
58   const Value* excluded_category_list =
59       dict.FindListKey(kExcludedCategoriesParam);
60   if (excluded_category_list)
61     SetCategoriesFromExcludedList(*excluded_category_list);
62 }
63 
IsCategoryGroupEnabled(const StringPiece & category_group_name) const64 bool TraceConfigCategoryFilter::IsCategoryGroupEnabled(
65     const StringPiece& category_group_name) const {
66   bool had_enabled_by_default = false;
67   DCHECK(!category_group_name.empty());
68   CStringTokenizer category_group_tokens(category_group_name.begin(),
69                                          category_group_name.end(), ",");
70   while (category_group_tokens.GetNext()) {
71     StringPiece category_group_token = category_group_tokens.token_piece();
72     // Don't allow empty tokens, nor tokens with leading or trailing space.
73     DCHECK(IsCategoryNameAllowed(category_group_token))
74         << "Disallowed category string";
75     if (IsCategoryEnabled(category_group_token))
76       return true;
77 
78     if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
79       had_enabled_by_default = true;
80   }
81   // Do a second pass to check for explicitly disabled categories
82   // (those explicitly enabled have priority due to first pass).
83   category_group_tokens.Reset();
84   bool category_group_disabled = false;
85   while (category_group_tokens.GetNext()) {
86     StringPiece category_group_token = category_group_tokens.token_piece();
87     for (const std::string& category : excluded_categories_) {
88       if (MatchPattern(category_group_token, category)) {
89         // Current token of category_group_name is present in excluded_list.
90         // Flag the exclusion and proceed further to check if any of the
91         // remaining categories of category_group_name is not present in the
92         // excluded_ list.
93         category_group_disabled = true;
94         break;
95       }
96       // One of the category of category_group_name is not present in
97       // excluded_ list. So, if it's not a disabled-by-default category,
98       // it has to be included_ list. Enable the category_group_name
99       // for recording.
100       if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
101         category_group_disabled = false;
102     }
103     // One of the categories present in category_group_name is not present in
104     // excluded_ list. Implies this category_group_name group can be enabled
105     // for recording, since one of its groups is enabled for recording.
106     if (!category_group_disabled)
107       break;
108   }
109   // If the category group is not excluded, and there are no included patterns
110   // we consider this category group enabled, as long as it had categories
111   // other than disabled-by-default.
112   return !category_group_disabled && had_enabled_by_default &&
113          included_categories_.empty();
114 }
115 
IsCategoryEnabled(const StringPiece & category_name) const116 bool TraceConfigCategoryFilter::IsCategoryEnabled(
117     const StringPiece& category_name) const {
118   // Check the disabled- filters and the disabled-* wildcard first so that a
119   // "*" filter does not include the disabled.
120   for (const std::string& category : disabled_categories_) {
121     if (MatchPattern(category_name, category))
122       return true;
123   }
124 
125   if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*")))
126     return false;
127 
128   for (const std::string& category : included_categories_) {
129     if (MatchPattern(category_name, category))
130       return true;
131   }
132 
133   return false;
134 }
135 
Merge(const TraceConfigCategoryFilter & config)136 void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) {
137   // Keep included patterns only if both filters have an included entry.
138   // Otherwise, one of the filter was specifying "*" and we want to honor the
139   // broadest filter.
140   if (!included_categories_.empty() && !config.included_categories_.empty()) {
141     included_categories_.insert(included_categories_.end(),
142                                 config.included_categories_.begin(),
143                                 config.included_categories_.end());
144   } else {
145     included_categories_.clear();
146   }
147 
148   disabled_categories_.insert(disabled_categories_.end(),
149                               config.disabled_categories_.begin(),
150                               config.disabled_categories_.end());
151   excluded_categories_.insert(excluded_categories_.end(),
152                               config.excluded_categories_.begin(),
153                               config.excluded_categories_.end());
154 }
155 
Clear()156 void TraceConfigCategoryFilter::Clear() {
157   included_categories_.clear();
158   disabled_categories_.clear();
159   excluded_categories_.clear();
160 }
161 
ToDict(Value * dict) const162 void TraceConfigCategoryFilter::ToDict(Value* dict) const {
163   StringList categories(included_categories_);
164   categories.insert(categories.end(), disabled_categories_.begin(),
165                     disabled_categories_.end());
166   AddCategoriesToDict(categories, kIncludedCategoriesParam, dict);
167   AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict);
168 }
169 
ToFilterString() const170 std::string TraceConfigCategoryFilter::ToFilterString() const {
171   std::string filter_string;
172   WriteCategoryFilterString(included_categories_, &filter_string, true);
173   WriteCategoryFilterString(disabled_categories_, &filter_string, true);
174   WriteCategoryFilterString(excluded_categories_, &filter_string, false);
175   return filter_string;
176 }
177 
SetCategoriesFromIncludedList(const Value & included_list)178 void TraceConfigCategoryFilter::SetCategoriesFromIncludedList(
179     const Value& included_list) {
180   included_categories_.clear();
181   for (const Value& item : included_list.GetList()) {
182     if (!item.is_string())
183       continue;
184     const std::string& category = item.GetString();
185     if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")),
186                          TRACE_DISABLED_BY_DEFAULT("")) == 0) {
187       disabled_categories_.push_back(category);
188     } else {
189       included_categories_.push_back(category);
190     }
191   }
192 }
193 
SetCategoriesFromExcludedList(const Value & excluded_list)194 void TraceConfigCategoryFilter::SetCategoriesFromExcludedList(
195     const Value& excluded_list) {
196   excluded_categories_.clear();
197   for (const Value& item : excluded_list.GetList()) {
198     if (item.is_string())
199       excluded_categories_.push_back(item.GetString());
200   }
201 }
202 
AddCategoriesToDict(const StringList & categories,const char * param,Value * dict) const203 void TraceConfigCategoryFilter::AddCategoriesToDict(
204     const StringList& categories,
205     const char* param,
206     Value* dict) const {
207   if (categories.empty())
208     return;
209 
210   std::vector<base::Value> list;
211   for (const std::string& category : categories)
212     list.emplace_back(category);
213   dict->SetKey(param, base::Value(std::move(list)));
214 }
215 
WriteCategoryFilterString(const StringList & values,std::string * out,bool included) const216 void TraceConfigCategoryFilter::WriteCategoryFilterString(
217     const StringList& values,
218     std::string* out,
219     bool included) const {
220   bool prepend_comma = !out->empty();
221   int token_cnt = 0;
222   for (const std::string& category : values) {
223     if (token_cnt > 0 || prepend_comma)
224       StringAppendF(out, ",");
225     StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str());
226     ++token_cnt;
227   }
228 }
229 
230 // static
IsCategoryNameAllowed(StringPiece str)231 bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) {
232   return !str.empty() && str.front() != ' ' && str.back() != ' ';
233 }
234 
235 }  // namespace trace_event
236 }  // namespace base
237