/FileExtraction/Java_unseen/dlitz_resin/modules/quercus/src/com/caucho/quercus/lib/ErrorModule.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.quercus.lib;

import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.*;
import com.caucho.quercus.expr.Expr;
import com.caucho.quercus.expr.CallExpr;
import com.caucho.quercus.expr.FunIncludeExpr;
import com.caucho.quercus.expr.FunIncludeOnceExpr;
import com.caucho.quercus.expr.ObjectMethodExpr;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.quercus.module.IniDefinitions;
import com.caucho.quercus.module.IniDefinition;
import com.caucho.quercus.lib.file.FileModule;

import com.caucho.util.L10N;

import java.util.logging.Logger;

/**
 * PHP error handling.
 */
public class ErrorModule extends AbstractQuercusModule {
  private static final L10N L = new L10N(ErrorModule.class);
  private static final Logger log
    = Logger.getLogger(ErrorModule.class.getName());

  private static final IniDefinitions _iniDefinitions = new IniDefinitions();

  public static final int E_ERROR = Env.E_ERROR;
  public static final int E_WARNING = Env.E_WARNING;
  public static final int E_PARSE = Env.E_PARSE;
  public static final int E_NOTICE = Env.E_NOTICE;
  public static final int E_CORE_ERROR = Env.E_CORE_ERROR;
  public static final int E_CORE_WARNING = Env.E_CORE_WARNING;
  public static final int E_COMPILE_ERROR = Env.E_COMPILE_ERROR;
  public static final int E_COMPILE_WARNING = Env.E_COMPILE_WARNING;
  public static final int E_USER_ERROR = Env.E_USER_ERROR;
  public static final int E_USER_WARNING = Env.E_USER_WARNING;
  public static final int E_USER_NOTICE = Env.E_USER_NOTICE;
  public static final int E_ALL = Env.E_ALL;
  public static final int E_STRICT = Env.E_STRICT;
  public static final int E_RECOVERABLE_ERROR = Env.E_RECOVERABLE_ERROR;

  public static final StringValue ARGS = new ConstStringValue("args");
  public static final StringValue CLASS = new ConstStringValue("class");
  public static final StringValue FILE = new ConstStringValue("file");
  public static final StringValue FUNCTION = new ConstStringValue("function");
  public static final StringValue LINE = new ConstStringValue("line");
  public static final StringValue TYPE = new ConstStringValue("type");

  /**
   * Returns the default php.ini values.
   */
  public IniDefinitions getIniDefinitions()
  {
    return _iniDefinitions;
  }

  /**
   * Exits
   */
  public static Value die(Env env, @Optional String msg)
  {
    if (msg != null)
      return env.die(msg);
    else
      return env.die();
  }
  
