/* 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: Konstantin Serebryany.
// Author: Timur Iskhodzhanov.

// Experimental off-line race detector.
// Reads program events from a file and detects races.
// See http://code.google.com/p/data-race-test

// ------------- Includes ------------- {{{1
#include "thread_sanitizer.h"
#include "ts_events.h"

#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>

// ------------- Globals ------------- {{{1
static map<string, int> *g_event_type_map;
struct PcInfo {
  string img_name;
  string file_name;
  string rtn_name;
  int line;
};

static map<uintptr_t, PcInfo> *g_pc_info_map;

unsigned long offline_line_n;
//------------- Read binary file Utils ------------ {{{1
static const int kBufSize = 65536;

template<typename T>
static bool Read(FILE *fp, T *res) {
  unsigned char buf[16];
  int size = fread(buf, sizeof(T), 1, fp);
  *res = 0;
  for (unsigned int i=0; i<sizeof(T); i++) {
    *res <<= 8;
    *res += buf[i];
  }
  return size == 1;
}


static bool ReadANSI(FILE *file, string *res) {
  char buf[kBufSize];
  unsigned short length;
  if (!Read<unsigned short>(file, &length)) {
    return false;
  }
  int size = fread(buf, 1, (int)length, file);
  buf[length] = 0;
  *res = (char *)buf;
  return size == length;
}
//------------- Utils ------------------- {{{1
static EventType EventNameToEventType(const char *name) {
  map<string, int>::iterator it = g_event_type_map->find(name);
  if (it == g_event_type_map->end()) {
    Printf("Unknown event type: %s\n", name);
  }
  CHECK(it != g_event_type_map->end());
  return (EventType)it->second;
}

static void InitEventTypeMap() {
  g_event_type_map = new map<string, int>;
  for (int i = 0; i < LAST_EVENT; i++) {
    (*g_event_type_map)[kEventNames[i]] = i;
  }
}

static void SkipCommentText(FILE *file) {
  char buff[kBufSize];
  int i = 0;
  while (true) {
    int c = fgetc(file);
    if (c == EOF) break;
    if (c == '\n') {
      offline_line_n++;
      break;
    }
    if (i < kBufSize - 1)
      buff[i++] = c;
  }
  buff[i] = 0;
  if (buff[0] == 'P' && buff[1] == 'C') {
    char img[kBufSize];
    char rtn[kBufSize];
    char file[kBufSize];
    int line = 0;
    unsigned long pc = 0;
    if (sscanf(buff, "PC %lx %s %s %s %d", (unsigned long*)&pc,
               img, rtn, file, &line) == 5 &&
        pc != 0) {
      CHECK(g_pc_info_map);
      PcInfo pc_info;
      pc_info.img_name = img;
      pc_info.rtn_name = rtn;
      pc_info.file_name = file;
      pc_info.line = line;
      (*g_pc_info_map)[pc] = pc_info;
      // Printf("***** PC %lx %s\n", pc, rtn);
    }
  }
  if (buff[0] == '>') {
    // Just print the rest of comment.
    Printf("%s\n", buff + 2);
  }
}

static void SkipWhiteSpaceAndComments(FILE *file) {
  int c = 0;
  while (true) {
    c = fgetc(file);
    if (c == EOF) return;
    if (c == '#' || c == '=') {
      SkipCommentText(file);
      continue;
    }
    if (isspace(c)) continue;
    break;
  }
  ungetc(c, file);
}

typedef bool (*EventReader)(FILE *, Event *);

bool ReadOneStrEventFromFile(FILE *file, Event *event) {
  CHECK(event);
  char name[1024];
  uint32_t tid;
  unsigned long pc, a, info;
  SkipWhiteSpaceAndComments(file);
  offline_line_n++;
  if (5 == fscanf(file, "%s%x%lx%lx%lx", name, &tid, &pc, &a, &info)) {
    event->Init(EventNameToEventType(name), tid, pc, a, info);
    return true;
  }
  return false;
}

bool ProcessCodePosition(FILE *input, int *pc, string *str) {
  bool ok = Read<int>(input, pc);
  ok &= ReadANSI(input, str);
  return ok;
}

bool ProcessMessage(FILE *input, string *str) {
  return ReadANSI(input, str);
}

// Read information about event in format: [[[info] address] pc] tid.
bool ProcessEvent(FILE *input, EventType type, Event *event) {
  bool ok = true;
  unsigned short tid = 0;
  int pc = 0;
  int64_t address = 0;
  unsigned short extra = 0;
  // It's tricky switch without breaks.
  switch (type) {
    case THR_START:
      ok &= Read<unsigned short>(input, &extra);
      // fallthrough.
    case READ:
    case READER_LOCK:
    case SIGNAL:
    case THR_JOIN_AFTER:
    case UNLOCK:
    case WAIT:
    case WRITE:
    case WRITER_LOCK:
      ok &= Read<int64_t>(input, &address);
      // fallthrough.
    case EXPECT_RACE_BEGIN:
    case EXPECT_RACE_END:
    case RTN_EXIT:
    case SBLOCK_ENTER:
    case STACK_TRACE:
    case THR_END:
    case THR_FIRST_INSN:
      ok &= Read<int>(input, &pc);
      // fallthrough.
    case RTN_CALL:
      ok &= Read<unsigned short>(input, &tid);
      break;
    default:
      // read unsupported EventType.
      Printf("Unsupported EventType %s %d\n", type, (int)type);
      CHECK(false);
  }
  if (type == READ || type == WRITE) {
    extra = 1;
  }
  event->Init(type, (int)tid, pc, address, (int)extra);
  return ok;
}

bool ReadOneBinEventFromFile(FILE *input, Event *event) {
  CHECK(event);
  bool ok = true;
  EventType type;
  unsigned char typeOrd;
  int pc;
  int line;
  char rtn[kBufSize];
  char file[kBufSize];
  string str;
  while (ok) {
    offline_line_n++;
    ok &= Read<unsigned char>(input, &typeOrd);
    if (!ok) break;
    type = (EventType)typeOrd;
    switch (type) {
      case PC_DESCRIPTION:
        ok &= ProcessCodePosition(input, &pc, &str);
        if (sscanf(str.c_str(), "%s %s %d", rtn, file, &line) == 3 && pc != 0) {
          CHECK(g_pc_info_map);
          PcInfo pc_info;
          pc_info.img_name = "java";
          pc_info.rtn_name = rtn;
          pc_info.file_name = file;
          pc_info.line = line;
          (*g_pc_info_map)[pc] = pc_info;
        }
        break;
      case PRINT_MESSAGE:
        ok &= ProcessMessage(input, &str);
        // Just print the rest of comment.
        Printf("%s\n", str.c_str());
        break;
      default:
        ok &= ProcessEvent(input, type, event);
        return ok;
    }
  }
  return false;
}

void DecodeEventsFromFile(FILE *input, FILE *output) {
  offline_line_n = 0;
  bool ok = true;
  EventType type;
  unsigned char typeOrd;
  int pc;
  string str;
  Event event;
  while (ok) {
    ok &= Read<unsigned char>(input, &typeOrd);
    if (!ok) break;
    type = (EventType)typeOrd;
    switch (type) {
      case PC_DESCRIPTION:
        ok &= ProcessCodePosition(input, &pc, &str);
        fprintf(output, "#PC %x java %s\n", pc, str.c_str());
        break;
      case PRINT_MESSAGE:
        ok &= ProcessMessage(input, &str);
        fprintf(output, "#> %s\n", str.c_str());
        break;
      default:
        ok &= ProcessEvent(input, type, &event);
        fprintf(output, "%s %x %x %lx %lx\n", kEventNames[event.type()],
            event.tid(), (unsigned int)event.pc(),
            (long unsigned int)event.a(), (long unsigned int)event.info());
        break;
    }
    offline_line_n++;
  }
  Printf("INFO: ThreadSanitizer write %ld lines.\n", offline_line_n);
}

static const uint32_t max_unknown_thread = 10000;

static bool known_threads[max_unknown_thread] = {};

INLINE void ReadEventsFromFile(FILE *file, EventReader event_reader_cb) {
  Event event;
  uint64_t n_events = 0;
  offline_line_n = 0;
  while (event_reader_cb(file, &event)) {
    //event.Print();
    n_events++;
    uint32_t tid = event.tid();
    if (event.type() == THR_START && tid < max_unknown_thread) {
      known_threads[tid] = true;
    }
    if (tid >= max_unknown_thread || known_threads[tid]) {
      ThreadSanitizerHandleOneEvent(&event);
    }
  }
  Printf("INFO: ThreadSanitizerOffline: %ld events read\n", n_events);
}
//------------- ThreadSanitizer exports ------------ {{{1

void PcToStrings(uintptr_t pc, bool demangle,
                string *img_name, string *rtn_name,
                string *file_name, int *line_no) {
  if (g_pc_info_map->count(pc) == 0) {
    *img_name = "";
    *rtn_name = "";
    *file_name = "";
    *line_no = 0;
    return;
  }
  PcInfo &info = (*g_pc_info_map)[pc];
  *img_name = info.img_name;
  *rtn_name = info.rtn_name;
  *file_name = info.file_name;
  *line_no = info.line;
  if (*file_name == "unknown")
    *file_name = "";
}

string PcToRtnName(uintptr_t pc, bool demangle) {
  string img, rtn, file;
  int line;
  PcToStrings(pc, demangle, &img, &rtn, &file, &line);
  return rtn;
}
//------------- main ---------------------------- {{{1
int main(int argc, char *argv[]) {
  Printf("INFO: ThreadSanitizerOffline r%s\n", TS_VERSION);

  InitEventTypeMap();
  g_pc_info_map = new map<uintptr_t, PcInfo>;
  G_flags = new FLAGS;

  vector<string> args(argv + 1, argv + argc);
  ThreadSanitizerParseFlags(&args);
  ThreadSanitizerInit();

  CHECK(G_flags);
  if (G_flags->input_type == "bin") {
    ReadEventsFromFile(stdin, ReadOneBinEventFromFile);
  } else if (G_flags->input_type == "decode") {
    FILE* output;
    if (G_flags->log_file.size() > 0) {
      output = fopen(G_flags->log_file.c_str(), "w");
    } else {
      output = stdout;
    }
    DecodeEventsFromFile(stdin, output);
  } else if (G_flags->input_type == "str") {
    ReadEventsFromFile(stdin, ReadOneStrEventFromFile);
  } else {
    Printf("Error: Unknown input_type value %s\n", G_flags->input_type.c_str());
    exit(5);
  }

  ThreadSanitizerFini();
  if (G_flags->error_exitcode && GetNumberOfFoundErrors() > 0) {
    return G_flags->error_exitcode;
  }
}

// end. {{{1
// vim:shiftwidth=2:softtabstop=2:expandtab:tw=80