/* 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;
}