/// <summary> /// Extracts debug information from an eval trace and returns it in a form of extended file name: /// {full canonical file name of the inner most eval source file} /// { inside {eval|assert|...|run-time funcion} (on line #, column #) }* /// </summary> private static string EvalTraceToFileName(List <ErrorStackInfo> /*!*/ trace, string sourceRoot, bool html) { StringBuilder sb = new StringBuilder(); for (int i = trace.Count - 1; i > 0; i--) { ErrorStackInfo info = trace[i]; if (sb.Length == 0) { sb.Append(Path.GetFullPath(Path.Combine(sourceRoot, info.File))); } if (info.Caller != null) { sb.Append(' '); if (info.Line >= 0 && info.Column >= 0) { sb.Append(CoreResources.GetString(html ? "error_message_html_eval_debug" : "error_message_plain_eval_debug", info.Caller, info.Line, info.Column)); } else { sb.Append(CoreResources.GetString(html ? "error_message_html_eval" : "error_message_plain_eval", info.Caller)); } } } return(sb.ToString()); }
/// <summary> /// Modifies a specified eval trace such that all transparent evals get hidden. /// The others will have updated line numbers. /// </summary> private static void HideTransparentEvals(List <ErrorStackInfo> /*!*/ trace) { int add_line = 0; for (int i = trace.Count - 1; i >= 0; i--) { ErrorStackInfo info = trace[i]; if (info.Caller == null) { // skips the frame if there is no debug info: if (info.Line > 0) { add_line += info.Line - 1; } } else if (add_line > 0) { // skips the frame if there is no debug info: if (info.Line > 0) { info.Line += add_line; // replace struct: trace[i] = info; } add_line = 0; } } Debug.Assert(add_line == 0); }
/// <summary> /// Creates a new PHP stack frame. /// </summary> /// <param name="context">A script context.</param> /// <param name="frame">The respective CLR frame.</param> /// <param name="kind">A kind of the frame.</param> internal PhpStackFrame(ScriptContext /*!*/ context, StackFrame /*!*/ frame, FrameKinds kind) { Debug.Assert(context != null && frame != null && kind != FrameKinds.Invisible); this.frame = frame; this.kind = kind; MethodBase method = frame.GetMethod(); if (kind == FrameKinds.ClassLibraryFunction) { this.name = ImplementsFunctionAttribute.Reflect(method).Name; SetDebugInfo(frame); } else { Type type = method.DeclaringType; int eval_id = TransientAssembly.InvalidEvalId; if (type != null && context.ApplicationContext.IsTransientRealType(type)) { // gets [PhpEvalId] attribute defined on the type: object[] attrs = type.GetCustomAttributes(typeof(PhpEvalIdAttribute), false); eval_id = ((PhpEvalIdAttribute)attrs[0]).Id; ErrorStackInfo info = new ErrorStackInfo(); PhpStackTrace.FillEvalStackInfo(context, eval_id, ref info, false); this.line = info.Line; this.column = info.Column; this.file = info.File; this.name = info.Caller; } else { SetDebugInfo(frame); } // the caller has already been set by FillEvalStackInfo // if it is not an eval main: if (!(eval_id != TransientAssembly.InvalidEvalId && PhpScript.IsScriptType(type) && kind == FrameKinds.Main)) { if (this.name == ScriptModule.MainHelperName) { this.name = "{main}"; } else { int j; PhpScript.ParseMDeclName(method.Name, out this.name, out j); } } } }
/// <summary> /// Initializes a new instance of the PhpException class with serialized data. This constructor is used /// when an exception is thrown in a remotely called method. Such an exceptions needs to be serialized, /// transferred back to the caller and then rethrown using this constructor. /// </summary> /// <param name="info">The SerializationInfo that holds the serialized object data about the exception /// being thrown.</param> /// <param name="context">The StreamingContext that contains contextual information about the source or /// destination.</param> protected PhpException(SerializationInfo info, StreamingContext context) : base(info, context) { this.error = (PhpError)info.GetValue("error", typeof(PhpError)); this.info = new ErrorStackInfo( (string)info.GetString("file"), (string)info.GetString("caller"), (int)info.GetInt32("line"), (int)info.GetInt32("column"), (bool)info.GetBoolean("libraryCaller")); }
/// <summary> /// Reports error thrown by compiler. /// </summary> internal static void ThrowByWebCompiler(PhpError error, int id, string sourceFile, int line, int column, string message) { ErrorStackInfo info = new ErrorStackInfo(sourceFile, null, line, column, false); // gets the current script context and config: LocalConfiguration config = Configuration.Local; #if !SILVERLIGHT ReportError(config, HttpContext.Current.Response.Output, error, id, info, message); #else ReportError(config, new StreamWriter(ScriptContext.CurrentContext.OutputStream), error, id, info, message); #endif if (((PhpErrorSet)error & PhpErrorSet.Fatal) != 0) { throw new PhpException(error, message, info); } }
/// <summary> /// Reports an error to log file, event log and to output (as configured). /// </summary> private static void ReportError(LocalConfiguration config, TextWriter output, PhpError error, int id, ErrorStackInfo info, string message) { string formatted_message = FormatErrorMessageOutput(config, error, id, info, message); // logs error if logging is enabled: if (config.ErrorControl.EnableLogging) { #if SILVERLIGHT throw new NotSupportedException("Logging is not supported on Silverlight. Set EnableLogging to false."); #else // adds a message to log file: if (config.ErrorControl.LogFile != null) { try { // <error>: <caller>(): <message> in <file> on line <line> string caller = (info.Caller != null) ? (info.Caller + "(): ") : null; string place = (info.Line > 0 && info.Column > 0) ? CoreResources.GetString("error_place", info.File, info.Line, info.Column) : null; Logger.AppendLine(config.ErrorControl.LogFile, string.Concat(error, ": ", caller, message, place)); } catch (Exception) { } } // adds a message to event log: if (config.ErrorControl.SysLog) { try { Logger.AddToEventLog(message); } catch (Exception) { } } #endif } // displays an error message if desired: if (config.ErrorControl.DisplayErrors) { output.Write(config.ErrorControl.ErrorPrependString); output.Write(formatted_message); output.Write(config.ErrorControl.ErrorAppendString); } }
/// <summary> /// /// </summary> /// <param name="info"></param> /// <param name="config"></param> /// <returns>Returns caller name with () or null. Formatted for the current output capabilities.</returns> internal static string FormatErrorCallerName(ErrorStackInfo info, LocalConfiguration config) { if (info.Caller == null) { return(null); } if (config.ErrorControl.HtmlMessages && config.ErrorControl.DocRefRoot != null && info.LibraryCaller) { // able to display HTML return(String.Format("<a href='{0}/function.{1}{2}'>{3}()</a>", config.ErrorControl.DocRefRoot, info.Caller.Replace('_', '-').ToLower(), config.ErrorControl.DocRefExtension, info.Caller)); } else { return(info.Caller + "()"); } }
/// <summary> /// Fills an instance of <see cref="ErrorStackInfo"/> with information gathered from eval transient debug info. /// </summary> /// <param name="context">Script context.</param> /// <param name="evalId">An id of the inner-most eval where an error occured.</param> /// <param name="result">The resulting error stack info.</param> /// <param name="html">Whether the message is used in HTML.</param> internal static void FillEvalStackInfo(ScriptContext /*!*/ context, int evalId, ref ErrorStackInfo result, bool html) { Debug.Assert(context != null); FullPath source_root = Configuration.Application.Compiler.SourceRoot; // stack info about the error position (with respect to inner most eval): result.Line = context.EvalLine; result.Column = context.EvalColumn; result.Caller = "<error>"; result.File = null; List <ErrorStackInfo> infos = new List <ErrorStackInfo>(); infos.Add(result); // fills "infos" with full eval error trace: context.ApplicationContext.TransientAssemblyBuilder.TransientAssembly.GetEvalFullTrace(evalId, infos); Debug.WriteLine("EVAL ERROR", ""); foreach (ErrorStackInfo info in infos) { Debug.WriteLine("EVAL ERROR", "info: {0}({1})", info.File, info.Line, info.Caller); } // hides transparent evals and modifies the others accordingly: HideTransparentEvals(infos); // refresh inner most error info: result = infos[0]; result.File = EvalTraceToFileName(infos, source_root, html); }
/// <summary> /// Exception constructor. /// </summary> /// <param name="error">The type of PHP error.</param> /// <param name="message">The error message.</param> /// <param name="info">Information about an error gained from a stack.</param> private PhpException(PhpError error, string message, ErrorStackInfo info) : base(message) { this.info = info; this.error = error; }
/// <summary> /// /// </summary> /// <param name="info"></param> /// <param name="config"></param> /// <returns>Returns caller name with () or null. Formatted for the current output capabilities.</returns> internal static string FormatErrorCallerName(ErrorStackInfo info, LocalConfiguration config) { if (info.Caller == null) return null; if (config.ErrorControl.HtmlMessages && config.ErrorControl.DocRefRoot != null && info.LibraryCaller) { // able to display HTML return String.Format("<a href='{0}/function.{1}{2}'>{3}()</a>", config.ErrorControl.DocRefRoot, info.Caller.Replace('_', '-').ToLower(), config.ErrorControl.DocRefExtension, info.Caller); } else { return info.Caller + "()"; } }
/// <summary> /// Calls user error handler. /// </summary> /// <returns>Whether to report error by default handler (determined by handler's return value).</returns> /// <exception cref="ScriptDiedException">Error handler dies.</exception> private static bool CallUserErrorHandler(ScriptContext context, PhpError error, ErrorStackInfo info, string message) { LocalConfiguration config = context.Config; try { object result = PhpVariable.Dereference(config.ErrorControl.UserHandler.Invoke(new PhpReference[] { new PhpReference((int)error), new PhpReference(message), new PhpReference(info.File), new PhpReference(info.Line), new PhpReference() // global variables list is not supported })); // since PHP5 an error is reported by default error handler if user handler returns false: return result is bool && (bool)result == false; } catch (ScriptDiedException) { // user handler has cancelled the error via script termination: throw; } catch (PhpUserException) { // rethrow user exceptions: throw; } catch (Exception) { } return false; }
/// <summary> /// Creates a new PHP stack frame. /// </summary> /// <param name="context">A script context.</param> /// <param name="frame">The respective CLR frame.</param> /// <param name="kind">A kind of the frame.</param> internal PhpStackFrame(ScriptContext/*!*/ context, StackFrame/*!*/ frame, FrameKinds kind) { Debug.Assert(context != null && frame != null && kind != FrameKinds.Invisible); this.frame = frame; this.kind = kind; MethodBase method = frame.GetMethod(); if (kind == FrameKinds.ClassLibraryFunction) { this.name = ImplementsFunctionAttribute.Reflect(method).Name; SetDebugInfo(frame); } else { Type type = method.DeclaringType; int eval_id = TransientAssembly.InvalidEvalId; if (type != null && context.ApplicationContext.IsTransientRealType(type)) { // gets [PhpEvalId] attribute defined on the type: object[] attrs = type.GetCustomAttributes(typeof(PhpEvalIdAttribute), false); eval_id = ((PhpEvalIdAttribute)attrs[0]).Id; ErrorStackInfo info = new ErrorStackInfo(); PhpStackTrace.FillEvalStackInfo(context, eval_id, ref info, false); this.line = info.Line; this.column = info.Column; this.file = info.File; this.name = info.Caller; } else { SetDebugInfo(frame); } // the caller has already been set by FillEvalStackInfo // if it is not an eval main: if (!(eval_id != TransientAssembly.InvalidEvalId && PhpScript.IsScriptType(type) && method.Name == ScriptModule.MainHelperName)) { int j; PhpScript.ParseMDeclName(method.Name, out this.name, out j); } } }
/// <summary> /// Formats error message. /// </summary> /// <param name="config">A configuration.</param> /// <param name="error">A type of the error.</param> /// <param name="id">Error id or -1.</param> /// <param name="info">A stack information about the error.</param> /// <param name="message">A message.</param> /// <returns>A formatted plain text or HTML message depending on settings in <paramref name="config"/>.</returns> /// <exception cref="ArgumentNullException"><paramren name="config"/> is a <B>null</B> reference.</exception> public static string FormatErrorMessageOutput(LocalConfiguration config, PhpError error, int id, ErrorStackInfo info, string message) { if (config == null) { throw new ArgumentNullException("config"); } string error_str = PhpErrorText(error, id); // the error type (Warning, Error, ...) bool show_place = info.Line > 0 && info.Column > 0; // we are able to report error position string caller = FormatErrorCallerName(info, config); // current function name "foo()" or null // change the message or caller, based on the error type FormatErrorMessageText(error, ref message, ref caller); // error message string ErrorFormatString = config.ErrorControl.HtmlMessages ? (show_place ? CoreResources.error_message_html_debug : CoreResources.error_message_html) : (show_place ? CoreResources.error_message_plain_debug : CoreResources.error_message_plain); if (show_place) { return(string.Format(ErrorFormatString, error_str, caller, message, info.File, info.Line, info.Column)); } else { return(string.Format(ErrorFormatString, error_str, caller, message)); } }
/// <summary> /// Reports a PHP error. /// </summary> /// <param name="error">The error type</param> /// <param name="message">The error message.</param> public static void Throw(PhpError error, string message) { if (ThrowCallbackOverride != null) { ThrowCallbackOverride(error, message); return; } ErrorStackInfo info = new ErrorStackInfo(); bool info_loaded = false; // gets the current script context and config: ScriptContext context = ScriptContext.CurrentContext; LocalConfiguration config = context.Config; // determines whether the error will be reported and whether it is handleable: bool is_error_reported = ((PhpErrorSet)error & config.ErrorControl.ReportErrors) != 0 && !context.ErrorReportingDisabled; bool is_error_handleable = ((PhpErrorSet)error & PhpErrorSet.Handleable & (PhpErrorSet)config.ErrorControl.UserHandlerErrors) != 0; bool is_error_fatal = ((PhpErrorSet)error & PhpErrorSet.Fatal) != 0; bool do_report = true; // remember last error info context.LastErrorType = error; context.LastErrorMessage = message; context.LastErrorFile = null; // only if we are getting ErrorStackInfo, see PhpStackTrace.TraceErrorFrame context.LastErrorLine = 0; // only if we are getting ErrorStackInfo, see PhpStackTrace.TraceErrorFrame // calls a user defined handler if available: if (is_error_handleable && config.ErrorControl.UserHandler != null) { // loads stack info: Func <ErrorStackInfo> func = () => { if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context, true); info_loaded = true; } return(info); }; do_report = CallUserErrorHandler(context, error, func, message); } // reports error to output and logs: if (do_report && is_error_reported && (config.ErrorControl.DisplayErrors || config.ErrorControl.EnableLogging)) // check if the error will be displayed to avoid stack trace loading { // loads stack info: if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context, false); info_loaded = true; } ReportError(config, context.Output, error, -1, info, message); } // Throws an exception if the error is fatal and throwing is enabled. // PhpError.UserError is also fatal, but can be cancelled by user handler => handler call must precede this line. // Error displaying must also precede this line because the error should be displayed before an exception is thrown. if (is_error_fatal && context.ThrowExceptionOnError) { // loads stack info: if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context, false); info_loaded = true; } throw new PhpException(error, message, info); } }
/// <summary> /// Reports a PHP error. /// </summary> /// <param name="error">The error type</param> /// <param name="message">The error message.</param> public static void Throw(PhpError error, string message) { if (ThrowCallbackOverride != null) { ThrowCallbackOverride(error, message); return; } ErrorStackInfo info = new ErrorStackInfo(); bool info_loaded = false; // gets the current script context and config: ScriptContext context = ScriptContext.CurrentContext; LocalConfiguration config = context.Config; // determines whether the error will be reported and whether it is handleable: bool is_error_reported = ((PhpErrorSet)error & config.ErrorControl.ReportErrors) != 0 && !context.ErrorReportingDisabled; bool is_error_handleable = ((PhpErrorSet)error & PhpErrorSet.Handleable & (PhpErrorSet)config.ErrorControl.UserHandlerErrors) != 0; bool is_error_fatal = ((PhpErrorSet)error & PhpErrorSet.Fatal) != 0; bool do_report = true; // remember last error info context.LastErrorType = error; context.LastErrorMessage = message; context.LastErrorFile = null; // only if we are getting ErrorStackInfo, see PhpStackTrace.TraceErrorFrame context.LastErrorLine = 0; // only if we are getting ErrorStackInfo, see PhpStackTrace.TraceErrorFrame // calls a user defined handler if available: if (is_error_handleable && config.ErrorControl.UserHandler != null) { // loads stack info: if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context); info_loaded = true; } do_report = CallUserErrorHandler(context, error, info, message); } // reports error to output and logs: if (do_report && is_error_reported && (config.ErrorControl.DisplayErrors || config.ErrorControl.EnableLogging)) // check if the error will be displayed to avoid stack trace loading { // loads stack info: if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context); info_loaded = true; } ReportError(config, context.Output, error, -1, info, message); } // Throws an exception if the error is fatal and throwing is enabled. // PhpError.UserError is also fatal, but can be cancelled by user handler => handler call must precede this line. // Error displaying must also precede this line because the error should be displayed before an exception is thrown. if (is_error_fatal && context.ThrowExceptionOnError) { // loads stack info: if (!info_loaded) { info = PhpStackTrace.TraceErrorFrame(context); info_loaded = true; } throw new PhpException(error, message, info); } }
/// <summary> /// Reports an error to log file, event log and to output (as configured). /// </summary> private static void ReportError(LocalConfiguration config, TextWriter output, PhpError error, int id, ErrorStackInfo info, string message) { string formatted_message = FormatErrorMessageOutput(config, error, id, info, message); // logs error if logging is enabled: if (config.ErrorControl.EnableLogging) { #if SILVERLIGHT throw new NotSupportedException("Logging is not supported on Silverlight. Set EnableLogging to false."); #else // adds a message to log file: if (config.ErrorControl.LogFile != null) try { // <error>: <caller>(): <message> in <file> on line <line> string caller = (info.Caller != null) ? (info.Caller + "(): ") : null; string place = (info.Line > 0 && info.Column > 0) ? CoreResources.GetString("error_place", info.File, info.Line, info.Column) : null; Logger.AppendLine(config.ErrorControl.LogFile, string.Concat(error, ": ", caller, message, place)); } catch (Exception) { } // adds a message to event log: if (config.ErrorControl.SysLog) try { Logger.AddToEventLog(message); } catch (Exception) { } #endif } // displays an error message if desired: if (config.ErrorControl.DisplayErrors) { output.Write(config.ErrorControl.ErrorPrependString); output.Write(formatted_message); output.Write(config.ErrorControl.ErrorAppendString); } }
/// <summary> /// Traces up the stack frame containing the method call that has caused an error. /// </summary> /// <returns>Found stack info.</returns> /// <remarks> /// Starts with a frame of a calling method and ends with the first frame belonging to user routine. /// If there was <see cref="ImplementsFunctionAttribute"/> found during the walk the last one's value /// is considered as the caller. /// If there was not such attribute found (error occured in an operator, directly in the code etc.) /// the last inspected method's debug info is returned. /// If the trace ends up with a function or method inside transient assembly an eval hierarchy is inspected /// and added to the resulting source position information. /// </remarks> internal static ErrorStackInfo TraceErrorFrame(ScriptContext/*!*/ context) { Debug.Assert(context != null); ErrorStackInfo result = new ErrorStackInfo(); int cl_function_idx = -1; string cl_function_name = null; StackFrame frame; int eval_id = TransientAssembly.InvalidEvalId; // stack trace without debug info is constructed: #if !SILVERLIGHT StackTrace trace = new StackTrace(1, false); // note: method stack frame contains a debug info about the call to the callee // hence if we find a method that reported the error we should look the next frame // to obtain a debug info int i = 0; for (; ; ) { // gets frame: frame = trace.GetFrame(i++); // error has been thrown directly by Core without intermediary user code (all frames are invisible): if (frame == null) { // cl_function_idx can be non-minus-one here because a callback can be called directly from Core // (e.g. output buffer filter targeting class library function): if (cl_function_idx != -1) { result.Caller = cl_function_name; result.LibraryCaller = true; } return result; } FrameKinds frame_kind = GetFrameKind(frame); if (frame_kind == FrameKinds.Visible) { MethodBase method = frame.GetMethod(); int eid = TransientModule.GetEvalId(context.ApplicationContext, method); if (eval_id == TransientAssembly.InvalidEvalId) eval_id = eid; if (eid == TransientAssembly.InvalidEvalId) break; } else if (frame_kind == FrameKinds.ClassLibraryFunction) { MethodBase method = frame.GetMethod(); cl_function_idx = i; cl_function_name = ImplementsFunctionAttribute.Reflect(method).Name; } } // skips i frames (the very first frame has been skipped in the previous // trace construction and we want to skip i-1 frames from that trace => i frames totally): frame = new StackFrame(1 + i - 1, true); // extracts a source info (file & position): if (eval_id != TransientAssembly.InvalidEvalId) { FillEvalStackInfo(context, eval_id, ref result, false); } else { result.Line = frame.GetFileLineNumber(); result.Column = frame.GetFileColumnNumber(); result.File = frame.GetFileName(); } // determines a caller (either a library function or a user function/method): if (cl_function_idx >= 0) { result.Caller = cl_function_name; result.LibraryCaller = true; } //else //{ // MethodBase method = frame.GetMethod(); // Type type = method.DeclaringType; // // the caller has already been set by FillEvalStackInfo // // if we are in eval and the function is Main helper of the script type: // if (eval_id == TransientAssembly.InvalidEvalId) // { // result.LibraryCaller = false; // if (type != null) // { // result.Caller = String.Concat(DTypeDesc.MakeFullName(type), "::", DRoutineDesc.MakeFullName(method)); // } // else // { // result.Caller = DRoutineDesc.MakeFullName(method); // } // } //} #endif // add missing info about file and line context.LastErrorLine = result.Line; context.LastErrorFile = result.File; // return result; }
/// <summary> /// Reports error thrown by compiler. /// </summary> internal static void ThrowByWebCompiler(PhpError error, int id, string sourceFile, int line, int column, string message) { ErrorStackInfo info = new ErrorStackInfo(sourceFile, null, line, column, false); // gets the current script context and config: LocalConfiguration config = Configuration.Local; #if !SILVERLIGHT ReportError(config, HttpContext.Current.Response.Output, error, id, info, message); #else ReportError(config, new StreamWriter(ScriptContext.CurrentContext.OutputStream), error, id, info, message); #endif if (((PhpErrorSet)error & PhpErrorSet.Fatal) != 0) throw new PhpException(error, message, info); }
/// <summary> /// Fills an instance of <see cref="ErrorStackInfo"/> with information gathered from eval transient debug info. /// </summary> /// <param name="context">Script context.</param> /// <param name="evalId">An id of the inner-most eval where an error occured.</param> /// <param name="result">The resulting error stack info.</param> /// <param name="html">Whether the message is used in HTML.</param> internal static void FillEvalStackInfo(ScriptContext/*!*/ context, int evalId, ref ErrorStackInfo result, bool html) { Debug.Assert(context != null); FullPath source_root = Configuration.Application.Compiler.SourceRoot; // stack info about the error position (with respect to inner most eval): result.Line = context.EvalLine; result.Column = context.EvalColumn; result.Caller = "<error>"; result.File = null; List<ErrorStackInfo> infos = new List<ErrorStackInfo>(); infos.Add(result); // fills "infos" with full eval error trace: context.ApplicationContext.TransientAssemblyBuilder.TransientAssembly.GetEvalFullTrace(evalId, infos); Debug.WriteLine("EVAL ERROR", ""); foreach (ErrorStackInfo info in infos) { Debug.WriteLine("EVAL ERROR", "info: {0}({1})", info.File, info.Line, info.Caller); } // hides transparent evals and modifies the others accordingly: HideTransparentEvals(infos); // refresh inner most error info: result = infos[0]; result.File = EvalTraceToFileName(infos, source_root, html); }
/// <summary> /// Formats error message. /// </summary> /// <param name="config">A configuration.</param> /// <param name="error">A type of the error.</param> /// <param name="id">Error id or -1.</param> /// <param name="info">A stack information about the error.</param> /// <param name="message">A message.</param> /// <returns>A formatted plain text or HTML message depending on settings in <paramref name="config"/>.</returns> /// <exception cref="ArgumentNullException"><paramren name="config"/> is a <B>null</B> reference.</exception> public static string FormatErrorMessageOutput(LocalConfiguration config, PhpError error, int id, ErrorStackInfo info, string message) { if (config == null) throw new ArgumentNullException("config"); string error_str = PhpErrorText(error, id); // the error type (Warning, Error, ...) bool show_place = info.Line > 0 && info.Column > 0; // we are able to report error position string caller = FormatErrorCallerName(info, config); // current function name "foo()" or null // change the message or caller, based on the error type FormatErrorMessageText(error, ref message, ref caller); // error message string ErrorFormatStringId = config.ErrorControl.HtmlMessages ? (show_place ? "error_message_html_debug" : "error_message_html") : (show_place ? "error_message_plain_debug" : "error_message_plain"); if (show_place) return CoreResources.GetString(ErrorFormatStringId, error_str, caller, message, info.File, info.Line, info.Column); else return CoreResources.GetString(ErrorFormatStringId, error_str, caller, message); }
/// <summary> /// Traces up the stack frame containing the method call that has caused an error. /// </summary> /// <returns>Found stack info.</returns> /// <remarks> /// Starts with a frame of a calling method and ends with the first frame belonging to user routine. /// If there was <see cref="ImplementsFunctionAttribute"/> found during the walk the last one's value /// is considered as the caller. /// If there was not such attribute found (error occured in an operator, directly in the code etc.) /// the last inspected method's debug info is returned. /// If the trace ends up with a function or method inside transient assembly an eval hierarchy is inspected /// and added to the resulting source position information. /// </remarks> internal static ErrorStackInfo TraceErrorFrame(ScriptContext /*!*/ context, bool lazy) { Debug.Assert(context != null); ErrorStackInfo result = new ErrorStackInfo(); int cl_function_idx = -1; string cl_function_name = null; StackFrame frame; int eval_id = TransientAssembly.InvalidEvalId; // stack trace without debug info is constructed: #if !SILVERLIGHT StackTrace trace = new StackTrace(1, false); // note: method stack frame contains a debug info about the call to the callee // hence if we find a method that reported the error we should look the next frame // to obtain a debug info int i = 0; for (; ;) { // gets frame: frame = trace.GetFrame(i++); // error has been thrown directly by Core without intermediary user code (all frames are invisible): if (frame == null) { // cl_function_idx can be non-minus-one here because a callback can be called directly from Core // (e.g. output buffer filter targeting class library function): if (cl_function_idx != -1) { result.Caller = cl_function_name; result.LibraryCaller = true; } return(result); } MethodBase method = frame.GetMethod(); if (lazy) { if (method.Name == "Throw" && method.DeclaringType == typeof(PhpException)) { lazy = false; } continue; } FrameKinds frame_kind = GetFrameKind(frame); if (frame_kind == FrameKinds.Visible || frame_kind == FrameKinds.Main) { int eid = TransientModule.GetEvalId(context.ApplicationContext, method); if (eval_id == TransientAssembly.InvalidEvalId) { eval_id = eid; } if (eid == TransientAssembly.InvalidEvalId) { break; } } else if (frame_kind == FrameKinds.ClassLibraryFunction) { cl_function_idx = i; cl_function_name = ImplementsFunctionAttribute.Reflect(method).Name; } } // skips i frames (the very first frame has been skipped in the previous // trace construction and we want to skip i-1 frames from that trace => i frames totally): frame = new StackFrame(1 + i - 1, true); // extracts a source info (file & position): if (eval_id != TransientAssembly.InvalidEvalId) { FillEvalStackInfo(context, eval_id, ref result, false); } else { result.Line = frame.GetFileLineNumber(); result.Column = frame.GetFileColumnNumber(); result.File = frame.GetFileName(); } // determines a caller (either a library function or a user function/method): if (cl_function_idx >= 0) { result.Caller = cl_function_name; result.LibraryCaller = true; } //else //{ // MethodBase method = frame.GetMethod(); // Type type = method.DeclaringType; // // the caller has already been set by FillEvalStackInfo // // if we are in eval and the function is Main helper of the script type: // if (eval_id == TransientAssembly.InvalidEvalId) // { // result.LibraryCaller = false; // if (type != null) // { // result.Caller = String.Concat(DTypeDesc.MakeFullName(type), "::", DRoutineDesc.MakeFullName(method)); // } // else // { // result.Caller = DRoutineDesc.MakeFullName(method); // } // } //} #endif // add missing info about file and line context.LastErrorLine = result.Line; context.LastErrorFile = result.File; // return(result); }
/// <summary> /// Calls user error handler. /// </summary> /// <returns>Whether to report error by default handler (determined by handler's return value).</returns> /// <exception cref="ScriptDiedException">Error handler dies.</exception> private static bool CallUserErrorHandler(ScriptContext context, PhpError error, ErrorStackInfo info, string message) { LocalConfiguration config = context.Config; try { object result = PhpVariable.Dereference(config.ErrorControl.UserHandler.Invoke(new PhpReference[] { new PhpReference((int)error), new PhpReference(message), new PhpReference(info.File), new PhpReference(info.Line), new PhpReference() // global variables list is not supported })); // since PHP5 an error is reported by default error handler if user handler returns false: return(result is bool && (bool)result == false); } catch (ScriptDiedException) { // user handler has cancelled the error via script termination: throw; } catch (PhpUserException) { // rethrow user exceptions: throw; } catch (Exception) { } return(false); }