• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (c) 2008-2010, Google Inc.
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Neither the name of Google Inc. nor the names of its
11  * contributors may be used to endorse or promote products derived from
12  * this software without specific prior written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 // This file is part of ThreadSanitizer, a dynamic data race detector.
28 // Author: Evgeniy Stepanov.
29 
30 // This file contains the parser for valgrind-compatible suppressions.
31 
32 #include "suppressions.h"
33 
34 // TODO(eugenis): convert checks to warning messages.
35 // TODO(eugenis): write tests for incorrect syntax.
36 
37 enum LocationType {
38   LT_STAR,  // ...
39   LT_OBJ,  // obj:
40   LT_FUN,  // fun:
41 };
42 
43 struct Location {
44   LocationType type;
45   string name;
46 };
47 
48 struct StackTraceTemplate {
49   vector<Location> locations;
50 };
51 
52 struct Suppression {
53   string name;
54   set<string> tools;
55   string warning_name;
56   // Extra information available for some suppression types.
57   // ex.: Memcheck:Param
58   string extra;
59   vector<StackTraceTemplate> templates;
60 };
61 
62 class Parser {
63  public:
Parser(const string & str)64   explicit Parser(const string &str)
65       : buffer_(str), next_(buffer_.c_str()),
66         end_(buffer_.c_str() + buffer_.size()), line_no_(0), error_(false) {}
67 
68   bool NextSuppression(Suppression* suppression);
69   bool GetError();
70   string GetErrorString();
71   int GetLineNo();
72 
73  private:
Eof()74   bool Eof() { return next_ >= end_; }
75   string NextLine();
76   string NextLineSkipComments();
77   void PutBackSkipComments(string line);
78   bool ParseSuppressionToolsLine(Suppression* supp, string line);
79   bool IsExtraLine(string line);
80   bool ParseStackTraceLine(StackTraceTemplate* trace, string line);
81   bool NextStackTraceTemplate(StackTraceTemplate* trace, bool* last);
82 
83   void SetError(string desc);
84 
85   const string& buffer_;
86   const char* next_;
87   const char* end_;
88   stack<string> put_back_stack_;
89 
90   int line_no_;
91   bool error_;
92   string error_string_;
93 };
94 
95 #define PARSER_CHECK(cond, desc) do {\
96     if (!(cond)) {\
97       SetError(desc);\
98       return false;\
99     }} while ((void)0, 0)
100 
SetError(string desc)101 void Parser::SetError(string desc) {
102   error_ = true;
103   error_string_ = desc;
104 }
105 
GetError()106 bool Parser::GetError() {
107   return error_;
108 }
109 
GetErrorString()110 string Parser::GetErrorString() {
111   return error_string_;
112 }
113 
GetLineNo()114 int Parser::GetLineNo() {
115   return line_no_;
116 }
117 
NextLine()118 string Parser::NextLine() {
119   const char* first = next_;
120   while (!Eof() && *next_ != '\n') {
121     ++next_;
122   }
123   string line(first, next_ - first);
124   if (*next_ == '\n') {
125     ++next_;
126   }
127   ++line_no_;
128   return line;
129 }
130 
NextLineSkipComments()131 string Parser::NextLineSkipComments() {
132   string line;
133   if (!put_back_stack_.empty()) {
134     line = put_back_stack_.top();
135     put_back_stack_.pop();
136     return line;
137   }
138   while (!Eof()) {
139     line = NextLine();
140     // Skip empty lines.
141     if (line.empty())
142       continue;
143     // Skip comments.
144     if (line[0] == '#')
145       continue;
146     const char* p = line.c_str();
147     const char* e = p + line.size();
148     // Strip whitespace.
149     while (p < e && (*p == ' ' || *p == '\t'))
150       ++p;
151     if (p >= e)
152       continue;
153     const char* last = e - 1;
154     while (last > p && (*last == ' ' || *last == '\t'))
155       --last;
156     return string(p, last - p + 1);
157   }
158   return "";
159 }
160 
PutBackSkipComments(string line)161 void Parser::PutBackSkipComments(string line) {
162   put_back_stack_.push(line);
163 }
164 
ParseSuppressionToolsLine(Suppression * supp,string line)165 bool Parser::ParseSuppressionToolsLine(Suppression* supp, string line) {
166   size_t idx = line.find(':');
167   PARSER_CHECK(idx != string::npos, "expected ':' in tools line");
168   string s1 = line.substr(0, idx);
169   string s2 = line.substr(idx + 1);
170   PARSER_CHECK(!s1.empty(), "expected non-empty tool(s) name");
171   PARSER_CHECK(!s2.empty(), "expected non-empty warning name");
172   size_t idx2;
173   while ((idx2 = s1.find(',')) != string::npos) {
174     supp->tools.insert(s1.substr(0, idx2));
175     s1.erase(0, idx2 + 1);
176   }
177   supp->tools.insert(s1);
178   supp->warning_name = s2;
179   return true;
180 }
181 
ParseStackTraceLine(StackTraceTemplate * trace,string line)182 bool Parser::ParseStackTraceLine(StackTraceTemplate* trace, string line) {
183   if (line == "...") {
184     Location location = {LT_STAR, ""};
185     trace->locations.push_back(location);
186     return true;
187   } else {
188     size_t idx = line.find(':');
189     PARSER_CHECK(idx != string::npos, "expected ':' in stack trace line");
190     string s1 = line.substr(0, idx);
191     string s2 = line.substr(idx + 1);
192     if (s1 == "obj") {
193       Location location = {LT_OBJ, s2};
194       trace->locations.push_back(location);
195       return true;
196     } else if (s1 == "fun") {
197       Location location = {LT_FUN, s2};
198       // A suppression frame can only have ( or ) if it comes from Objective-C,
199       // i.e. starts with +[ or -[ or =[
200       PARSER_CHECK(s2.find_first_of("()") == string::npos ||
201                    (s2[1] == '[' && strchr("+-=", s2[0]) != NULL),
202                    "'fun:' lines can't contain '()'");
203 
204       // Check that we don't have template arguments in the suppression.
205       {
206         // Caveat: don't be confused by "operator>>" and similar...
207         size_t checked_till = 0;
208         // List of possible >>-like operators, sorted by the operation length.
209         const char *OP[] = {">>=", "<<=",
210                             ">>", "<<",
211                             ">=", "<=",
212                             "->", "->*",
213                             "<", ">"};
214         bool check_failed = false;
215         while (!check_failed && checked_till < s2.size()) {
216           size_t next = s2.find_first_of("<>", checked_till);
217           if (next == string::npos)
218             break;
219 
220           if (next < 8) {
221             // operatorX won't fit
222             check_failed = true;
223             break;
224           }
225 
226           for (size_t i = 0; i < TS_ARRAY_SIZE(OP); i++) {
227             size_t op_offset = ((string)OP[i]).find(s2[next]);
228             if (op_offset == string::npos)
229               continue;
230             if (next >= 8 + op_offset &&
231                 "operator" == s2.substr(next- (8 + op_offset), 8) &&
232                 OP[i] == s2.substr(next- op_offset, strlen(OP[i]))) {
233               checked_till = next + strlen(OP[i] + op_offset);
234               break;
235             }
236           }
237         }
238 
239         PARSER_CHECK(!check_failed, "'fun:' lines can't contain '<' or '>' "
240                      "except for operators");
241       }
242 
243       trace->locations.push_back(location);
244       return true;
245     } else {
246       SetError("bad stack trace line");
247       return false;
248     }
249   }
250 }
251 
252 // Checks if this line can not be parsed by Parser::NextStackTraceTemplate
253 // and, therefore, is an extra information for the suppression.
IsExtraLine(string line)254 bool Parser::IsExtraLine(string line) {
255   if (line == "..." || line == "{" || line == "}")
256     return false;
257   if (line.size() < 4)
258     return true;
259   string prefix = line.substr(0, 4);
260   return !(prefix == "obj:" || prefix == "fun:");
261 }
262 
NextStackTraceTemplate(StackTraceTemplate * trace,bool * last_stack_trace)263 bool Parser::NextStackTraceTemplate(StackTraceTemplate* trace,
264     bool* last_stack_trace) {
265   string line = NextLineSkipComments();
266   if (line == "}") {  // No more stack traces in multi-trace syntax
267     *last_stack_trace = true;
268     return false;
269   }
270 
271   if (line == "{") {  // A multi-trace syntax
272     line = NextLineSkipComments();
273   } else {
274     *last_stack_trace = true;
275   }
276 
277   while (true) {
278     if (!ParseStackTraceLine(trace, line))
279       return false;
280     line = NextLineSkipComments();
281     if (line == "}")
282       break;
283   }
284   return true;
285 }
286 
NextSuppression(Suppression * supp)287 bool Parser::NextSuppression(Suppression* supp) {
288   string line;
289   line = NextLineSkipComments();
290   if (line.empty())
291     return false;
292   // Opening {
293   PARSER_CHECK(line == "{", "expected '{'");
294   // Suppression name.
295   line = NextLineSkipComments();
296   PARSER_CHECK(!line.empty(), "expected suppression name");
297   supp->name = line;
298   // tool[,tool]:warning_name.
299   line = NextLineSkipComments();
300   PARSER_CHECK(!line.empty(), "expected tool[, tool]:warning_name line");
301   if (!ParseSuppressionToolsLine(supp, line))
302     return false;
303   if (0) {  // Not used currently. May still be needed later.
304     // A possible extra line.
305     line = NextLineSkipComments();
306     if (IsExtraLine(line))
307       supp->extra = line;
308     else
309       PutBackSkipComments(line);
310   }
311   // Everything else.
312   bool done = false;
313   while (!done) {
314     StackTraceTemplate trace;
315     if (NextStackTraceTemplate(&trace, &done))
316       supp->templates.push_back(trace);
317     if (error_)
318       return false;
319   }
320   // TODO(eugenis): Do we need to check for empty traces?
321   return true;
322 }
323 
324 struct Suppressions::SuppressionsRep {
325   vector<Suppression> suppressions;
326   string error_string_;
327   int error_line_no_;
328 };
329 
Suppressions()330 Suppressions::Suppressions() : rep_(new SuppressionsRep) {}
331 
~Suppressions()332 Suppressions::~Suppressions() {
333   delete rep_;
334 }
335 
ReadFromString(const string & str)336 int Suppressions::ReadFromString(const string &str) {
337   int sizeBefore = rep_->suppressions.size();
338   Parser parser(str);
339   Suppression supp;
340   while (parser.NextSuppression(&supp)) {
341     rep_->suppressions.push_back(supp);
342   }
343   if (parser.GetError()) {
344     rep_->error_string_ = parser.GetErrorString();
345     rep_->error_line_no_ = parser.GetLineNo();
346     return -1;
347   }
348   return rep_->suppressions.size() - sizeBefore;
349 }
350 
GetErrorString()351 string Suppressions::GetErrorString() {
352   return rep_->error_string_;
353 }
354 
GetErrorLineNo()355 int Suppressions::GetErrorLineNo() {
356   return rep_->error_line_no_;
357 }
358 
359 struct MatcherContext {
MatcherContextMatcherContext360   MatcherContext(
361       const vector<string>& function_names_mangled_,
362       const vector<string>& function_names_demangled_,
363       const vector<string>& object_names_) :
364       function_names_mangled(function_names_mangled_),
365       function_names_demangled(function_names_demangled_),
366       object_names(object_names_),
367       tmpl(NULL)
368   {}
369 
370   const vector<string>& function_names_mangled;
371   const vector<string>& function_names_demangled;
372   const vector<string>& object_names;
373   StackTraceTemplate* tmpl;
374 };
375 
MatchStackTraceRecursive(MatcherContext ctx,int trace_index,int tmpl_index)376 static bool MatchStackTraceRecursive(MatcherContext ctx, int trace_index,
377     int tmpl_index) {
378   const int trace_size = ctx.function_names_mangled.size();
379   const int tmpl_size = ctx.tmpl->locations.size();
380   while (trace_index < trace_size && tmpl_index < tmpl_size) {
381     Location& location = ctx.tmpl->locations[tmpl_index];
382     if (location.type == LT_STAR) {
383       ++tmpl_index;
384       while (trace_index < trace_size) {
385         if (MatchStackTraceRecursive(ctx, trace_index++, tmpl_index))
386           return true;
387       }
388       return false;
389     } else {
390       bool match = false;
391       if (location.type == LT_OBJ) {
392         match = StringMatch(location.name, ctx.object_names[trace_index]);
393       } else {
394         CHECK(location.type == LT_FUN);
395         match =
396           StringMatch(location.name, ctx.function_names_mangled[trace_index]) ||
397           StringMatch(location.name, ctx.function_names_demangled[trace_index]);
398       }
399       if (match) {
400         ++trace_index;
401         ++tmpl_index;
402       } else {
403         return false;
404       }
405     }
406   }
407   return tmpl_index == tmpl_size;
408 }
409 
StackTraceSuppressed(string tool_name,string warning_name,const vector<string> & function_names_mangled,const vector<string> & function_names_demangled,const vector<string> & object_names,string * name_of_suppression)410 bool Suppressions::StackTraceSuppressed(string tool_name, string warning_name,
411     const vector<string>& function_names_mangled,
412     const vector<string>& function_names_demangled,
413     const vector<string>& object_names,
414     string *name_of_suppression) {
415   MatcherContext ctx(function_names_mangled, function_names_demangled,
416       object_names);
417   for (vector<Suppression>::iterator it = rep_->suppressions.begin();
418        it != rep_->suppressions.end(); ++it) {
419     if (it->warning_name != warning_name ||
420         it->tools.find(tool_name) == it->tools.end())
421       continue;
422     for (vector<StackTraceTemplate>::iterator it2 = it->templates.begin();
423          it2 != it->templates.end(); ++it2) {
424       ctx.tmpl = &*it2;
425       bool result = MatchStackTraceRecursive(ctx, 0, 0);
426       if (result) {
427         *name_of_suppression = it->name;
428         return true;
429       }
430     }
431   }
432   return false;
433 }
434