/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   exceptions.c -- functions for dealing with exceptions in the interpreter.
   Created: Chris Toshok <toshok@hungry.com>
*/
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "runtimeint.h"
#include "arch.h"
#include "op_stack.h"

#include "nspr.h"
#include "plstr.h"

extern PRLogModuleInfo *exceptionLm;

/*
 * Map program counter to line number.  Requires the LineNumberBlock
 * to be sorted by increasing pc values.
 */
int
method_pc_to_line_number(HungryEnv *henv, MethodStruct *method, PRUint16 pc)
{
  int i = 0;
  PR_ASSERT(NULL != henv);
  PR_ASSERT(NULL != method);

  if (method->line_numbers)
    {
      for (i = 0; i < method->num_line_number_blocks; i++)
        {
          if (pc >= method->line_numbers[i].start_pc)
            return method->line_numbers[i].line_number;
        }
    }

  /* XXX Unknown line number */
  return -1;
}

PR_IMPLEMENT(void)
HVM_ExceptionPrintStackTrace(HungryEnv *henv,
                             japhar_object* throwable_ref,
                             japhar_object* stream_ref)
{
  ClazzFile *throwable_class, *stream_class;
  MethodStruct *getMessage, *println = NULL;
  japhar_object* msg = NULL;
  ClazzFile *exception_cf = throwable_ref->clazz;
  char *exceptionname = getClassName(env, exception_cf);
  japhar_object* exception_cache;
  ExceptionInfo *exc_info = HVM_ObjectGetNativeState(throwable_ref);
  BackTraceLevel *level;
  InterpValue msg_value;
  char *msg_to_print;
  japhar_object *msg_obj;

  if (stream_ref != NULL)
    {
      ClazzFile *stream_clazz = HVM_ClassFind(henv, "java/io/PrintWriter");
      PRBool pwriter = HVM_ObjectIsInstanceOf(henv, stream_ref, stream_clazz);
      if (pwriter)
        stream_class = HVM_ClassFind(henv, "java/io/PrintWriter");
      else
        stream_class = HVM_ClassFind(henv, "java/io/PrintStream");

      if (stream_class == NULL)
        {
          if (pwriter)
            abort_with_message("ExceptionPrintStackTrace could not "
                               "find java/io/PrintWriter");
          else
            abort_with_message("ExceptionPrintStackTrace could not "
                               "find java/io/PrintStream");
        }

      println = HVM_MethodFind(henv, stream_class, "println",
                               "(Ljava/lang/String;)V");
      if (println == NULL)
        abort_with_message("ExceptionPrintStacktrace could not "
                           "find method println");
    }

  level = exc_info->head;
  while (level)
    {
      int line_number = method_pc_to_line_number(henv, level->method,
                                                 level->pc);

      PR_ASSERT(NULL != level->method);
      if (level->method->access_flags & ACC_NATIVE)
        {
          msg_to_print = PR_smprintf("        in %s.%s(%s%snative method)",
                                     level->classname,
                                     level->method->name,
                                     level->filename ? level->filename : "",
                                     level->filename ? ", " : "");
        }
      else if (line_number == -1)
        {
          msg_to_print = PR_smprintf("        in %s.%s(%s%spc = %d)",
                                     level->classname,
                                     level->method->name,
                                     level->filename ? level->filename : "",
                                     level->filename ? ", " : "",
                                     level->pc);
        }
      else
        {
          msg_to_print = PR_smprintf("        at %s.%s(%s%s%d, pc = %d)",
                                     level->classname,
                                     level->method->name,
                                     level->filename ? level->filename : "",
                                     level->filename ? ":" : "line ",
                                     line_number,
                                     level->pc);
        }

      msg_obj = HVM_StringFromCString(henv, msg_to_print);
      if (msg_obj == NULL)
        abort_with_message("ExceptionPrintStackTrace unable to "
                           "allocate message");

      if (println != NULL)
        {
          HVM_MethodCall(henv, println, stream_ref, msg_obj);
        }
      else
        fprintf (stderr, "%s\n", msg_to_print);

      PR_smprintf_free(msg_to_print);

      level = level->next;
    }
}

void
toplevel_exception_handler(HungryEnv *henv,
                           japhar_object* throwable_ref)
{
  /* XXX remove the gc root */
  henv->_exception = NULL;

  HVM_ExceptionPrintStackTrace(henv, throwable_ref, (japhar_object*)NULL);
}

