Skip to content

Commit

Permalink
[TIMOB-10707] iOS: Expose runtime errors to module developers
Browse files Browse the repository at this point in the history
  • Loading branch information
Max Stepanov committed Sep 14, 2012
1 parent 03b109c commit a72fc22
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 105 deletions.
53 changes: 24 additions & 29 deletions iphone/Classes/KrollBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
#import "KrollContext.h"
#import "TiDebugger.h"
#import "TiConsole.h"
#import "TiExceptionHandler.h"

#ifdef KROLL_COVERAGE
# include "KrollCoverage.h"
#endif

extern BOOL const TI_APPLICATION_ANALYTICS;
extern NSString * const TI_APPLICATION_DEPLOYTYPE;

NSString * TitaniumModuleRequireFormat = @"(function(exports){"
"var __OXP=exports;var module={'exports':exports};%@;\n"
Expand Down Expand Up @@ -367,12 +369,6 @@ - (id)evalJSAndWait:(NSString*)code
return [context evalJSAndWait:code];
}

- (void)scriptError:(NSString*)message
{
evaluationError = YES;
[[TiApp app] showModalError:message];
}

-(BOOL)evaluationError
{
return evaluationError;
Expand Down Expand Up @@ -417,15 +413,15 @@ - (void)evalFileOnThread:(NSString*)path context:(KrollContext*)context_
{
NSLog(@"[ERROR] Error loading path: %@, %@",path,error);

evaluationError = YES;
TiScriptError *scriptError = nil;
// check for file not found a give a friendlier message
if ([error code]==260 && [error domain]==NSCocoaErrorDomain)
{
[self scriptError:[NSString stringWithFormat:@"Could not find the file %@",[path lastPathComponent]]];
}
else
{
[self scriptError:[NSString stringWithFormat:@"Error loading script %@. %@",[path lastPathComponent],[error description]]];
if ([error code]==260 && [error domain]==NSCocoaErrorDomain) {
scriptError = [[TiScriptError alloc] initWithMessage:[NSString stringWithFormat:@"Could not find the file %@",[path lastPathComponent]] sourceURL:nil lineNo:0];
} else {
scriptError = [[TiScriptError alloc] initWithMessage:[NSString stringWithFormat:@"Error loading script %@. %@",[path lastPathComponent],[error description]] sourceURL:nil lineNo:0];
}
[[TiExceptionHandler defaultExceptionHandler] reportScriptError:scriptError];
return;
}

Expand All @@ -435,17 +431,12 @@ - (void)evalFileOnThread:(NSString*)path context:(KrollContext*)context_
TiStringRef jsURL = TiStringCreateWithUTF8CString(urlCString);

// validate script
// TODO: we do not need to do this in production app
if (!TiCheckScriptSyntax(jsContext,jsCode,jsURL,1,&exception))
{
id excm = [KrollObject toID:context value:exception];
DebugLog(@"[ERROR] Syntax Error = %@",[TiUtils exceptionMessage:excm]);
[self scriptError:[TiUtils exceptionMessage:excm]];
if (![TI_APPLICATION_DEPLOYTYPE isEqualToString:@"production"]) {
TiCheckScriptSyntax(jsContext,jsCode,jsURL,1,&exception);
}

// only continue if we don't have any exceptions from above
if (exception == NULL)
{
if (exception == NULL) {
if ([[self host] debugMode]) {
TiDebuggerBeginScript(context_,urlCString);
}
Expand All @@ -455,17 +446,21 @@ - (void)evalFileOnThread:(NSString*)path context:(KrollContext*)context_
if ([[self host] debugMode]) {
TiDebuggerEndScript(context_);
}

if (exception!=NULL)
{
id excm = [KrollObject toID:context value:exception];
DebugLog(@"[ERROR] Script Error = %@.",[TiUtils exceptionMessage:excm]);
[self scriptError:[TiUtils exceptionMessage:excm]];
}
else {
if (exception == NULL) {
evaluationError = NO;
}
}
if (exception != NULL) {
id excm = [KrollObject toID:context value:exception];
TiScriptError *scriptError = nil;
if ([excm isKindOfClass:[NSDictionary class]]) {
scriptError = [[TiScriptError alloc] initWithDictionary:excm];
} else {
scriptError = [[TiScriptError alloc] initWithMessage:[excm description] sourceURL:nil lineNo:0];
}
evaluationError = YES;
[[TiExceptionHandler defaultExceptionHandler] reportScriptError:scriptError];
}

TiStringRelease(jsCode);
TiStringRelease(jsURL);
Expand Down
58 changes: 3 additions & 55 deletions iphone/Classes/TiApp.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Please see the LICENSE included with this distribution for details.
*/
#include <stdio.h>
#include <execinfo.h>

#import "TiApp.h"
#import "Webcolor.h"
Expand All @@ -18,6 +17,7 @@
#import <AVFoundation/AVFoundation.h>
#import "ApplicationDefaults.h"
#import <libkern/OSAtomic.h>
#import "TiExceptionHandler.h"

#ifdef KROLL_COVERAGE
# import "KrollCoverage.h"
Expand All @@ -38,58 +38,6 @@
#define SHUTDOWN_TIMEOUT_IN_SEC 3
#define TIV @"TiVerify"

//
// thanks to: http://www.restoroot.com/Blog/2008/10/18/crash-reporter-for-iphone-applications/
//
void MyUncaughtExceptionHandler(NSException *exception)
{
static BOOL insideException = NO;

// prevent recursive exceptions
if (insideException==YES)
{
exit(1);
return;
}

insideException = YES;
NSArray *callStackArray = [exception callStackReturnAddresses];
int frameCount = [callStackArray count];
void *backtraceFrames[frameCount];

for (int i=0; i<frameCount; i++)
{
backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue];
}

char **frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount);

NSMutableString *stack = [[NSMutableString alloc] init];

[stack appendString:@"[ERROR] The application has crashed with an unhandled exception. Stack trace:\n\n"];

if(frameStrings != NULL)
{
for(int x = 0; x < frameCount; x++)
{
if(frameStrings[x] == NULL)
{
break;
}
[stack appendFormat:@"%s\n",frameStrings[x]];
}
free(frameStrings);
}
[stack appendString:@"\n"];

NSLog(@"%@",stack);

[stack release];

//TODO - attempt to report the exception
insideException=NO;
}

BOOL applicationInMemoryPanic = NO;

TI_INLINE void waitForMemoryPanicCleared(); //WARNING: This must never be run on main thread, or else there is a risk of deadlock!
Expand Down Expand Up @@ -279,7 +227,7 @@ - (void)booted:(id)bridge

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
[TiExceptionHandler defaultExceptionHandler];
[self initController];
[self loadUserDefaults];
[self boot];
Expand All @@ -305,7 +253,7 @@ - (void)generateNotification:(NSDictionary*)dict
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions_
{
started = [NSDate timeIntervalSinceReferenceDate];
NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
[TiExceptionHandler defaultExceptionHandler];

// nibless window
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
Expand Down
82 changes: 82 additions & 0 deletions iphone/Classes/TiExceptionHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2012 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/

#import <Foundation/Foundation.h>

#pragma mark - TiScriptError

/**
* Script Error class
*/
@interface TiScriptError : NSObject

/**
* Returns source URL where error happenned.
*/
@property (nonatomic, readonly) NSString *sourceURL;

/**
* Returns line number where error happenned.
*/
@property (nonatomic, readonly) NSInteger lineNo;

/**
* Returns error related message
*/
@property (nonatomic, readonly) NSString *message;

- (id)initWithMessage:(NSString *)message sourceURL:(NSString *)sourceURL lineNo:(NSInteger)lineNo;
- (id)initWithDictionary:(NSDictionary *)dictionary;

@end

#pragma mark - TiExceptionHandlerDelegate

/**
* Exception handler delegate protocol.
*/
@protocol TiExceptionHandlerDelegate <NSObject>

/**
* Called when Objective-C exception is thrown.
* @param exception An original NSException object
* @param stackTrace An array of strings containing stack trace description
*/
- (void)handleUncaughtException:(NSException *)exception withStackTrace:(NSArray *)stackTrace;
- (void)handleScriptError:(TiScriptError *)error;

@end

#pragma mark - TiExceptionHandler

/**
* The Exception Handler class. Singleton instance accessed via <defaultExceptionHandler>
*/
@interface TiExceptionHandler : NSObject < TiExceptionHandlerDelegate >

/**
* Delegate for error/exception handling
* @see TiExceptionHandlerDelegate
*/
@property (nonatomic, assign) id<TiExceptionHandlerDelegate> delegate;

/**
* Presents provided script error to user. Default behavior in development mode.
* @param error The script error object/
*/
- (void)showScriptError:(TiScriptError *)error;

/**
* Returns singleton instance of TiExceptionHandler.
* @return singleton instance
*/
+ (TiExceptionHandler *)defaultExceptionHandler;

- (void)reportScriptError:(TiScriptError *)error;

@end

1 comment on commit a72fc22

@farfromrefug
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since that commit i get [TiUtils exceptionMessage:]: unrecognized selector sent to class ...

Please sign in to comment.