From 3a7a285b94cac7dd359e2b71e6dfbe6097224733 Mon Sep 17 00:00:00 2001 From: Jason Zhekov Date: Wed, 3 Feb 2016 15:28:52 +0200 Subject: [PATCH] TNS Objective-C exception handler --- build/project-template/internal/main.m | 11 +++- build/scripts/build-runtime.sh | 3 +- build/scripts/build.sh | 1 + cmake/CreateNativeScriptApp.cmake | 3 +- cmake/main.m | 3 + examples/BlankApp/main.m | 3 + src/NativeScript/CMakeLists.txt | 1 + src/NativeScript/JSErrors.mm | 2 +- src/NativeScript/NativeScript.h | 1 + src/NativeScript/ObjC/TNSExceptionHandler.h | 63 +++++++++++++++++++++ src/NativeScript/TNSRuntime+Diagnostics.h | 3 +- src/NativeScript/TNSRuntime+Diagnostics.mm | 49 +++++++--------- src/NativeScript/TNSRuntime.h | 4 +- src/NativeScript/TNSRuntime.mm | 6 ++ src/debugging/TNSDebugging.h | 4 +- tests/TestFixtures/exported-symbols.txt | 1 + 16 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 src/NativeScript/ObjC/TNSExceptionHandler.h diff --git a/build/project-template/internal/main.m b/build/project-template/internal/main.m index 2231392c8..43999e6fe 100644 --- a/build/project-template/internal/main.m +++ b/build/project-template/internal/main.m @@ -5,6 +5,7 @@ #include #include #include +#include #if DEBUG #include @@ -20,9 +21,11 @@ int main(int argc, char *argv[]) { #if DEBUG NSString *libraryPath = [NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES) firstObject]; - NSString *liveSyncPath = - [NSString pathWithComponents: - @[ libraryPath, @"Application Support", @"LiveSync" ]]; + NSString *liveSyncPath = [NSString pathWithComponents:@[ + libraryPath, + @"Application Support", + @"LiveSync" + ]]; NSString *appFolderPath = [NSString pathWithComponents:@[ liveSyncPath, @"app" ]]; @@ -35,6 +38,8 @@ int main(int argc, char *argv[]) { #endif [TNSRuntime initializeMetadata:&startOfMetadataSection]; + TNSInstallExceptionHandler(); + runtime = [[TNSRuntime alloc] initWithApplicationPath:applicationPath]; [runtime scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; diff --git a/build/scripts/build-runtime.sh b/build/scripts/build-runtime.sh index a71a7516b..fd808f4b9 100755 --- a/build/scripts/build-runtime.sh +++ b/build/scripts/build-runtime.sh @@ -10,13 +10,14 @@ PACKAGE_DIR="$DIST_DIR/package" FRAMEWORK_DIR="$PACKAGE_DIR/framework" INTERNAL_DIR="$FRAMEWORK_DIR/internal" -. "$WORKSPACE/build/scripts/build.sh" +. "$WORKSPACE/build/scripts/build.sh" mkdir -p "$INTERNAL_DIR/NativeScript/Frameworks" cp -R "$DIST_DIR/NativeScript/" "$INTERNAL_DIR/NativeScript" cp -R "$DIST_DIR/NativeScript.framework" "$INTERNAL_DIR/NativeScript/Frameworks" cp -R "$WORKSPACE/src/debugging/TNSDebugging.h" "$INTERNAL_DIR" +cp -R "$WORKSPACE/src/NativeScript/ObjC/TNSExceptionHandler.h" "$INTERNAL_DIR" cp -R "$DIST_DIR/metadataGenerator" "$INTERNAL_DIR/metadata-generator" cp -R "$BUILD_DIR/project-template/" "$FRAMEWORK_DIR" cp -R "$BUILD_DIR/npm/runtime_package.json" "$PACKAGE_DIR/package.json" diff --git a/build/scripts/build.sh b/build/scripts/build.sh index 8274513ae..fcf99b610 100755 --- a/build/scripts/build.sh +++ b/build/scripts/build.sh @@ -57,6 +57,7 @@ NATIVESCRIPT_DIR="$WORKSPACE/src/NativeScript/" cp \ "$NATIVESCRIPT_DIR/NativeScript.h" \ "$NATIVESCRIPT_DIR/TNSRuntime.h" \ + "$NATIVESCRIPT_DIR/TNSRuntime+Diagnostics.h" \ "$NATIVESCRIPT_DIR/TNSRuntime+Inspector.h" \ "$WORKSPACE/dist/NativeScript/include" diff --git a/cmake/CreateNativeScriptApp.cmake b/cmake/CreateNativeScriptApp.cmake index 313817912..1667a3ab6 100644 --- a/cmake/CreateNativeScriptApp.cmake +++ b/cmake/CreateNativeScriptApp.cmake @@ -1,10 +1,11 @@ function(CreateNativeScriptApp _target _main _plist _resources) - include_directories(${RUNTIME_DIR} ${NATIVESCRIPT_DEBUGGING_DIR}) + include_directories("${RUNTIME_DIR}/**" ${NATIVESCRIPT_DEBUGGING_DIR}) link_directories(${WEBKIT_LINK_DIRECTORIES} "${LIBFFI_LIB_DIR}") add_executable(${_target} ${_main} ${_resources}) target_link_libraries(${_target} + "-ObjC" "-framework CoreGraphics" "-framework UIKit" "-framework MobileCoreServices" diff --git a/cmake/main.m b/cmake/main.m index 71b01d687..bcd60ba05 100644 --- a/cmake/main.m +++ b/cmake/main.m @@ -1,4 +1,5 @@ #import +#import #ifndef NDEBUG #include @@ -16,6 +17,8 @@ int main(int argc, char *argv[]) { forMode:NSRunLoopCommonModes]; TNSRuntimeInspector.logsToSystemConsole = YES; + TNSInstallExceptionHandler(); + #ifndef NDEBUG TNSEnableRemoteInspector(argc, argv); #endif diff --git a/examples/BlankApp/main.m b/examples/BlankApp/main.m index 6dbd5808f..bfb49a27c 100644 --- a/examples/BlankApp/main.m +++ b/examples/BlankApp/main.m @@ -1,6 +1,7 @@ #import #import #import +#import static NSString *toString(JSContextRef context, JSValueRef value) { JSStringRef errorMessageRef = JSValueToStringCopy(context, value, NULL); @@ -36,6 +37,8 @@ int main(int argc, char *argv[]) { return 1; } + TNSInstallExceptionHandler(); + JSValueRef errorRef = NULL; JSStringRef scriptRef = JSStringCreateWithUTF8CString(script.UTF8String); diff --git a/src/NativeScript/CMakeLists.txt b/src/NativeScript/CMakeLists.txt index 93d00bc09..86ad7de79 100644 --- a/src/NativeScript/CMakeLists.txt +++ b/src/NativeScript/CMakeLists.txt @@ -171,6 +171,7 @@ set(SOURCE_FILES set(NativeScript_PUBLIC_HEADERS NativeScript.h + TNSRuntime+Diagnostics.h TNSRuntime+Inspector.h TNSRuntime.h ) diff --git a/src/NativeScript/JSErrors.mm b/src/NativeScript/JSErrors.mm index 9f4215f60..9c3745a2e 100644 --- a/src/NativeScript/JSErrors.mm +++ b/src/NativeScript/JSErrors.mm @@ -31,7 +31,7 @@ void TNSSetUncaughtErrorHandler(TNSUncaughtErrorHandler handler) { using namespace JSC; static void handleJsUncaughtErrorCallback(ExecState* execState, Exception* exception) { - JSValue callback = execState->lexicalGlobalObject()->get(execState, Identifier::fromString(execState, "__onUncaughtError")); + JSValue callback = execState->lexicalGlobalObject()->get(execState, Identifier::fromString(execState, "__onUncaughtError")); // Keep in sync with TNSExceptionHandler.h CallData callData; CallType callType = getCallData(callback, callData); diff --git a/src/NativeScript/NativeScript.h b/src/NativeScript/NativeScript.h index 45bb13734..6ab29d85e 100644 --- a/src/NativeScript/NativeScript.h +++ b/src/NativeScript/NativeScript.h @@ -7,4 +7,5 @@ // #import "TNSRuntime.h" +#import "TNSRuntime+Diagnostics.h" #import "TNSRuntime+Inspector.h" diff --git a/src/NativeScript/ObjC/TNSExceptionHandler.h b/src/NativeScript/ObjC/TNSExceptionHandler.h new file mode 100644 index 000000000..9f72110e2 --- /dev/null +++ b/src/NativeScript/ObjC/TNSExceptionHandler.h @@ -0,0 +1,63 @@ +// +// TNSExceptionHandler.h +// NativeScript +// +// Created by Jason Zhekov on 2/1/16. +// Copyright © 2016 Telerik. All rights reserved. +// + +#ifndef TNSExceptionHandler_h +#define TNSExceptionHandler_h + +#import +#import + +TNSRuntime* runtime; + +static NSUncaughtExceptionHandler* oldExceptionHandler = NULL; + +static void TNSObjectiveCUncaughtExceptionHandler(NSException* currentException) { + JSGlobalContextRef context = runtime.globalContext; + JSObjectRef globalObject = JSContextGetGlobalObject(context); + + JSStringRef uncaughtPropertyName = JSStringCreateWithUTF8CString("__onUncaughtError"); // Keep in sync with JSErrors.mm + JSValueRef uncaughtCallback = JSObjectGetProperty(context, globalObject, uncaughtPropertyName, NULL); + JSStringRelease(uncaughtPropertyName); + + if (!JSValueIsUndefined(context, uncaughtCallback)) { + JSStringRef reason = JSStringCreateWithUTF8CString(currentException.reason.UTF8String); + JSObjectRef error = JSObjectMakeError( + context, 1, (JSValueRef[]){ JSValueMakeString(context, reason) }, NULL); + JSStringRelease(reason); + + JSValueRef wrappedException = [runtime convertObject:currentException]; + JSStringRef nativeExceptionPropertyName = JSStringCreateWithUTF8CString("nativeException"); + JSObjectSetProperty(context, error, nativeExceptionPropertyName, + wrappedException, kJSPropertyAttributeNone, NULL); + JSStringRelease(nativeExceptionPropertyName); + + JSValueRef callError = NULL; + JSObjectCallAsFunction(context, (JSObjectRef)uncaughtCallback, NULL, 1, + (JSValueRef[]){ error }, &callError); + if (callError) { + JSStringRef callErrorMessage = JSValueToStringCopy(context, callError, NULL); + NSLog(@"Error executing uncaught error handler: %@", + CFBridgingRelease(JSStringCopyCFString(CFAllocatorGetDefault(), + callErrorMessage))); + JSStringRelease(callErrorMessage); + } + } + + NSLog(@"*** JavaScript call stack:\n(\n%@\n)", [runtime getCurrentStack]); + + if (oldExceptionHandler) { + oldExceptionHandler(currentException); + } +} + +static void TNSInstallExceptionHandler() { + oldExceptionHandler = NSGetUncaughtExceptionHandler(); + NSSetUncaughtExceptionHandler(TNSObjectiveCUncaughtExceptionHandler); +} + +#endif /* TNSExceptionHandler_h */ diff --git a/src/NativeScript/TNSRuntime+Diagnostics.h b/src/NativeScript/TNSRuntime+Diagnostics.h index e5db35d98..2a126506e 100644 --- a/src/NativeScript/TNSRuntime+Diagnostics.h +++ b/src/NativeScript/TNSRuntime+Diagnostics.h @@ -10,7 +10,6 @@ @interface TNSRuntime (Diagnostics) -+ (void)_printCurrentStack; -+ (NSString*)_getCurrentStack; +- (NSString*)getCurrentStack; @end diff --git a/src/NativeScript/TNSRuntime+Diagnostics.mm b/src/NativeScript/TNSRuntime+Diagnostics.mm index d3ee0fead..935336299 100644 --- a/src/NativeScript/TNSRuntime+Diagnostics.mm +++ b/src/NativeScript/TNSRuntime+Diagnostics.mm @@ -7,44 +7,37 @@ // #import "TNSRuntime+Diagnostics.h" +#include +#include +#include +#include +#include +#include #import "TNSRuntime+Private.h" -#import - using namespace JSC; using namespace NativeScript; @implementation TNSRuntime (Diagnostics) -struct StackTraceFunctor { -public: - StackTraceFunctor(WTF::StringBuilder& trace) - : _trace(trace) - , line(0) { - } - - StackVisitor::Status operator()(StackVisitor& visitor) { - if (line++) { - _trace.append("\n"); - } - this->_trace.append(visitor->toString().utf8().data()); - return StackVisitor::Continue; - } - -private: - WTF::StringBuilder& _trace; - int line; -}; - + (void)_printCurrentStack { - NSLog(@"--> JavaScript Stack trace:\n%@", [self _getCurrentStack]); + NSLog(@"%s is deprecated - use [runtime getCurrentStack] instead.", __FUNCTION__); } -+ (NSString*)_getCurrentStack { - WTF::StringBuilder trace; - StackTraceFunctor functor(trace); - static_cast(WTF::wtfThreadData().m_apiData)->_vm->topCallFrame->iterate(functor); - return (NSString*)trace.toString().createCFString().autorelease(); +- (NSString*)getCurrentStack { + std::stringstream output; + RefPtr callStack = Inspector::createScriptCallStack(self->_vm->topCallFrame, Inspector::ScriptCallStack::maxCallStackSizeToCapture); + for (size_t i = 0; i < callStack->size(); ++i) { + Inspector::ScriptCallFrame frame = callStack->at(i); + output << "\t" << std::setw(4) << std::setfill(' ') << std::left << i << frame.functionName().utf8().data() << "@" << frame.sourceURL().utf8().data(); + if (frame.lineNumber() && frame.columnNumber()) { + output << ":" << frame.lineNumber() << ":" << frame.columnNumber(); + } + if (i != callStack->size() - 1) { + output << "\n"; + } + } + return [NSString stringWithUTF8String:output.str().c_str()]; } @end diff --git a/src/NativeScript/TNSRuntime.h b/src/NativeScript/TNSRuntime.h index f1aacc1b5..7eaea4a6c 100644 --- a/src/NativeScript/TNSRuntime.h +++ b/src/NativeScript/TNSRuntime.h @@ -15,7 +15,7 @@ FOUNDATION_EXTERN void TNSSetUncaughtErrorHandler(TNSUncaughtErrorHandler handle @interface TNSRuntime : NSObject -@property(nonatomic, retain) NSString* applicationPath; +@property(nonatomic, retain, readonly) NSString* applicationPath; + (void)initializeMetadata:(void*)metadataPtr; @@ -29,4 +29,6 @@ FOUNDATION_EXTERN void TNSSetUncaughtErrorHandler(TNSUncaughtErrorHandler handle - (void)executeModule:(NSString*)entryPointModuleIdentifier; +- (JSValueRef)convertObject:(id)object; + @end \ No newline at end of file diff --git a/src/NativeScript/TNSRuntime.mm b/src/NativeScript/TNSRuntime.mm index 2aa71df51..29a16753f 100644 --- a/src/NativeScript/TNSRuntime.mm +++ b/src/NativeScript/TNSRuntime.mm @@ -26,6 +26,7 @@ #import "TNSRuntime+Private.h" #include "JSErrors.h" #include "Metadata/Metadata.h" +#include "ObjCTypes.h" using namespace JSC; using namespace NativeScript; @@ -118,6 +119,11 @@ - (void)executeModule:(NSString*)entryPointModuleIdentifier { } } +- (JSValueRef)convertObject:(id)object { + JSLockHolder lock(*self->_vm); + return toRef(self->_globalObject->globalExec(), toValue(self->_globalObject->globalExec(), object)); +} + - (void)dealloc { [self->_applicationPath release]; #if PLATFORM(IOS) diff --git a/src/debugging/TNSDebugging.h b/src/debugging/TNSDebugging.h index 644949251..4141f5643 100644 --- a/src/debugging/TNSDebugging.h +++ b/src/debugging/TNSDebugging.h @@ -153,7 +153,7 @@ static dispatch_source_t TNSCreateInspectorServer( return listenSource; } -static void TNSObjectiveCUncaughtExceptionHandler(NSException *exception) { +static void TNSInspectorUncaughtExceptionHandler(NSException *exception) { JSStringRef exceptionMessage = JSStringCreateWithUTF8CString(exception.description.UTF8String); @@ -211,7 +211,7 @@ static void TNSEnableRemoteInspector(int argc, char **argv) { inspector = [runtime attachInspectorWithHandler:sendMessageToFrontend]; - NSSetUncaughtExceptionHandler(&TNSObjectiveCUncaughtExceptionHandler); + NSSetUncaughtExceptionHandler(&TNSInspectorUncaughtExceptionHandler); if (isWaitingForDebugger) { isWaitingForDebugger = NO; diff --git a/tests/TestFixtures/exported-symbols.txt b/tests/TestFixtures/exported-symbols.txt index 839513523..f5072b36b 100644 --- a/tests/TestFixtures/exported-symbols.txt +++ b/tests/TestFixtures/exported-symbols.txt @@ -5,6 +5,7 @@ functionReturningFunctionPtrAsVoidPtr functionReturnsCFRetained functionReturnsNSRetained functionReturnsUnmanaged +functionThrowsException functionWhichReturnsSimpleFunctionPointer functionWith_BoolPtr functionWith_VoidPtr