  /**
   * Produces a backtrace
   */
  public static Value debug_backtrace(Env env)
  {
    ArrayValue result = new ArrayValueImpl();
    
    Exception e = new Exception();
    e.fillInStackTrace();

    StackTraceElement []stack = e.getStackTrace();
    int depth = 0;

    for (int i = 1; i < stack.length; i++) {
      StackTraceElement elt = stack[i];

      String name = elt.getMethodName();
      String className = elt.getClassName();

      if (name.equals("executeTop")) {
        return result;
      }
      else if (className.startsWith("_quercus._")
               && name.equals("call")) {
        String path = unmangleFile(className);
        String fileName = env
            .getQuercus().getPwd().lookup("./" + path).getNativePath();
        
        String fun = findFunction(stack, i);

        if (fun == null || fun.equals("debug_backtrace"))
          continue;

        ArrayValue call = new ArrayValueImpl();
        result.put(call);

        call.put(FILE, env.createString(fileName));
        call.put(LINE, LongValue.create(
            env.getSourceLine(className, elt.getLineNumber())));

        call.put(FUNCTION, env.createString(fun));

        call.put(ARGS, new ArrayValueImpl());
      }
      else if (className.startsWith("_quercus._")
               && name.equals("callMethod")) {
        String path = unmangleFile(className);
        String fileName = env
            .getQuercus().getPwd().lookup("./" + path).getNativePath();
        
        ArrayValue call = new ArrayValueImpl();
        result.put(call);

        call.put(FILE, env.createString(fileName));
        call.put(LINE, LongValue.create(
            env.getSourceLine(className, elt.getLineNumber())));
        
        call.put(FUNCTION, env.createString(unmangleFunction(className)));
        call.put(CLASS, env.createString(unmangleClass(className)));
        call.put(TYPE, env.createString("->"));

        call.put(ARGS, new ArrayValueImpl());
      }
      else if (className.startsWith("_quercus._")
               && name.equals("execute")) {
        String methodName = stack[i - 1].getMethodName();

        String path = unmangleFile(className);

        String fileName = env
            .getQuercus().getPwd().lookup("./" + path).getNativePath();

        ArrayValue call = new ArrayValueImpl();
        
        call.put(FILE, env.createString(fileName));
        call.put(LINE, LongValue.create(
            env.getSourceLine(className, elt.getLineNumber())));

        if (methodName.equals("includeOnce")) {
          call.put(FUNCTION, env.createString("include_once"));
          
          result.put(call);
        }
        else if (methodName.equals("include")) {
          call.put(FUNCTION, env.createString("include"));
          
          result.put(call);
        }
        else if (methodName.equals("callNew")) {
        }
        else if (methodName.equals("createException")) {
        }
        else {
          String fun = findFunction(stack, i);
          
          if (fun == null || fun.equals("debug_backtrace")) {
          }
          else {
            call.put(FUNCTION, env.createString(fun));
            
            result.put(call);
          }
        }
      }
      else if (className.equals("com.caucho.quercus.expr.FunctionExpr")
               && name.equals("evalImpl")) {
        if (stack[i - 1].getMethodName().equals("evalArguments")) {
        }
        else
          addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.expr.MethodCallExpr")
               && name.equals("eval")) {
        if (stack[i - 1].getMethodName().equals("evalArguments")) {
        }
        else
          addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.expr.NewExpr")
               && name.equals("eval")) {
        if (stack[i - 1].getMethodName().equals("evalArguments")) {
        }
        else
          addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.expr.IncludeExpr")
               && name.equals("eval")) {
        addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.expr.IncludeOnceExpr")
               && name.equals("eval")) {
        addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.expr.CallExpr")) {
        addInterpreted(env, result, depth++);
      }
      else if (className.equals("com.caucho.quercus.env.Env")
               && name.equals("close")) {
        return result;
      }
      else if (className.startsWith("com.caucho.quercus")) {
      }
      else if (name.equals("invoke") || name.equals("invoke0")) {
      }
      else {
        ArrayValue call = new ArrayValueImpl();
        result.put(call);

        call.put(FILE, env.createString(elt.getFileName()));
        call.put(LINE, LongValue.create(elt.getLineNumber()));
        
        call.put(FUNCTION, env.createString(elt.getMethodName()));
        call.put(CLASS, env.createString(elt.getClassName()));

        call.put(ARGS, new ArrayValueImpl());
      }
    }

    return result;
  }
  
  private static String findFunction(StackTraceElement []stack, int i)
  {
    String className = stack[i].getClassName();
    String methodName = stack[i].getMethodName();
    
    if (i == 0)
      return unmangleFunction(className);
    
    String prevClassName = stack[i - 1].getClassName();
    String prevMethodName = stack[i - 1].getMethodName();

    if (className.startsWith("_quercus._")
        && methodName.startsWith("call"))
      return unmangleFunction(className);
    else if (prevClassName.startsWith("_quercus._")
        && prevMethodName.startsWith("call"))
      return unmangleFunction(prevClassName);
    else
      return null;
  }
  
  private static void addInterpreted(Env env, ArrayValue result, int i)
  {
    Expr expr = env.peekCall(i);

    if (expr instanceof CallExpr) {
      CallExpr callExpr = (CallExpr) expr;

      String functionName = callExpr.getName();
      if ("debug_backtrace".equals(functionName))
        return;
        
      ArrayValue call = new ArrayValueImpl();
      result.put(call);
      
      if (callExpr.getFileName() != null) {
        call.put(FILE,
                 env.createString(callExpr.getFileName()));
        call.put(LINE,
                 LongValue.create(callExpr.getLine()));
      }

      call.put(FUNCTION,
               env.createString(callExpr.getName()));

      // Create "args" argument value array

      // evaluating args a second time is problematic, affecting mediawiki
      // php/180q
      //ArrayValueImpl args = evalArgsArray(env, callExpr);

      ArrayValueImpl args = new ArrayValueImpl(env.peekArgs(i));
      
      call.put(ARGS, args);
    }
    else if (expr instanceof ObjectMethodExpr) {
      ObjectMethodExpr callExpr = (ObjectMethodExpr) expr;

      ArrayValue call = new ArrayValueImpl();
      result.put(call);
      
      if (callExpr.getFileName() != null) {
        call.put(FILE,
                 env.createString(callExpr.getFileName()));
        call.put(LINE,
                 LongValue.create(callExpr.getLine()));
      }

      call.put(FUNCTION,
               env.createString(callExpr.getName()));

      call.put(CLASS,
               env.createString(env.peekCallThis(i).getClassName()));

      call.put(TYPE, env.createString("->"));

      call.put(ARGS, new ArrayValueImpl());
    }
    else if (expr instanceof FunIncludeExpr) {
      ArrayValue call = new ArrayValueImpl();
      result.put(call);
      
      if (expr.getFileName() != null) {
        call.put(FILE, env.createString(expr.getFileName()));
        call.put(LINE, LongValue.create(expr.getLine()));
      }

      call.put(FUNCTION, env.createString("include"));
    }
    else if (expr instanceof FunIncludeOnceExpr) {
      boolean isRequire = ((FunIncludeOnceExpr) expr).isRequire();
      
      ArrayValue call = new ArrayValueImpl();
      result.put(call);
      
      if (expr.getFileName() != null) {
        call.put(FILE, env.createString(expr.getFileName()));
        call.put(LINE, LongValue.create(expr.getLine()));
      }
    
      String name;
      
      if (isRequire)
        name = "require_once";
      else
        name = "include_once";
      
      call.put(FUNCTION, env.createString(name));
    }
  }