PR_IMPLEMENT(void)
HVM_ExceptionCleanup(HungryEnv *henv, japhar_object* throwable_ref)
{
  ExceptionInfo *exc_info = HVM_ObjectGetNativeState(throwable_ref);
  BackTraceLevel *level;

  if (exc_info == NULL) return;

  level = exc_info->head;
  while (level)
    {
      BackTraceLevel *next_level;
      PR_DELETE(level->classname);

      next_level = level->next;
      PR_DELETE(level);
      level = next_level;
    }

  PR_DELETE(exc_info);
  HVM_ObjectSetNativeState(throwable_ref, NULL);
}

static void
add_level_for_frame(HungryEnv *henv, japhar_object* throwable_ref,
                    StackFrame *throw_frame)
{
  ExceptionInfo *exc_info = HVM_ObjectGetNativeState(throwable_ref);
  BackTraceLevel *new_level = (BackTraceLevel*)PR_MALLOC(sizeof(BackTraceLevel));

  new_level->classname = PL_strdup(getClassName(henv, throw_frame->method->clazz));
  new_level->filename = throw_frame->method->clazz->source_filename;
  new_level->method = throw_frame->method;
  new_level->pc = throw_frame->pc;
  new_level->next = NULL;
  new_level->prev = NULL;

  /* link the new level into the list of levels */
  if (exc_info->tail)
    {
      new_level->prev = exc_info->tail;
      exc_info->tail->next = new_level;
      exc_info->tail = new_level;
    }
  else
    {
      exc_info->head =
        exc_info->tail = new_level;
    }
}

static PRBool
is_throwable(HungryEnv *henv, ClazzFile *cf)
{
  static ClazzFile *throwable_clazz = NULL;

  if (!throwable_clazz) throwable_clazz = HVM_ClassFind(henv, java_lang_Throwable);

  while (cf)
    {
      if (cf == throwable_clazz)
        return PR_TRUE;
      else
        cf = getSuperClass(henv, cf);
    }

  return PR_FALSE;
}

/*
** This method is kinda weird.  if we start at the topframe, recording
** everything, we end up with a stack trace that looks like this:
**
** java/lang/RuntimeException (Hi there xtoph)
**   in java/lang/Throwable.fillInStackTrace()Ljava/lang/Throwable;(Throwable.java, pc = 20432)
**   at java/lang/Throwable.<init>(Ljava/lang/String;)V(Throwable.java:93, pc = 8)
**   at java/lang/Exception.<init>(Ljava/lang/String;)V(Exception.java:42, pc = 5)
**   at java/lang/RuntimeException.<init>(Ljava/lang/String;)V(RuntimeException.java:47, pc = 5)
**   at Foo.fweep()V(Foo.java:4, pc = 9)
**   at Foo.bweep()V(Foo.java:8, pc = 3)
**   at Foo.main([Ljava/lang/String;)V(Foo.java:17, pc = 3)
**
** What we *want* is something that looks like this:
**
** java.lang.RuntimeException: Hi there xtoph
**         at Foo.fweep(Foo.java:4)
**         at Foo.bweep(Foo.java:9)
**         at Foo.main(Foo.java:18)
**
** so, we need to bump back up the stack until we get to a frame
** that's not the <init> method of something subclasses from
** java/lang/Throwable.
*/
PR_IMPLEMENT(void)
HVM_ExceptionFillInBacktraceFromStack(HungryEnv *henv,
                                      japhar_object* throwable_ref)
{
  StackFrame *frame = henv->top_frame;
  int found_first_non_init_frame = 0;

  /* first we skip the fillInStackTrace frame that is always present */
  frame = frame->parent;

  while (frame < henv->stack_highwater)
    {
      if (!found_first_non_init_frame)
        {
          if (!PL_strcmp(frame->method->name, "<init>")
              && is_throwable(henv, frame->method->clazz))
            {
              frame = frame->parent;
              continue;
            }
          else
            {
              found_first_non_init_frame = 1;
            }
          /* fall through */
        }

      add_level_for_frame(henv, throwable_ref, frame);

      frame = frame->parent;
    }
}

