/* 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 "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}; // A suppression frame can only have ( or ) if it comes from Objective-C, // i.e. starts with +[ or -[ or =[ PARSER_CHECK(s2.find_first_of("()") == string::npos || (s2[1] == '[' && strchr("+-=", s2[0]) != NULL), "'fun:' lines can't contain '()'"); PARSER_CHECK(s2.find_first_of("<>") == string::npos, "'fun:' lines can't contain '<>'"); 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; }