  // Return an array that contains the values passed
  // into a function as the arguments. IF no values
  // were passed this method returns an empty array.

  private static ArrayValueImpl evalArgsArray(Env env, CallExpr callExpr)
  {
    ArrayValueImpl args = new ArrayValueImpl();

    Value []argsValues = callExpr.evalArguments(env);

    if (argsValues != null) {
      for (int index = 0; index < argsValues.length; index++) {
        Value ref = argsValues[index].toLocalVarDeclAsRef();
        args.put(ref);
      }
    }

    return args;
  }

  private static String unmangleFile(String className)
  {
    int i = "_quercus".length();
    int end = className.indexOf('$');

    if (end < 0)
      end = className.length();
    
    StringBuilder sb = new StringBuilder();

    for (; i < end; i++) {
      char ch = className.charAt(i);

      if (ch == '.' && className.charAt(i + 1) == '_') {
        sb.append('/');
        i++;
      }
      else if (ch != '_') {
        sb.append(ch);
      }
      else if (className.charAt(i + 1) == '_') {
        sb.append('.');
        i++;
      }
      else {
//        System.out.println(
//            "UNKNOWN:" + className.charAt(i + 1) + " " + className);
      }
    }

    return sb.toString();
  }

  private static String unmangleFunction(String className)
  {
    int p = className.lastIndexOf("$fun_");

    if (p > 0) {
      className = className.substring(p + "$fun_".length());

      p = className.lastIndexOf('_');
      if (p > 0)
        return className.substring(0, p);
      else
        return className;
    }
    else
      return className;
  }

  private static String unmangleClass(String className)
  {
    int p = className.lastIndexOf("$quercus_");
    int q = className.lastIndexOf("$");

    if (p > 0 && p < q)
      className = className.substring(p + "$quercus_".length(), q);

    int i = className.lastIndexOf("_");
    
    if (i >= 0)
      return className.substring(0, i);
    else
      return className;
  }

  /**
   * Write an error
   */
  /*
  public Value error(Env env, String msg)
    throws Exception
  {
    // XXX: valiate
    env.error(msg);

    return NullValue.NULL;
  }
  */

  /**
   * Exits
   */
  public Value exit(Env env, @Optional Value msg)
  {
    return env.exit(msg);
  }

  /**
   * Send a message to the log.
   */
  public static boolean error_log(Env env,
                                  StringValue message,
                                  @Optional("0") int type,
                                  @Optional StringValue destination,
                                  @Optional StringValue extraHeaders)
  {
    if (type == 3) {
      // message is appended to the file destination, no newline added

      Value numBytes = FileModule.file_put_contents(env,
        destination, message, FileModule.FILE_APPEND, null);

      if (numBytes == BooleanValue.FALSE)
        return false;
      if (numBytes.toLong() == message.length())
        return true;
      else
        return false;
    } else if (type == 2) {
      // XXX : message sent via remote debugging connection

      return false;
    } else if (type == 1) {
      // XXX : message sent by email to the address in destination

      return false;
    } else {
      // message sent to PHP's system logger
      String dest = ErrorModule.INI_ERROR_LOG.getAsString(env);
      if("syslog".equals(dest))
      {
        log.warning(message.toString());
        return true;
      } else {
        return ErrorModule.error_log(env, message, 3,
                                     env.createString(dest), extraHeaders);
      }
    }
  }

  /**
   * Changes the error reporting value.
   */
  public static long error_reporting(Env env,
                                     @Optional Value levelV)
  {
    long oldMask = env.getIni("error_reporting").toLong();

    if (! levelV.isDefault())
      env.setIni("error_reporting", levelV);
    
    return oldMask;
  }

  /**
   * Restores the error handler
   *
   * @param env the quercus environment
   */
  public static boolean restore_error_handler(Env env)
  {
    env.restoreErrorHandler();

    return true;
  }

  /**
   * Sets an error handler
   *
   * @param env the quercus environment
   * @param fun the error handler
   * @param code errorMask error level
   */
  public static boolean set_error_handler(Env env,
                                          Callable fun,
                                          @Optional("E_ALL") int errorMask)
  {
    env.setErrorHandler(errorMask, fun);

    return true;
  }

  /**
   * Sets an exception handler
   *
   * @param env the quercus environment
   * @param fun the exception handler
   */
  public static Value set_exception_handler(Env env,
                                            Callable fun)
  {
    return env.setExceptionHandler(fun);
  }

  /**
   * Restore an exception handler
   *
   * @param env the quercus environment
   */
  public static Value restore_exception_handler(Env env)
  {
    env.restoreExceptionHandler();

    return BooleanValue.TRUE;
  }

  /**
   * Triggers an error.
   *
   * @param env the quercus environment
   * @param msg the error message
   * @param code the error level
   */
  public static Value trigger_error(Env env,
                                    String msg,
                                    @Optional("E_USER_NOTICE") int code)
  {
    switch (code) {
    case Env.E_USER_NOTICE:
      env.error(Env.B_USER_NOTICE, "", msg);
      return BooleanValue.TRUE;

    case Env.E_USER_WARNING:
      env.error(Env.B_USER_WARNING, "", msg);
      return BooleanValue.TRUE;

    case Env.E_USER_ERROR:
      env.error(Env.B_USER_ERROR, "", msg);
      return BooleanValue.TRUE;

    default:
      env.warning(L.l("'0x{0}' is an invalid error type",
                      Integer.toHexString(code)));

      return BooleanValue.FALSE;
    }
  }

  /**
   * Triggers an error.
   *
   * @param env the quercus environment
   * @param msg the error message
   * @param code the error level
   */
  public Value user_error(Env env,
                          String msg,
                          @Optional("E_USER_NOTICE") int code)
  {
    return trigger_error(env, msg, code);
  }

  static final IniDefinition INI_ERROR_REPORING
    = _iniDefinitions.add("error_reporting", Env.E_DEFAULT, PHP_INI_ALL);
  static final IniDefinition INI_DISPLAY_ERRORS
    = _iniDefinitions.add("display_errors", "1", PHP_INI_ALL);
  static final IniDefinition INI_DISPLAY_STARTUP_ERRORS
    = _iniDefinitions.add("display_startup_errors", false, PHP_INI_ALL);
  static final IniDefinition INI_LOG_ERRORS
    = _iniDefinitions.add("log_errors", false, PHP_INI_ALL);
  static final IniDefinition INI_LOG_ERRORS_MAX_LEN
    = _iniDefinitions.add("log_errors_max_len", 1024, PHP_INI_ALL);
  static final IniDefinition INI_IGNORE_REPEATED_ERRORS
    = _iniDefinitions.add("ignore_repeated_errors", false, PHP_INI_ALL);
  static final IniDefinition INI_IGNORE_REPEATED_SOURCE
    = _iniDefinitions.add("ignore_repeated_source", false, PHP_INI_ALL);
  static final IniDefinition INI_REPORT_MEMLEAKS
    = _iniDefinitions.add("report_memleaks", true, PHP_INI_ALL);
  static final IniDefinition INI_TRACK_ERRORS
    = _iniDefinitions.add("track_errors", false, PHP_INI_ALL);
  static final IniDefinition INI_HTML_ERRORS
    = _iniDefinitions.add("html_errors", true, PHP_INI_ALL);
  static final IniDefinition INI_DOCREF_ROOT
    = _iniDefinitions.add("docref_root", "", PHP_INI_ALL);
  static final IniDefinition INI_DOCREF_EXT
    = _iniDefinitions.add("docref_ext", "", PHP_INI_ALL);
  static final IniDefinition INI_ERROR_PREPEND_STRING
    = _iniDefinitions.add("error_prepend_string", null, PHP_INI_ALL);
  static final IniDefinition INI_ERROR_APPEND_STRING
    = _iniDefinitions.add("error_append_string", null, PHP_INI_ALL);
  static final IniDefinition INI_ERROR_LOG
    = _iniDefinitions.add("error_log", null, PHP_INI_ALL);
  static final IniDefinition INI_WARN_PLUS_OVERLOADING
    = _iniDefinitions.add("warn_plus_overloading", null, PHP_INI_ALL);
}