void
HVM_ExceptionThrowWithStackFrame(HungryEnv *henv, japhar_object* throwable_ref, StackFrame *f)
{
  StackFrame *throw_frame, *new_throw_frame;
#ifdef DEBUG
  ClazzFile *throwable_cf = throwable_ref->clazz;
#endif
  PR_ASSERT(throwable_ref);
  if (!throwable_ref) return;

  PR_LOG(exceptionLm, PR_LOG_DEBUG, ("in throw_exception()\n"));

  throw_frame = f;

  PR_LOG(exceptionLm, PR_LOG_DEBUG,
         ("Exception %s thrown from %s.%s - at pc %d\n",
          getClassName(ENV(throw_frame), throwable_cf),
          getClassName(ENV(throw_frame), throw_frame->method->clazz),
          throw_frame->method->name,
          throw_frame->pc));

  while (throw_frame != f->_env->stack_highwater)
    {
      if (throw_frame->flags & FRAME_NATIVE)
        {
          PR_LOG(exceptionLm, PR_LOG_DEBUG,
                 ("... we hit a native frame (%s.%s)\n",
                  getClassName(henv, throw_frame->method->clazz),
                  throw_frame->method->name));

          /* if we hit a native frame, we just return.
             the interpreter loop will return to it's caller
             if the exception hasn't been handled here. */

          return;
        }
      else
        {
          int i;

          PR_LOG(exceptionLm, PR_LOG_DEBUG,
                 ("... we're at a java frame (%s.%s pc %d)\n",
                  getClassName(henv, throw_frame->method->clazz),
                  throw_frame->method->name,
                  throw_frame->pc));

          for (i = 0; i < throw_frame->method->num_exception_blocks; i ++)
            {
              ClazzFile *catch_class;
              ExceptionBlock *exc_block = &throw_frame->method->exceptions[i];

              if (throw_frame->pc < exc_block->start_pc
                  || throw_frame->pc > exc_block->end_pc)
                continue;

              catch_class = ExceptionBlock_getHandlerClazz(henv,
                                                           throw_frame->method->clazz,
                                                           exc_block);

              if (!HVM_ObjectIsInstanceOf(henv, throwable_ref, catch_class))
                continue;

              /* we found a match.  reset the pc, push the exception
                 on the stack and return, but only after we mark where
                 the exception was thrown from. */

              throw_frame->pc = exc_block->handler_pc;

              henv->op_stack->stack_top = throw_frame->opstack_top;

              op_stack_push_object(henv->op_stack, throwable_ref);

              /* XXX remove the gc root */
              henv->_exception = NULL;

              PR_LOG(exceptionLm, PR_LOG_DEBUG,
                     ("Exception %s caught by %s.%s - at pc %d\n",
                      getClassName(henv, catch_class),
                      getClassName(henv, throw_frame->method->clazz),
                      throw_frame->method->name,
                      throw_frame->pc));

              return;
            }

          /* if we didn't find a match, pop the stack
             frame and do it again. */
          new_throw_frame = throw_frame->parent;
          pop_frame(henv);
          throw_frame = new_throw_frame;
        }
    }

  /* we fall off here, and will either unwind to the thread's start
     function, which will return and clean up the thread, or it will
     return to the toplevel JNI call that started this thread's
     interpreter loop. */
}

PR_IMPLEMENT(void)
HVM_ExceptionThrow(HungryEnv *henv,
                   const char *exception_name,
                   const char *format,
                   ...)
{
  va_list varargs;
  ClazzFile *exception_cls;
  char buf[1000];
  char *msg = NULL;

  exception_cls = HVM_ClassFind(henv, exception_name);

  if (!exception_cls)
    abort_with_message("Unable to raise exception.");

  if (format)
    {
      va_start(varargs, format);
      PR_vsnprintf(buf, sizeof(buf), format, varargs);
      va_end(varargs);
      msg = PL_strdup(buf);
    }

  HVM_ExceptionThrowWithClass(henv, exception_cls, msg);
}

PR_IMPLEMENT(japhar_object*)
HVM_ExceptionCreate(HungryEnv *henv,
                    ClazzFile *cf,
                    const char *message)
{
  MethodStruct* constructor;
  japhar_object* new_exception;

  new_exception = HVM_ObjectNew(henv, cf);

  if (message)
    {
      japhar_object* string;

      string = HVM_StringFromCString(henv, message);
      if (string == NULL)
        abort_with_message("ThrowNew unable to allocate message");

      constructor = HVM_MethodFind(henv, cf,
                                   "<init>", "(Ljava/lang/String;)V");

      HVM_MethodCall(henv, constructor, new_exception, string);
    }
  else
    {
      constructor = HVM_MethodFind(henv, cf, "<init>", "()V");

      HVM_MethodCallA(henv, constructor, new_exception, NULL);
    }

  return new_exception;
}

PR_IMPLEMENT(void)
HVM_ExceptionThrowWithClass(HungryEnv *henv,
                            ClazzFile *cf,
                            const char *message)
{
  henv->_exception = HVM_ExceptionCreate(henv, cf, message);
  HVM_ExceptionThrowWithStackFrame(henv, henv->_exception,
                                   henv->top_frame);
}
