| /* Copyright (c) 2008-2010, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| // This file is part of ThreadSanitizer, a dynamic data race detector. |
| // Author: Evgeniy Stepanov. |
| |
| // This file contains the parser for valgrind-compatible suppressions. |
| |
| #include "common_util.h" |
| #include "ts_util.h" |
| |
| #include "suppressions.h" |
| |
| // TODO(eugenis): convert checks to warning messages. |
| // TODO(eugenis): write tests for incorrect syntax. |
| |
| enum LocationType { |
| LT_STAR, // ... |
| LT_OBJ, // obj: |
| LT_FUN, // fun: |
| }; |
| |
| struct Location { |
| LocationType type; |
| string name; |
| }; |
| |
| struct StackTraceTemplate { |
| vector<Location> locations; |
| }; |
| |
| struct Suppression { |
| string name; |
| set<string> tools; |
| string warning_name; |
| // Extra information available for some suppression types. |
| // ex.: Memcheck:Param |
| string extra; |
| vector<StackTraceTemplate> templates; |
| }; |
| |
| class Parser { |
| public: |
| explicit Parser(const string &str) |
| : buffer_(str), next_(buffer_.c_str()), |
| end_(buffer_.c_str() + buffer_.size()), line_no_(0), error_(false) {} |
| |
| bool NextSuppression(Suppression* suppression); |
| bool GetError(); |
| string GetErrorString(); |
| int GetLineNo(); |
| |
| private: |
| bool Eof() { return next_ >= end_; } |
| string NextLine(); |
| string NextLineSkipComments(); |
| void PutBackSkipComments(string line); |
| bool ParseSuppressionToolsLine(Suppression* supp, string line); |
| bool IsExtraLine(string line); |
| bool ParseStackTraceLine(StackTraceTemplate* trace, string line); |
| bool NextStackTraceTemplate(StackTraceTemplate* trace, bool* last); |
| |
| void SetError(string desc); |
| |
| const string& buffer_; |
| const char* next_; |
| const char* end_; |
| stack<string> put_back_stack_; |
| |
| int line_no_; |
| bool error_; |
| string error_string_; |
| }; |
| |
| #define PARSER_CHECK(cond, desc) do {\ |
| if (!(cond)) {\ |
| SetError(desc);\ |
| return false;\ |
| }} while (0) |
| |
| void Parser::SetError(string desc) { |
| error_ = true; |
| error_string_ = desc; |
| } |
| |
| bool Parser::GetError() { |
| return error_; |
| } |
| |
| string Parser::GetErrorString() { |
| return error_string_; |
| } |
| |
| int Parser::GetLineNo() { |
| return line_no_; |
| } |
| |
| string Parser::NextLine() { |
| const char* first = next_; |
| while (!Eof() && *next_ != '\n') { |
| ++next_; |
| } |
| string line(first, next_ - first); |
| if (*next_ == '\n') { |
| ++next_; |
| } |
| ++line_no_; |
| return line; |
| } |
| |
| string Parser::NextLineSkipComments() { |
| string line; |
| if (!put_back_stack_.empty()) { |
| line = put_back_stack_.top(); |
| put_back_stack_.pop(); |
| return line; |
| } |
| while (!Eof()) { |
| line = NextLine(); |
| // Skip empty lines. |
| if (line.empty()) |
| continue; |
| // Skip comments. |
| if (line[0] == '#') |
| continue; |
| const char* p = line.c_str(); |
| const char* e = p + line.size(); |
| // Strip whitespace. |
| while (p < e && (*p == ' ' || *p == '\t')) |
| ++p; |
| if (p >= e) |
| continue; |
| const char* last = e - 1; |
| while (last > p && (*last == ' ' || *last == '\t')) |
| --last; |
| return string(p, last - p + 1); |
| } |
| return ""; |
| } |
| |
| void Parser::PutBackSkipComments(string line) { |
| put_back_stack_.push(line); |
| } |
| |
| bool Parser::ParseSuppressionToolsLine(Suppression* supp, string line) { |
| size_t idx = line.find(':'); |
| PARSER_CHECK(idx != string::npos, "expected ':' in tools line"); |
| string s1 = line.substr(0, idx); |
| string s2 = line.substr(idx + 1); |
| PARSER_CHECK(!s1.empty(), "expected non-empty tool(s) name"); |
| PARSER_CHECK(!s2.empty(), "expected non-empty warning name"); |
| size_t idx2; |
| while ((idx2 = s1.find(',')) != string::npos) { |
| supp->tools.insert(s1.substr(0, idx2)); |
| s1.erase(0, idx2 + 1); |
| } |
| supp->tools.insert(s1); |
| supp->warning_name = s2; |
| return true; |
| } |
| |
| bool Parser::ParseStackTraceLine(StackTraceTemplate* trace, string line) { |
| if (line == "...") { |
| Location location = {LT_STAR, ""}; |
| trace->locations.push_back(location); |
| return true; |
| } else { |
| size_t idx = line.find(':'); |
| PARSER_CHECK(idx != string::npos, "expected ':' in stack trace line"); |
| string s1 = line.substr(0, idx); |
| string s2 = line.substr(idx + 1); |
| if (s1 == "obj") { |
| Location location = {LT_OBJ, s2}; |
| trace->locations.push_back(location); |
| return true; |
| } else if (s1 == "fun") { |
| Location location = {LT_FUN, s2}; |
| trace->locations.push_back(location); |
| return true; |
| } else { |
| SetError("bad stack trace line"); |
| return false; |
| } |
| } |
| } |
| |
| // Checks if this line can not be parsed by Parser::NextStackTraceTemplate |
| // and, therefore, is an extra information for the suppression. |
| bool Parser::IsExtraLine(string line) { |
| if (line == "..." || line == "{" || line == "}") |
| return false; |
| if (line.size() < 4) |
| return true; |
| string prefix = line.substr(0, 4); |
| return !(prefix == "obj:" || prefix == "fun:"); |
| } |
| |
| bool Parser::NextStackTraceTemplate(StackTraceTemplate* trace, |
| bool* last_stack_trace) { |
| string line = NextLineSkipComments(); |
| if (line == "}") { // No more stack traces in multi-trace syntax |
| *last_stack_trace = true; |
| return false; |
| } |
| |
| if (line == "{") { // A multi-trace syntax |
| line = NextLineSkipComments(); |
| } else { |
| *last_stack_trace = true; |
| } |
| |
| while (true) { |
| if (!ParseStackTraceLine(trace, line)) |
| return false; |
| line = NextLineSkipComments(); |
| if (line == "}") |
| break; |
| } |
| return true; |
| } |
| |
| bool Parser::NextSuppression(Suppression* supp) { |
| string line; |
| line = NextLineSkipComments(); |
| if (line.empty()) |
| return false; |
| // Opening { |
| PARSER_CHECK(line == "{", "expected '{'"); |
| // Suppression name. |
| line = NextLineSkipComments(); |
| PARSER_CHECK(!line.empty(), "expected suppression name"); |
| supp->name = line; |
| // tool[,tool]:warning_name. |
| line = NextLineSkipComments(); |
| PARSER_CHECK(!line.empty(), "expected tool[, tool]:warning_name line"); |
| if (!ParseSuppressionToolsLine(supp, line)) |
| return false; |
| if (0) { // Not used currently. May still be needed later. |
| // A possible extra line. |
| line = NextLineSkipComments(); |
| if (IsExtraLine(line)) |
| supp->extra = line; |
| else |
| PutBackSkipComments(line); |
| } |
| // Everything else. |
| bool done = false; |
| while (!done) { |
| StackTraceTemplate trace; |
| if (NextStackTraceTemplate(&trace, &done)) |
| supp->templates.push_back(trace); |
| if (error_) |
| return false; |
| } |
| // TODO(eugenis): Do we need to check for empty traces? |
| return true; |
| } |
| |
| struct Suppressions::SuppressionsRep { |
| vector<Suppression> suppressions; |
| string error_string_; |
| int error_line_no_; |
| }; |
| |
| Suppressions::Suppressions() : rep_(new SuppressionsRep) {} |
| |
| Suppressions::~Suppressions() { |
| delete rep_; |
| } |
| |
| int Suppressions::ReadFromString(const string &str) { |
| int sizeBefore = rep_->suppressions.size(); |
| Parser parser(str); |
| Suppression supp; |
| while (parser.NextSuppression(&supp)) { |
| rep_->suppressions.push_back(supp); |
| } |
| if (parser.GetError()) { |
| rep_->error_string_ = parser.GetErrorString(); |
| rep_->error_line_no_ = parser.GetLineNo(); |
| return -1; |
| } |
| return rep_->suppressions.size() - sizeBefore; |
| } |
| |
| string Suppressions::GetErrorString() { |
| return rep_->error_string_; |
| } |
| |
| int Suppressions::GetErrorLineNo() { |
| return rep_->error_line_no_; |
| } |
| |
| struct MatcherContext { |
| MatcherContext( |
| const vector<string>& function_names_mangled_, |
| const vector<string>& function_names_demangled_, |
| const vector<string>& object_names_) : |
| function_names_mangled(function_names_mangled_), |
| function_names_demangled(function_names_demangled_), |
| object_names(object_names_), |
| tmpl(NULL) |
| {} |
| |
| const vector<string>& function_names_mangled; |
| const vector<string>& function_names_demangled; |
| const vector<string>& object_names; |
| StackTraceTemplate* tmpl; |
| }; |
| |
| static bool MatchStackTraceRecursive(MatcherContext ctx, int trace_index, |
| int tmpl_index) { |
| const int trace_size = ctx.function_names_mangled.size(); |
| const int tmpl_size = ctx.tmpl->locations.size(); |
| while (trace_index < trace_size && tmpl_index < tmpl_size) { |
| Location& location = ctx.tmpl->locations[tmpl_index]; |
| if (location.type == LT_STAR) { |
| ++tmpl_index; |
| while (trace_index < trace_size) { |
| if (MatchStackTraceRecursive(ctx, trace_index++, tmpl_index)) |
| return true; |
| } |
| return false; |
| } else { |
| bool match = false; |
| if (location.type == LT_OBJ) { |
| match = StringMatch(location.name, ctx.object_names[trace_index]); |
| } else { |
| CHECK(location.type == LT_FUN); |
| match = |
| StringMatch(location.name, ctx.function_names_mangled[trace_index]) || |
| StringMatch(location.name, ctx.function_names_demangled[trace_index]); |
| } |
| if (match) { |
| ++trace_index; |
| ++tmpl_index; |
| } else { |
| return false; |
| } |
| } |
| } |
| return tmpl_index == tmpl_size; |
| } |
| |
| bool Suppressions::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) { |
| MatcherContext ctx(function_names_mangled, function_names_demangled, |
| object_names); |
| for (vector<Suppression>::iterator it = rep_->suppressions.begin(); |
| it != rep_->suppressions.end(); ++it) { |
| if (it->warning_name != warning_name || |
| it->tools.find(tool_name) == it->tools.end()) |
| continue; |
| for (vector<StackTraceTemplate>::iterator it2 = it->templates.begin(); |
| it2 != it->templates.end(); ++it2) { |
| ctx.tmpl = &*it2; |
| bool result = MatchStackTraceRecursive(ctx, 0, 0); |
| if (result) { |
| *name_of_suppression = it->name; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |