mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 01:19:28 +01:00
JBR-8651 Pycharm Crashing after lock/sleep: SIGABRT at # C [libsystem_kernel.dylib+0x9388] __pthread_kill / __displaycb_handle_block_invoke
Rewritten exception handling to adopt the improved NSApplicationAWT exception handler (log all exceptions, avoid crash GUI) Fixed JNI_COCOA_EXIT(env) usages to test also pending JNI exceptions Added JNI_COCOA_EXIT_FATAL(message) used by NSApplicationAWT.sendEvent to report a crash with full details Unified logException to report both crash and exceptions Added isAWTCrashOnException() using the system property 'apple.awt.crashOnException' to crash on any exception occuring in NSApplication level Added missing CHECK_EXCEPTION after (*env)->Call...Method() Intercept all exception in NSApplicationAWT as NSExceptionHandlerDelegate + added tests in LWCToolkit (native) and Java code to test all exceptions are reported in logs and caught properly
This commit is contained in:
@@ -741,8 +741,7 @@ static void debugPrintNSEvent(NSEvent* event, const char* comment) {
|
||||
DECLARE_CLASS_RETURN(jc_CPlatformView, "sun/lwawt/macosx/CPlatformView", NULL);
|
||||
DECLARE_FIELD_RETURN(jf_Peer, jc_CPlatformView, "peer", "Lsun/lwawt/LWWindowPeer;", NULL);
|
||||
if ((env == NULL) || (m_cPlatformView == NULL)) {
|
||||
NSLog(@"Apple AWT : Error AWTView:awtComponent given bad parameters.");
|
||||
NSLog(@"%@",[NSThread callStackSymbols]);
|
||||
NSAPP_AWT_LOG_MESSAGE(@"Apple AWT: Error AWTView:awtComponent given bad parameters.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -755,8 +754,7 @@ static void debugPrintNSEvent(NSEvent* event, const char* comment) {
|
||||
DECLARE_CLASS_RETURN(jc_LWWindowPeer, "sun/lwawt/LWWindowPeer", NULL);
|
||||
DECLARE_FIELD_RETURN(jf_Target, jc_LWWindowPeer, "target", "Ljava/awt/Component;", NULL);
|
||||
if (peer == NULL) {
|
||||
NSLog(@"Apple AWT : Error AWTView:awtComponent got null peer from CPlatformView");
|
||||
NSLog(@"%@",[NSThread callStackSymbols]);
|
||||
NSAPP_AWT_LOG_MESSAGE(@"Apple AWT: Error AWTView:awtComponent got null peer from CPlatformView");
|
||||
return NULL;
|
||||
}
|
||||
jobject comp = (*env)->GetObjectField(env, peer, jf_Target);
|
||||
|
||||
@@ -232,10 +232,9 @@ AWT_NS_WINDOW_IMPLEMENTATION
|
||||
};
|
||||
void (*_objc_msgSendSuper)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper; //cast our pointer so the compiler can sort out the ABI
|
||||
(*_objc_msgSendSuper)(&mySuper, @selector(_changeJustMain));
|
||||
} @catch (NSException *ex) {
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"WARNING: suppressed exception from _changeJustMain (workaround for JBR-2562)");
|
||||
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
|
||||
[NSApplicationAWT logException:ex forProcess:processInfo];
|
||||
NSAPP_AWT_LOG_EXCEPTION(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,10 +1058,9 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
NSRect frame;
|
||||
@try {
|
||||
frame = ConvertNSScreenRect(env, [self.nsWindow frame]);
|
||||
} @catch (NSException *e) {
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"WARNING: suppressed exception from ConvertNSScreenRect() in [AWTWindow _deliverMoveResizeEvent]");
|
||||
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
|
||||
[NSApplicationAWT logException:e forProcess:processInfo];
|
||||
NSAPP_AWT_LOG_EXCEPTION(exception);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1396,7 +1394,9 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
jobject target = (*env)->GetObjectField(env, platformWindow, jf_target);
|
||||
if (target) {
|
||||
h = (CGFloat) (*env)->CallFloatMethod(env, target, jm_internalCustomTitleBarHeight);
|
||||
if (!(*env)->ExceptionCheck(env)) {
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
} else {
|
||||
self.customTitleBarControlsVisible = (BOOL) (*env)->CallBooleanMethod(env, target, jm_internalCustomTitleBarControlsVisible);
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, target);
|
||||
@@ -1565,8 +1565,8 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
GET_CPLATFORM_WINDOW_CLASS();
|
||||
DECLARE_METHOD(jm_deliverNCMouseDown, jc_CPlatformWindow, "deliverNCMouseDown", "()V");
|
||||
(*env)->CallVoidMethod(env, platformWindow, jm_deliverNCMouseDown);
|
||||
CHECK_EXCEPTION();
|
||||
(*env)->DeleteLocalRef(env, platformWindow);
|
||||
CHECK_EXCEPTION();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2234,6 +2234,7 @@ JNI_COCOA_ENTER(env);
|
||||
DECLARE_METHOD_RETURN(jm_isIgnoreMouseEvents, jc_Window, "isIgnoreMouseEvents", "()Z", 0);
|
||||
isIgnoreMouseEvents = (*env)->CallBooleanMethod(env, awtWindow, jm_isIgnoreMouseEvents) == JNI_TRUE ? YES : NO;
|
||||
(*env)->DeleteLocalRef(env, awtWindow);
|
||||
CHECK_EXCEPTION();
|
||||
}
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
BOOL isApplicationOwner = NO;
|
||||
if (NSApp != nil) {
|
||||
if ([NSApp isMemberOfClass:[NSApplication class]] && overrideDelegate) shouldInstall = YES;
|
||||
if ([NSApp isKindOfClass:[NSApplicationAWT class]]) {
|
||||
if ([NSApplicationAWT isNSApplicationAWT]) {
|
||||
shouldInstall = YES;
|
||||
isApplicationOwner = YES;
|
||||
}
|
||||
@@ -213,7 +213,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
// Prep for about and preferences menu
|
||||
BOOL usingDefaultNib = YES;
|
||||
if ([NSApp isKindOfClass:[NSApplicationAWT class]]) {
|
||||
if ([NSApplicationAWT isNSApplicationAWT]) {
|
||||
usingDefaultNib = [NSApp usingDefaultNib];
|
||||
}
|
||||
if (!usingDefaultNib) return self;
|
||||
|
||||
@@ -102,6 +102,7 @@ JNI_COCOA_ENTER(env);
|
||||
// Pass the data to drop target:
|
||||
@try {
|
||||
(*env)->CallVoidMethod(env, jthis, newDataMethod, jformat, jdropdata); // AWT_THREADING Safe (!appKit)
|
||||
CHECK_EXCEPTION();
|
||||
} @catch (NSException *ex) {
|
||||
DLog2(@"[CDropTargetContextPeer startTransfer]: exception in newData() for %d.\n", (NSInteger) jdroptarget);
|
||||
(*env)->DeleteGlobalRef(env, jdropdata);
|
||||
|
||||
@@ -116,6 +116,7 @@ static void displaycb_handle
|
||||
(CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo)
|
||||
{
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
if (TRACE_DISPLAY_CALLBACKS) {
|
||||
@@ -152,7 +153,6 @@ JNI_COCOA_ENTER(env);
|
||||
block:^()
|
||||
{
|
||||
@try {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
jobject graphicsEnv = (*env)->NewLocalRef(env, cgeRef);
|
||||
if (graphicsEnv == NULL) return; // ref already GC'd
|
||||
DECLARE_CLASS(jc_CGraphicsEnvironment, "sun/awt/CGraphicsEnvironment");
|
||||
@@ -173,7 +173,6 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
// braces to reduce variable scope
|
||||
{
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
jobject graphicsEnv = (*env)->NewLocalRef(env, cgeRef);
|
||||
if (graphicsEnv == NULL) return; // ref already GC'd
|
||||
DECLARE_CLASS(jc_CGraphicsEnvironment, "sun/awt/CGraphicsEnvironment");
|
||||
|
||||
@@ -223,6 +223,12 @@ void JavaCT_DrawGlyphVector
|
||||
// Using the Quartz Surface Data context, draw a hot-substituted character run
|
||||
void JavaCT_DrawTextUsingQSD(JNIEnv *env, const QuartzSDOps *qsdo, const AWTStrike *strike, const jchar *chars, const jsize length)
|
||||
{
|
||||
NSAttributedString *attribString = nil;
|
||||
CTTypesetterRef typeSetterRef = NULL;
|
||||
CTLineRef lineRef = NULL;
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
CGContextRef cgRef = qsdo->cgRef;
|
||||
|
||||
AWTFont *awtFont = strike->fAWTFont;
|
||||
@@ -251,20 +257,18 @@ void JavaCT_DrawTextUsingQSD(JNIEnv *env, const QuartzSDOps *qsdo, const AWTStri
|
||||
|
||||
CTTypesetterRef typeSetterRef = CTTypesetterCreateWithAttributedStringAndOptions((CFAttributedStringRef) attribString, (CFDictionaryRef) ctsDictionaryFor(nsFont, JRSFontStyleUsesFractionalMetrics(strike->fStyle)));
|
||||
*/
|
||||
NSAttributedString *attribString = [[NSAttributedString alloc]
|
||||
attribString = [[NSAttributedString alloc]
|
||||
initWithString:string
|
||||
attributes:ctsDictionaryFor(nsFont, JRSFontStyleUsesFractionalMetrics(strike->fStyle))];
|
||||
|
||||
CTTypesetterRef typeSetterRef = CTTypesetterCreateWithAttributedString((CFAttributedStringRef) attribString);
|
||||
typeSetterRef = CTTypesetterCreateWithAttributedString((CFAttributedStringRef) attribString);
|
||||
|
||||
CFRange range = {0, length};
|
||||
CTLineRef lineRef = CTTypesetterCreateLine(typeSetterRef, range);
|
||||
lineRef = CTTypesetterCreateLine(typeSetterRef, range);
|
||||
|
||||
CTLineDraw(lineRef, cgRef);
|
||||
|
||||
[attribString release];
|
||||
CFRelease(lineRef);
|
||||
CFRelease(typeSetterRef);
|
||||
JNI_COCOA_EXIT_WITH_ACTION(env, ( [attribString release], CFRelease_even_NULL(lineRef), CFRelease_even_NULL(typeSetterRef) ) );
|
||||
}
|
||||
|
||||
|
||||
@@ -280,16 +284,16 @@ static void DrawTextContext
|
||||
return;
|
||||
}
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
qsdo->BeginSurface(env, qsdo, SD_Text);
|
||||
if (qsdo->cgRef == NULL)
|
||||
{
|
||||
qsdo->FinishSurface(env, qsdo);
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef cgRef = qsdo->cgRef;
|
||||
|
||||
|
||||
CGContextSaveGState(cgRef);
|
||||
JRSFontSetRenderingStyleOnContext(cgRef, strike->fStyle);
|
||||
|
||||
@@ -303,7 +307,7 @@ static void DrawTextContext
|
||||
|
||||
CGContextRestoreGState(cgRef);
|
||||
|
||||
qsdo->FinishSurface(env, qsdo);
|
||||
JNI_COCOA_RENDERER_EXIT(env);
|
||||
}
|
||||
|
||||
#pragma mark --- Glyph Vector Pipeline ---
|
||||
@@ -528,40 +532,34 @@ static inline void doDrawGlyphsPipe_getGlyphVectorLengthAndAlloc
|
||||
CGSize advances[length];
|
||||
doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers(env, qsdo, strike, gVector, glyphs, uniChars, advances, length, glyphsArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, we should malloc and free buffers for this large run
|
||||
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * length);
|
||||
int *uniChars = (int *)malloc(sizeof(int) * length);
|
||||
CGSize *advances = (CGSize *)malloc(sizeof(CGSize) * length);
|
||||
else {
|
||||
CGGlyph *glyphs = NULL;
|
||||
int *uniChars = NULL;
|
||||
CGSize *advances = NULL;
|
||||
@try {
|
||||
// otherwise, we should malloc and free buffers for this large run
|
||||
glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * length);
|
||||
uniChars = (int *) malloc(sizeof(int) * length);
|
||||
advances = (CGSize *) malloc(sizeof(CGSize) * length);
|
||||
|
||||
if (glyphs == NULL || uniChars == NULL || advances == NULL)
|
||||
{
|
||||
(*env)->DeleteLocalRef(env, glyphsArray);
|
||||
[NSException raise:NSMallocException format:@"%s-%s:%d", __FILE__, __FUNCTION__, __LINE__];
|
||||
if (glyphs)
|
||||
{
|
||||
if ((glyphs == NULL) || (uniChars == NULL) || (advances == NULL)) {
|
||||
[NSException raise:NSMallocException format:@"allocation failure (%s:%d %s)", __FILE__, __LINE__, __FUNCTION__];
|
||||
} else {
|
||||
doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers(env, qsdo, strike, gVector, glyphs, uniChars, advances, length, glyphsArray);
|
||||
}
|
||||
} @finally {
|
||||
if (glyphs != NULL) {
|
||||
free(glyphs);
|
||||
}
|
||||
if (uniChars)
|
||||
{
|
||||
if (uniChars != NULL) {
|
||||
free(uniChars);
|
||||
}
|
||||
if (advances)
|
||||
{
|
||||
if (advances != NULL) {
|
||||
free(advances);
|
||||
}
|
||||
return;
|
||||
(*env)->DeleteLocalRef(env, glyphsArray);
|
||||
}
|
||||
|
||||
doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers(env, qsdo, strike, gVector, glyphs, uniChars, advances, length, glyphsArray);
|
||||
|
||||
free(glyphs);
|
||||
free(uniChars);
|
||||
free(advances);
|
||||
}
|
||||
|
||||
(*env)->DeleteLocalRef(env, glyphsArray);
|
||||
}
|
||||
|
||||
// Setup and save the state of the CGContext, and apply any java.awt.Font transforms to the context.
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#import <objc/runtime.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Security/AuthSession.h>
|
||||
#import <ExceptionHandling/NSExceptionHandler.h>
|
||||
|
||||
#import "JNIUtilities.h"
|
||||
#import "LWCToolkit.h"
|
||||
@@ -203,19 +204,22 @@ static BOOL inDoDragDropLoop;
|
||||
}
|
||||
|
||||
- (void)perform {
|
||||
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
|
||||
DECLARE_CLASS(sjc_Runnable, "java/lang/Runnable");
|
||||
DECLARE_METHOD(jm_Runnable_run, sjc_Runnable, "run", "()V");
|
||||
(*env)->CallVoidMethod(env, self.runnable, jm_Runnable_run);
|
||||
CHECK_EXCEPTION();
|
||||
[self release];
|
||||
@try {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
DECLARE_CLASS(sjc_Runnable, "java/lang/Runnable");
|
||||
DECLARE_METHOD(jm_Runnable_run, sjc_Runnable, "run", "()V");
|
||||
(*env)->CallVoidMethod(env, self.runnable, jm_Runnable_run);
|
||||
CHECK_EXCEPTION();
|
||||
} @finally {
|
||||
[self release];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
void setBusy(BOOL busy) {
|
||||
AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
JNI_COCOA_ENTER(env);
|
||||
DECLARE_CLASS(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown");
|
||||
|
||||
if (busy) {
|
||||
@@ -225,7 +229,8 @@ void setBusy(BOOL busy) {
|
||||
DECLARE_STATIC_METHOD(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V");
|
||||
(*env)->CallStaticVoidMethod(env, jc_AWTAutoShutdown, jm_notifyFreeMethod);
|
||||
}
|
||||
CHECK_EXCEPTION();
|
||||
CHECK_EXCEPTION();
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
static void setUpAWTAppKit(BOOL installObservers)
|
||||
@@ -269,7 +274,6 @@ static void setUpAWTAppKit(BOOL installObservers)
|
||||
DECLARE_STATIC_METHOD(jsm_installToolkitThreadInJava, jc_LWCToolkit, "installToolkitThreadInJava", "()V");
|
||||
(*env)->CallStaticVoidMethod(env, jc_LWCToolkit, jsm_installToolkitThreadInJava);
|
||||
CHECK_EXCEPTION();
|
||||
|
||||
}
|
||||
|
||||
BOOL isSWTInWebStart(JNIEnv* env) {
|
||||
@@ -277,11 +281,6 @@ BOOL isSWTInWebStart(JNIEnv* env) {
|
||||
return [@"true" isCaseInsensitiveLike:swtWebStart];
|
||||
}
|
||||
|
||||
static void AWT_NSUncaughtExceptionHandler(NSException *exception) {
|
||||
NSLog(@"Apple AWT Internal Exception: %@", [exception description]);
|
||||
NSLog(@"trace: %@", [exception callStackSymbols]);
|
||||
}
|
||||
|
||||
@interface AWTStarter : NSObject
|
||||
+ (void)start:(BOOL)headless;
|
||||
+ (void)starter:(BOOL)onMainThread headless:(BOOL)headless;
|
||||
@@ -374,70 +373,73 @@ static void AWT_NSUncaughtExceptionHandler(NSException *exception) {
|
||||
if (delegate != nil) {
|
||||
OSXAPP_SetApplicationDelegate(delegate);
|
||||
}
|
||||
// Intercept any exception to let NSApplicationAWT handle them:
|
||||
NSExceptionHandler* exceptionHandler = [NSExceptionHandler defaultExceptionHandler];
|
||||
exceptionHandler.exceptionHandlingMask = NSLogAndHandleEveryExceptionMask;
|
||||
[exceptionHandler setDelegate:[NSApplication sharedApplication]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)starter:(BOOL)wasOnMainThread headless:(BOOL)headless {
|
||||
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
||||
// Add the exception handler of last resort
|
||||
NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler);
|
||||
|
||||
// Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return.
|
||||
if (headless) {
|
||||
// Note that we don't install run loop observers in headless mode
|
||||
// because we don't need them (see 7174704)
|
||||
if (!forceEmbeddedMode) {
|
||||
setUpAWTAppKit(false);
|
||||
@try {
|
||||
// Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return.
|
||||
if (headless) {
|
||||
// Note that we don't install run loop observers in headless mode
|
||||
// because we don't need them (see 7174704)
|
||||
if (!forceEmbeddedMode) {
|
||||
setUpAWTAppKit(false);
|
||||
}
|
||||
[AWTStarter markAppAsDaemon];
|
||||
return;
|
||||
}
|
||||
[AWTStarter markAppAsDaemon];
|
||||
return;
|
||||
}
|
||||
|
||||
if (forceEmbeddedMode) {
|
||||
AWT_STARTUP_LOG(@"in SWT or SWT/WebStart mode");
|
||||
if (forceEmbeddedMode) {
|
||||
AWT_STARTUP_LOG(@"in SWT or SWT/WebStart mode");
|
||||
|
||||
// Init a default NSApplication instance instead of the NSApplicationAWT.
|
||||
// Note that [NSApp isRunning] will return YES after that, though
|
||||
// this behavior isn't specified anywhere. We rely on that.
|
||||
NSApplicationLoad();
|
||||
}
|
||||
// Init a default NSApplication instance instead of the NSApplicationAWT.
|
||||
// Note that [NSApp isRunning] will return YES after that, though
|
||||
// this behavior isn't specified anywhere. We rely on that.
|
||||
NSApplicationLoad();
|
||||
}
|
||||
|
||||
// This will create a NSApplicationAWT for standalone AWT programs, unless there is
|
||||
// already a NSApplication instance. If there is already a NSApplication instance,
|
||||
// and -[NSApplication isRunning] returns YES, AWT is embedded inside another
|
||||
// AppKit Application.
|
||||
NSApplication *app = [NSApplicationAWT sharedApplication];
|
||||
isEmbedded = ![NSApp isKindOfClass:[NSApplicationAWT class]];
|
||||
// This will create a NSApplicationAWT for standalone AWT programs, unless there is
|
||||
// already a NSApplication instance. If there is already a NSApplication instance,
|
||||
// and -[NSApplication isRunning] returns YES, AWT is embedded inside another
|
||||
// AppKit Application.
|
||||
NSApplication *app = [NSApplicationAWT sharedApplication];
|
||||
isEmbedded = ![NSApplicationAWT isNSApplicationAWT];
|
||||
|
||||
if (!isEmbedded) {
|
||||
// Install run loop observers and set the AppKit Java thread name
|
||||
setUpAWTAppKit(true);
|
||||
}
|
||||
if (!isEmbedded) {
|
||||
// Install run loop observers and set the AppKit Java thread name
|
||||
setUpAWTAppKit(true);
|
||||
}
|
||||
|
||||
// AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent.
|
||||
if (![app isRunning]) {
|
||||
AWT_STARTUP_LOG(@"+[AWTStarter startAWT]: ![app isRunning]");
|
||||
// This is where the AWT AppKit thread parks itself to process events.
|
||||
[NSApplicationAWT runAWTLoopWithApp: app];
|
||||
} else {
|
||||
// We're either embedded, or showing a splash screen
|
||||
if (isEmbedded) {
|
||||
AWT_STARTUP_LOG(@"running embedded");
|
||||
|
||||
// We don't track if the runloop is busy, so set it free to let AWT finish when it needs
|
||||
setBusy(NO);
|
||||
// AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent.
|
||||
if (![app isRunning]) {
|
||||
AWT_STARTUP_LOG(@"+[AWTStarter startAWT]: ![app isRunning]");
|
||||
// This is where the AWT AppKit thread parks itself to process events.
|
||||
[NSApplicationAWT runAWTLoopWithApp:app];
|
||||
} else {
|
||||
AWT_STARTUP_LOG(@"running after showing a splash screen");
|
||||
// We're either embedded, or showing a splash screen
|
||||
if (isEmbedded) {
|
||||
AWT_STARTUP_LOG(@"running embedded");
|
||||
|
||||
// We don't track if the runloop is busy, so set it free to let AWT finish when it needs
|
||||
setBusy(NO);
|
||||
} else {
|
||||
AWT_STARTUP_LOG(@"running after showing a splash screen");
|
||||
}
|
||||
|
||||
// Signal so that JNI_OnLoad can proceed.
|
||||
if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil];
|
||||
|
||||
// Proceed to exit this call as there is no reason to run the NSApplication event loop.
|
||||
}
|
||||
|
||||
// Signal so that JNI_OnLoad can proceed.
|
||||
if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil];
|
||||
|
||||
// Proceed to exit this call as there is no reason to run the NSApplication event loop.
|
||||
} @finally {
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -452,9 +454,8 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_nativeSyncQueue
|
||||
{
|
||||
long currentEventNum = [AWTToolkit getEventCount];
|
||||
|
||||
NSApplication* sharedApp = [NSApplication sharedApplication];
|
||||
if ([sharedApp isKindOfClass:[NSApplicationAWT class]]) {
|
||||
NSApplicationAWT* theApp = (NSApplicationAWT*)sharedApp;
|
||||
NSApplicationAWT* app = [NSApplicationAWT sharedApplicationAWT];
|
||||
if (app != nil) {
|
||||
// We use two different API to post events to the application,
|
||||
// - [NSApplication postEvent]
|
||||
// - CGEventPost(), see CRobot.m
|
||||
@@ -465,25 +466,30 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_nativeSyncQueue
|
||||
|
||||
// If the native drag is in progress, skip native sync.
|
||||
if (!AWTToolkit.inDoDragDropLoop) {
|
||||
[theApp postDummyEvent:false];
|
||||
[theApp waitForDummyEvent:timeout / 2.0];
|
||||
@try {
|
||||
[app postDummyEvent:false];
|
||||
} @finally {
|
||||
[app waitForDummyEvent:timeout / 2.0];
|
||||
}
|
||||
}
|
||||
// test the condition again as inDoDragDropLoop may have changed?
|
||||
if (!AWTToolkit.inDoDragDropLoop) {
|
||||
[theApp postDummyEvent:true];
|
||||
[theApp waitForDummyEvent:timeout / 2.0];
|
||||
@try {
|
||||
[app postDummyEvent:true];
|
||||
} @finally {
|
||||
[app waitForDummyEvent:timeout / 2.0];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// could happen if we are embedded inside SWT application,
|
||||
// in this case just spin a single empty block through
|
||||
// the event loop to give it a chance to process pending events
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){}];
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:[ThreadUtilities GetEmptyBlock]];
|
||||
}
|
||||
|
||||
if (([AWTToolkit getEventCount] - currentEventNum) != 0) {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
@@ -496,7 +502,7 @@ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_LWCToolkit_flushNativeSelectors
|
||||
(JNIEnv *env, jclass clz)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){}];
|
||||
[ThreadUtilities performOnMainThreadWaiting:YES block:[ThreadUtilities GetEmptyBlock]];
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
@@ -893,6 +899,9 @@ JNIEXPORT void JNICALL
|
||||
Java_sun_lwawt_macosx_LWCToolkit_initIDs
|
||||
(JNIEnv *env, jclass klass) {
|
||||
|
||||
// Add the exception handler of last resort:
|
||||
GetAWTUncaughtExceptionHandler();
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
gNumberOfButtons = sun_lwawt_macosx_LWCToolkit_BUTTONS;
|
||||
@@ -914,8 +923,7 @@ Java_sun_lwawt_macosx_LWCToolkit_initIDs
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < gNumberOfButtons; i++) {
|
||||
for (int i = 0; i < gNumberOfButtons; i++) {
|
||||
gButtonDownMasks[i] = tmp[i];
|
||||
}
|
||||
|
||||
|
||||
@@ -1339,6 +1339,7 @@ static void treeNodeExpandedCollapsedImpl(
|
||||
const jobject cAccessibleGlobal = (*env)->NewGlobalRef(env, cAccessible);
|
||||
|
||||
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
if (cAccessibleGlobal != NULL) {
|
||||
(*env)->DeleteGlobalRef(env, cAccessibleGlobal);
|
||||
}
|
||||
@@ -1527,7 +1528,7 @@ void nativeAnnounceAppKit(
|
||||
|
||||
JNIEnv* const env = [ThreadUtilities getJNIEnv];
|
||||
if (env == NULL) { // unlikely
|
||||
NSLog(@"%s: failed to get JNIEnv instance\n%@\n", __func__, [NSThread callStackSymbols]);
|
||||
NSAPP_AWT_LOG_MESSAGE(@"Failed to get JNIEnv instance");
|
||||
return; // I believe it's dangerous to go on announcing in that case
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ JNIEXPORT JAWT_DrawingSurfaceInfo* JNICALL awt_DrawingSurface_GetDrawingSurfaceI
|
||||
DECLARE_METHOD_RETURN(jm_getPointer, jc_PlatformComponent, "getPointer", "()J", NULL);
|
||||
AWTSurfaceLayers *surfaceLayers = jlong_to_ptr((*env)->CallLongMethod(env, platformComponent, jm_getPointer));
|
||||
// REMIND: assert(surfaceLayers)
|
||||
CHECK_EXCEPTION();
|
||||
|
||||
dsi->platformInfo = surfaceLayers;
|
||||
dsi->ds = ds;
|
||||
|
||||
@@ -1087,7 +1087,8 @@ extern void initSamplers(id<MTLDevice> device);
|
||||
}
|
||||
|
||||
CVReturn mtlDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* nowTime, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) {
|
||||
JNI_COCOA_ENTER();
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
JNI_COCOA_ENTER(env);
|
||||
J2dTraceLn(J2D_TRACE_VERBOSE, "MTLContext_mtlDisplayLinkCallback: ctx=%p", displayLinkContext);
|
||||
|
||||
MTLDisplayLinkState *dlState = (__bridge MTLDisplayLinkState*) displayLinkContext;
|
||||
@@ -1130,7 +1131,7 @@ CVReturn mtlDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp*
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(redraw:) on:mtlc withObject:@(displayID)
|
||||
waitUntilDone:NO useJavaModes:NO]; // critical
|
||||
JNI_COCOA_EXIT();
|
||||
JNI_COCOA_EXIT(env);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ void
|
||||
MTLGC_DestroyMTLGraphicsConfig(jlong pConfigInfo)
|
||||
{
|
||||
J2dTraceLn(J2D_TRACE_INFO, "MTLGC_DestroyMTLGraphicsConfig");
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
JNI_COCOA_ENTER(env);
|
||||
__block MTLGraphicsConfigInfo *mtlinfo = (MTLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
|
||||
if (mtlinfo == NULL) {
|
||||
|
||||
@@ -569,9 +569,7 @@ Java_sun_java2d_metal_MTLLayer_nativeCreateLayer
|
||||
(JNIEnv *env, jobject obj, jboolean perfCountersEnabled)
|
||||
{
|
||||
__block MTLLayer *layer = nil;
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
JNI_COCOA_ENTER(env);
|
||||
jobject javaLayer = (*env)->NewWeakGlobalRef(env, obj);
|
||||
|
||||
// Wait and ensure main thread creates the MTLLayer instance now:
|
||||
@@ -583,8 +581,7 @@ JNI_COCOA_ENTER(env);
|
||||
if (TRACE_DISPLAY) {
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "MTLLayer_nativeCreateLayer: created layer[%p]", layer);
|
||||
}
|
||||
JNI_COCOA_EXIT(env);
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
return ptr_to_jlong(layer);
|
||||
}
|
||||
|
||||
@@ -593,6 +590,7 @@ JNIEXPORT void JNICALL
|
||||
Java_sun_java2d_metal_MTLLayer_validate
|
||||
(JNIEnv *env, jclass cls, jlong layerPtr, jobject surfaceData)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
MTLLayer *layer = OBJC(layerPtr);
|
||||
|
||||
if (surfaceData != NULL) {
|
||||
@@ -644,6 +642,7 @@ Java_sun_java2d_metal_MTLLayer_validate
|
||||
layer.ctx = NULL;
|
||||
[layer stopRedraw:YES];
|
||||
}
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
@@ -688,7 +687,6 @@ Java_sun_java2d_metal_MTLLayer_blitTexture
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[layer blitTexture];
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
@@ -698,13 +696,11 @@ Java_sun_java2d_metal_MTLLayer_nativeSetOpaque
|
||||
(JNIEnv *env, jclass cls, jlong layerPtr, jboolean opaque)
|
||||
{
|
||||
JNI_COCOA_ENTER(env);
|
||||
|
||||
MTLLayer *layer = jlong_to_ptr(layerPtr);
|
||||
// Ensure main thread changes the MTLLayer instance later:
|
||||
[ThreadUtilities performOnMainThreadWaiting:NO useJavaModes:NO // critical
|
||||
block:^(){
|
||||
[layer setOpaque:(opaque == JNI_TRUE)];
|
||||
}];
|
||||
|
||||
JNI_COCOA_EXIT(env);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ static const char* mtlOpToStr(uint op);
|
||||
BOOL sync = NO; \
|
||||
jint opcode = -1; \
|
||||
const NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
|
||||
@try
|
||||
@try {
|
||||
|
||||
/*
|
||||
* Derived from JNI_COCOA_EXIT(env):
|
||||
@@ -70,23 +70,18 @@ static const char* mtlOpToStr(uint op);
|
||||
* If there is a Java exception that has been thrown that should escape.
|
||||
* And ensure we drain the auto-release pool.
|
||||
*/
|
||||
#define RENDER_LOOP_EXIT(env, className) \
|
||||
@catch (NSException *e) { \
|
||||
#define RENDER_LOOP_EXIT(env, className) \
|
||||
} @catch (NSException *exception) { \
|
||||
lwc_plog(env, "%s_flushBuffer: Failed opcode=%s op=%s dstType=%s ctx=%p", \
|
||||
className, mtlOpCodeToStr(opcode), mtlOpToStr(mtlPreviousOp), \
|
||||
mtlDstTypeToStr(DST_TYPE(dstOps)), mtlc); \
|
||||
char* str = [NSString stringWithFormat:@"%@", [e description]].UTF8String; \
|
||||
lwc_plog(env, "%s_flushBuffer Exception: %s", className, str); \
|
||||
str = [NSString stringWithFormat:@"%@", [e callStackSymbols]].UTF8String; \
|
||||
lwc_plog(env, "%s_flushBuffer callstack: %s", className, str); \
|
||||
/* Finally (JetBrains Runtime only) report this message to JVM crash log: */ \
|
||||
JNU_LOG_EVENT(env, "%s_flushBuffer: Failed opcode=%s op=%s dstType=%s ctx=%p", \
|
||||
className, mtlOpCodeToStr(opcode), mtlOpToStr(mtlPreviousOp), \
|
||||
mtlDstTypeToStr(DST_TYPE(dstOps)), mtlc); \
|
||||
/* report fatal failure to make a crash report: */ \
|
||||
JNU_Fatal(env, __FILE__, __LINE__, "flushBuffer failed"); \
|
||||
} \
|
||||
@finally { \
|
||||
/* report exception to the NSApplicationAWT exception handler: */ \
|
||||
NSAPP_AWT_REPORT_EXCEPTION(exception, NO); \
|
||||
} @finally { \
|
||||
/* flush GPU state before draining pool: */ \
|
||||
MTLRenderQueue_reset(mtlc, sync); \
|
||||
[pool drain]; \
|
||||
@@ -177,7 +172,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
|
||||
|
||||
// Handle any NSException thrown:
|
||||
RENDER_LOOP_ENTER(env)
|
||||
{
|
||||
|
||||
while (b < end) {
|
||||
opcode = NEXT_INT(b);
|
||||
|
||||
@@ -958,7 +953,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
|
||||
return;
|
||||
}
|
||||
} // while op
|
||||
}
|
||||
|
||||
RENDER_LOOP_EXIT(env, "MTLRenderQueue");
|
||||
}
|
||||
|
||||
|
||||
@@ -172,8 +172,13 @@ MTLSD_SetNativeDimensions(JNIEnv *env, BMTLSDOps *mtlsdo,
|
||||
}
|
||||
|
||||
JNU_SetFieldByName(env, NULL, sdObject, "nativeWidth", "I", width);
|
||||
if (!((*env)->ExceptionCheck(env))) {
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
} else {
|
||||
JNU_SetFieldByName(env, NULL, sdObject, "nativeHeight", "I", height);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
}
|
||||
|
||||
(*env)->DeleteLocalRef(env, sdObject);
|
||||
@@ -215,8 +220,12 @@ MTLSD_Delete(JNIEnv *env, BMTLSDOps *bmtlsdo)
|
||||
void
|
||||
MTLSD_Dispose(JNIEnv *env, SurfaceDataOps *ops)
|
||||
{
|
||||
JNU_CallStaticMethodByName(env, NULL, "sun/java2d/metal/MTLSurfaceData",
|
||||
jboolean exc;
|
||||
JNU_CallStaticMethodByName(env, &exc, "sun/java2d/metal/MTLSurfaceData",
|
||||
"dispose", "(J)V", ptr_to_jlong(ops));
|
||||
if (exc) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,15 +28,18 @@
|
||||
|
||||
#include "jni.h"
|
||||
#include "jni_util.h"
|
||||
#include "ThreadUtilities.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
/* Define to 1 to check for pending exceptions in JNI_COCOA_EXIT */
|
||||
#define CHECK_PENDING_EXCEPTION 0
|
||||
|
||||
/******** LOGGING SUPPORT *********/
|
||||
|
||||
#define LOG_NULL(dst_var, name) \
|
||||
if (dst_var == NULL) { \
|
||||
NSLog(@"Bad JNI lookup %s\n", name); \
|
||||
NSLog(@"%@",[NSThread callStackSymbols]); \
|
||||
NSLog(@"Bad JNI lookup %s\n%@", name, [NSThread callStackSymbols]); \
|
||||
if ([NSThread isMainThread] == NO) { \
|
||||
if (!(*env)->ExceptionCheck(env)) { \
|
||||
JNU_ThrowInternalError(env, "Bad JNI Lookup"); \
|
||||
@@ -186,22 +189,36 @@
|
||||
#define CHECK_EXCEPTION_IN_ENV(env) { \
|
||||
jthrowable exc = (*(env))->ExceptionOccurred(env); \
|
||||
if (exc != NULL) { \
|
||||
if ([NSThread isMainThread] == YES) { \
|
||||
if ([NSThread isMainThread]) { \
|
||||
if (getenv("JNU_APPKIT_TRACE")) { \
|
||||
(*(env))->ExceptionDescribe(env); \
|
||||
NSLog(@"%@",[NSThread callStackSymbols]); \
|
||||
} else { \
|
||||
(*(env))->ExceptionClear(env); \
|
||||
NSLog(@"%@", [NSThread callStackSymbols]); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
(*env)->ExceptionClear(env); \
|
||||
if (getenv("JNU_NO_COCOA_EXCEPTION") == NULL) {\
|
||||
[NSException raise:NSGenericException \
|
||||
format:@"%@", ThrowableToNSString(env, exc)]; \
|
||||
} else { \
|
||||
(*(env))->ExceptionClear(env); \
|
||||
} \
|
||||
} \
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Use this macro to raise an NSException.
|
||||
* The message is a printf-style format string.
|
||||
* The arguments are the arguments to the format string.
|
||||
*
|
||||
* ONLY FOR DEBUGGING PURPOSES.
|
||||
* for example:
|
||||
* TEST_RAISE_EXCEPTION(@"LWCToolkit.setBusy: busy = %d", busy);
|
||||
*/
|
||||
#define TEST_RAISE_EXCEPTION(message, ...) { \
|
||||
[NSException raise:NSGenericException \
|
||||
format:@"RAISE_EXCEPTION in (%s:%d %s): %@", \
|
||||
__FILE__, __LINE__, __FUNCTION__, \
|
||||
[NSString stringWithFormat:message, ##__VA_ARGS__] \
|
||||
]; \
|
||||
}
|
||||
|
||||
#define CHECK_EXCEPTION() CHECK_EXCEPTION_IN_ENV(env)
|
||||
|
||||
@@ -209,7 +226,21 @@
|
||||
CHECK_EXCEPTION(); \
|
||||
if ((x) == NULL) { \
|
||||
return y; \
|
||||
};
|
||||
}
|
||||
|
||||
#define __JNI_LOG_EXCEPTION(exception) \
|
||||
NSAPP_AWT_LOG_EXCEPTION_PREFIX(exception, @"Apple AWT Cocoa Exception");
|
||||
|
||||
#if (CHECK_PENDING_EXCEPTION == 1)
|
||||
#define __JNI_CHECK_PENDING_EXCEPTION(env) \
|
||||
@try { \
|
||||
CHECK_EXCEPTION_IN_ENV(env); \
|
||||
} @catch (NSException *exception) { \
|
||||
__JNI_LOG_EXCEPTION(exception); \
|
||||
}
|
||||
#else
|
||||
#define __JNI_CHECK_PENDING_EXCEPTION(env) {}
|
||||
#endif
|
||||
|
||||
/* Create a pool and initiate a try block to catch any exception */
|
||||
#define JNI_COCOA_ENTER(env) \
|
||||
@@ -221,28 +252,24 @@
|
||||
* And ensure we drain the auto-release pool.
|
||||
*/
|
||||
#define JNI_COCOA_EXIT(env) \
|
||||
} \
|
||||
@catch (NSException *e) { \
|
||||
NSLog(@"Apple AWT Cocoa Exception: %@", [e description]); \
|
||||
NSLog(@"Apple AWT Cocoa Exception callstack: %@", [e callStackSymbols]); \
|
||||
} \
|
||||
@finally { \
|
||||
} @catch (NSException *exception) { \
|
||||
__JNI_LOG_EXCEPTION(exception); \
|
||||
} @finally { \
|
||||
__JNI_CHECK_PENDING_EXCEPTION(env); \
|
||||
[pool drain]; \
|
||||
};
|
||||
}
|
||||
|
||||
/* Same as above but adds a clean up action.
|
||||
* Requires that whatever is being cleaned up is in scope.
|
||||
*/
|
||||
#define JNI_COCOA_EXIT_WITH_ACTION(env, action) \
|
||||
} \
|
||||
@catch (NSException *e) { \
|
||||
} @catch (NSException *exception) { \
|
||||
__JNI_LOG_EXCEPTION(exception); \
|
||||
{ action; }; \
|
||||
NSLog(@"Apple AWT Cocoa Exception: %@", [e description]); \
|
||||
NSLog(@"Apple AWT Cocoa Exception callstack: %@", [e callStackSymbols]); \
|
||||
} \
|
||||
@finally { \
|
||||
} @finally { \
|
||||
__JNI_CHECK_PENDING_EXCEPTION(env); \
|
||||
[pool drain]; \
|
||||
};
|
||||
}
|
||||
|
||||
/******** STRING CONVERSION SUPPORT *********/
|
||||
|
||||
|
||||
@@ -115,8 +115,6 @@ jstring NormalizedPathJavaStringFromNSString(JNIEnv* env, NSString *str) {
|
||||
}
|
||||
|
||||
NSString *ThrowableToNSString(JNIEnv *env, jthrowable exc) {
|
||||
(*env)->ExceptionClear(env);
|
||||
|
||||
if (JNU_IsInstanceOfByName(env, exc, "java/lang/OutOfMemoryError")) {
|
||||
static NSString* const OOMEDescr = @"OutOfMemoryError";
|
||||
return OOMEDescr;
|
||||
@@ -128,17 +126,26 @@ NSString *ThrowableToNSString(JNIEnv *env, jthrowable exc) {
|
||||
DECLARE_METHOD_RETURN(jm_getStackTrace, sjc_Throwable, "getStackTrace",
|
||||
"()[Ljava/lang/StackTraceElement;", nil);
|
||||
jobject jstr = (*env)->CallObjectMethod(env, exc, jm_toString);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
|
||||
NSString* result = JavaStringToNSString(env, jstr);
|
||||
|
||||
jobjectArray frames =
|
||||
(jobjectArray) (*env)->CallObjectMethod(env, exc, jm_getStackTrace);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
if (frames != NULL) {
|
||||
jsize framesLen = (*env)->GetArrayLength(env, frames);
|
||||
|
||||
for (int i = 0; i < framesLen; i++) {
|
||||
jobject stackElem = (*env)->GetObjectArrayElement(env, frames, i);
|
||||
jobject stackElemStr = (*env)->CallObjectMethod(env, stackElem, jm_toString);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
NSString *frameStr = JavaStringToNSString(env, stackElemStr);
|
||||
result = [result stringByAppendingFormat:@"\n%@", frameStr];
|
||||
(*env)->DeleteLocalRef(env, stackElem);
|
||||
@@ -150,4 +157,3 @@ NSString *ThrowableToNSString(JNIEnv *env, jthrowable exc) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,24 @@ JNIEXPORT @interface NSApplicationAWT : NSApplication {
|
||||
- (void) waitForDummyEvent:(double) timeout;
|
||||
|
||||
+ (void) runAWTLoopWithApp:(NSApplication*)app;
|
||||
+ (void)logException:(NSException *)exception forProcess:(NSProcessInfo*)processInfo;
|
||||
+ (void) interruptAWTLoop;
|
||||
|
||||
+ (BOOL) isNSApplicationAWT;
|
||||
+ (NSApplicationAWT*) sharedApplicationAWT;
|
||||
|
||||
- (void) reportException:(NSException *)exception;
|
||||
- (void) reportException:(NSException *)exception uncaught:(BOOL)uncaught
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
+ (void) logException:(NSException *)exception;
|
||||
+ (void) logException:(NSException *)exception
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
+ (void) logException:(NSException *)exception prefix:(NSString *)prefix
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
+ (void) logMessage:(NSString *)message;
|
||||
+ (void) logMessage:(NSString *)message
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
@end
|
||||
|
||||
@@ -53,4 +70,3 @@ JNIEXPORT @interface NSApplicationAWT : NSApplication {
|
||||
@end
|
||||
|
||||
JNIEXPORT void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> delegate);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#import "NSApplicationAWT.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <ExceptionHandling/NSExceptionHandler.h>
|
||||
#import <JavaRuntimeSupport/JavaRuntimeSupport.h>
|
||||
|
||||
#import "PropertiesUtilities.h"
|
||||
@@ -38,6 +39,17 @@
|
||||
*/
|
||||
DEF_STATIC_JNI_OnLoad
|
||||
|
||||
/* may be worth to use system properties ? */
|
||||
#define MAX_RETRY_WRITE_EXCEPTION 3
|
||||
#define MAX_WRITE_EXCEPTION 50
|
||||
#define MAX_LOGGED_EXCEPTION 1000
|
||||
|
||||
#define JBR_ERR_PID_FILE @"%@/jbr_err_pid%d.log"
|
||||
/** JBR file pattern with extra count parameter (see MAX_WRITE_EXCEPTION) */
|
||||
#define JBR_ERR_PID_FILE_COUNT @"%@/jbr_err_pid%d-%02d.log"
|
||||
|
||||
#define NO_FILE_LINE_FUNCTION_ARGS file:nil line:0 function:nil
|
||||
|
||||
static BOOL sUsingDefaultNIB = YES;
|
||||
static NSString *SHARED_FRAMEWORK_BUNDLE = @"/System/Library/Frameworks/JavaVM.framework";
|
||||
static id <NSApplicationDelegate> applicationDelegate = nil;
|
||||
@@ -123,9 +135,14 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
[NSBundle loadNibFile:defaultNibFile externalNameTable: [NSDictionary dictionaryWithObject:self forKey:@"NSOwner"] withZone:nil];
|
||||
|
||||
// Set user defaults to not try to parse application arguments.
|
||||
NSUserDefaults * defs = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary * noOpenDict = [NSDictionary dictionaryWithObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
|
||||
[defs registerDefaults:noOpenDict];
|
||||
NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
(shouldCrashOnException() ? @"YES" : @"NO"), @"NSApplicationCrashOnExceptions",
|
||||
@"NO", @"NSTreatUnknownArgumentsAsOpen",
|
||||
/* fix for JBR-3127 Modal dialogs invoked from modal or floating dialogs are opened in full screen */
|
||||
@"NO", @"NSWindowAllowsImplicitFullScreen",
|
||||
nil];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||
|
||||
// Fix up the dock icon now that we are registered with CAS and the Dock.
|
||||
[self setDockIconWithEnv:env];
|
||||
@@ -157,9 +174,6 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
|
||||
[super finishLaunching];
|
||||
|
||||
// fix for JBR-3127 Modal dialogs invoked from modal or floating dialogs are opened in full screen
|
||||
[defs setBool:NO forKey:@"NSWindowAllowsImplicitFullScreen"];
|
||||
|
||||
// temporary possibility to load deprecated NSJavaVirtualMachine (just for testing)
|
||||
// todo: remove when completely tested on BigSur
|
||||
// see https://youtrack.jetbrains.com/issue/JBR-3127#focus=Comments-27-4684465.0-0
|
||||
@@ -319,8 +333,6 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
}
|
||||
|
||||
+ (void) runAWTLoopWithApp:(NSApplication*)app {
|
||||
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
||||
|
||||
// Define the special Critical RunLoop mode to ensure action executed ASAP:
|
||||
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[ThreadUtilities criticalRunLoopMode]];
|
||||
|
||||
@@ -328,20 +340,36 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[ThreadUtilities javaRunLoopMode]];
|
||||
|
||||
do {
|
||||
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
||||
@try {
|
||||
[app run];
|
||||
} @catch (NSException* e) {
|
||||
NSLog(@"Apple AWT Startup Exception: %@", [e description]);
|
||||
NSLog(@"Apple AWT Startup Exception callstack: %@", [e callStackSymbols]);
|
||||
NSLog(@"Apple AWT Restarting Native Event Thread");
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Apple AWT runAWTLoop Exception: %@\nCallstack: %@",
|
||||
[exception description], [exception callStackSymbols]);
|
||||
|
||||
[app stop:app];
|
||||
// interrupt the run loop now:
|
||||
[NSApplicationAWT interruptAWTLoop];
|
||||
} @ finally {
|
||||
[pool drain];
|
||||
}
|
||||
} while (YES);
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
/*
|
||||
* Thread-safe:
|
||||
* stop the shared application (run loop [NSApplication run])
|
||||
* and restart it
|
||||
*/
|
||||
+ (void) interruptAWTLoop {
|
||||
NSLog(@"Apple AWT Restarting Native Event Thread...");
|
||||
NSApplication *app = [NSApplicationAWT sharedApplication];
|
||||
// mark the current run loop [NSApplication run] to be stopped:
|
||||
[app stop:app];
|
||||
// send event to interrupt the run loop now (after the current event is processed):
|
||||
[app abortModal];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)usingDefaultNib {
|
||||
return sUsingDefaultNIB;
|
||||
}
|
||||
@@ -380,27 +408,34 @@ untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag {
|
||||
// NSTimeInterval has microseconds precision
|
||||
#define TS_EQUAL(ts1, ts2) (fabs((ts1) - (ts2)) < 1e-6)
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
if ([event type] == NSApplicationDefined
|
||||
&& TS_EQUAL([event timestamp], dummyEventTimestamp)
|
||||
&& (short)[event subtype] == NativeSyncQueueEvent
|
||||
&& [event data1] == NativeSyncQueueEvent
|
||||
&& [event data2] == NativeSyncQueueEvent) {
|
||||
[seenDummyEventLock lockWhenCondition:NO];
|
||||
[seenDummyEventLock unlockWithCondition:YES];
|
||||
} else if ([event type] == NSApplicationDefined
|
||||
&& (short)[event subtype] == ExecuteBlockEvent
|
||||
&& [event data1] != 0 && [event data2] == ExecuteBlockEvent) {
|
||||
void (^block)() = (void (^)()) [event data1];
|
||||
block();
|
||||
[block release];
|
||||
} else if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSCommandKeyMask)) {
|
||||
// Cocoa won't send us key up event when releasing a key while Cmd is down,
|
||||
// so we have to do it ourselves.
|
||||
[[self keyWindow] sendEvent:event];
|
||||
} else {
|
||||
[super sendEvent:event];
|
||||
- (void)sendEvent:(NSEvent *)event {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
@try {
|
||||
if ([event type] == NSApplicationDefined
|
||||
&& TS_EQUAL([event timestamp], dummyEventTimestamp)
|
||||
&& (short)[event subtype] == NativeSyncQueueEvent
|
||||
&& [event data1] == NativeSyncQueueEvent
|
||||
&& [event data2] == NativeSyncQueueEvent) {
|
||||
[seenDummyEventLock lockWhenCondition:NO];
|
||||
[seenDummyEventLock unlockWithCondition:YES];
|
||||
} else if ([event type] == NSApplicationDefined
|
||||
&& (short)[event subtype] == ExecuteBlockEvent
|
||||
&& [event data1] != 0 && [event data2] == ExecuteBlockEvent) {
|
||||
void (^block)() = (void (^)()) [event data1];
|
||||
block();
|
||||
[block release];
|
||||
} else if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSCommandKeyMask)) {
|
||||
// Cocoa won't send us key up event when releasing a key while Cmd is down,
|
||||
// so we have to do it ourselves.
|
||||
[[self keyWindow] sendEvent:event];
|
||||
} else {
|
||||
[super sendEvent:event];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
// report exception to the NSApplicationAWT exception handler:
|
||||
NSAPP_AWT_REPORT_EXCEPTION(exception, NO);
|
||||
} @finally {
|
||||
[pool drain];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,19 +448,23 @@ untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag {
|
||||
{
|
||||
void (^copy)() = [block copy];
|
||||
NSInteger encode = (NSInteger) copy;
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
|
||||
location: NSMakePoint(0,0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: ExecuteBlockEvent
|
||||
data1: encode
|
||||
data2: ExecuteBlockEvent];
|
||||
|
||||
[NSApp postEvent: event atStart: NO];
|
||||
[pool drain];
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
@try {
|
||||
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
|
||||
location: NSMakePoint(0,0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: ExecuteBlockEvent
|
||||
data1: encode
|
||||
data2: ExecuteBlockEvent];
|
||||
|
||||
[NSApp postEvent: event atStart: NO];
|
||||
} @finally {
|
||||
[pool drain];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)postDummyEvent:(bool)useCocoa {
|
||||
@@ -433,86 +472,272 @@ untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag {
|
||||
dummyEventTimestamp = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
|
||||
location: NSMakePoint(0,0)
|
||||
modifierFlags: 0
|
||||
timestamp: dummyEventTimestamp
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: NativeSyncQueueEvent
|
||||
data1: NativeSyncQueueEvent
|
||||
data2: NativeSyncQueueEvent];
|
||||
if (useCocoa) {
|
||||
[NSApp postEvent:event atStart:NO];
|
||||
} else {
|
||||
ProcessSerialNumber psn;
|
||||
GetCurrentProcess(&psn);
|
||||
CGEventPostToPSN(&psn, [event CGEvent]);
|
||||
@try {
|
||||
NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
|
||||
location: NSMakePoint(0,0)
|
||||
modifierFlags: 0
|
||||
timestamp: dummyEventTimestamp
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: NativeSyncQueueEvent
|
||||
data1: NativeSyncQueueEvent
|
||||
data2: NativeSyncQueueEvent];
|
||||
if (useCocoa) {
|
||||
[NSApp postEvent:event atStart:NO];
|
||||
} else {
|
||||
ProcessSerialNumber psn;
|
||||
GetCurrentProcess(&psn);
|
||||
CGEventPostToPSN(&psn, [event CGEvent]);
|
||||
}
|
||||
} @finally {
|
||||
[pool drain];
|
||||
}
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
- (void)waitForDummyEvent:(double)timeout {
|
||||
bool unlock = true;
|
||||
if (timeout >= 0) {
|
||||
double sec = timeout / 1000;
|
||||
unlock = [seenDummyEventLock lockWhenCondition:YES
|
||||
beforeDate:[NSDate dateWithTimeIntervalSinceNow:sec]];
|
||||
} else {
|
||||
[seenDummyEventLock lockWhenCondition:YES];
|
||||
@try {
|
||||
if (timeout >= 0) {
|
||||
double sec = timeout / 1000;
|
||||
unlock = [seenDummyEventLock lockWhenCondition:YES
|
||||
beforeDate:[NSDate dateWithTimeIntervalSinceNow:sec]];
|
||||
} else {
|
||||
[seenDummyEventLock lockWhenCondition:YES];
|
||||
}
|
||||
if (unlock) {
|
||||
[seenDummyEventLock unlock];
|
||||
}
|
||||
} @finally {
|
||||
[seenDummyEventLock release];
|
||||
seenDummyEventLock = nil;
|
||||
}
|
||||
if (unlock) {
|
||||
[seenDummyEventLock unlock];
|
||||
}
|
||||
[seenDummyEventLock release];
|
||||
|
||||
seenDummyEventLock = nil;
|
||||
}
|
||||
|
||||
//Provide info from unhandled ObjectiveC exceptions
|
||||
+ (void)logException:(NSException *)exception forProcess:(NSProcessInfo*)processInfo {
|
||||
// Provide helper methods to get the shared NSApplicationAWT instance
|
||||
+ (BOOL) isNSApplicationAWT {
|
||||
return [NSApp isKindOfClass:[NSApplicationAWT class]];
|
||||
}
|
||||
|
||||
+ (NSApplicationAWT*) sharedApplicationAWT {
|
||||
return [NSApplicationAWT isNSApplicationAWT] ? (NSApplicationAWT*)[NSApplication sharedApplication] : nil;
|
||||
}
|
||||
|
||||
// Provide info from unhandled ObjectiveC exceptions
|
||||
|
||||
/*
|
||||
* Handle all exceptions handled by NSApplication.
|
||||
*
|
||||
* Note: depending on the userDefaults.NSApplicationCrashOnExceptions (YES/ NO) flag,
|
||||
* (set in finishLaunching), calling with crash or not the JVM.
|
||||
*/
|
||||
- (void) reportException:(NSException *)exception {
|
||||
[self reportException:exception uncaught:NO NO_FILE_LINE_FUNCTION_ARGS];
|
||||
}
|
||||
|
||||
- (void) _crashOnException:(NSException *)exception {
|
||||
[self crashOnException:exception uncaught:NO withEnv:[ThreadUtilities getJNIEnvUncached] NO_FILE_LINE_FUNCTION_ARGS];
|
||||
}
|
||||
|
||||
// implementation (internals)
|
||||
- (void) reportException:(NSException *)exception uncaught:(BOOL)uncaught
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
@autoreleasepool {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
if (shouldCrashOnException()) {
|
||||
// calling [super reportException:exception] will cause a crash
|
||||
// so _crashOnException:exception will be then called but losing details.
|
||||
// Use the direct approach (full details):
|
||||
[self crashOnException:exception uncaught:uncaught withEnv:env
|
||||
file:file line:line function:function];
|
||||
} else {
|
||||
NSMutableString *info = [[[NSMutableString alloc] init] autorelease];
|
||||
[NSApplicationAWT logException:exception uncaught:uncaught forProcess:[NSProcessInfo processInfo]
|
||||
withEnv:env prefix:@"Reported " to:info file:file line:line function:function];
|
||||
|
||||
// interrupt the run loop now:
|
||||
[NSApplicationAWT interruptAWTLoop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) crashOnException:(NSException *)exception uncaught:(BOOL)uncaught withEnv:(JNIEnv *)env
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSMutableString *info = [[[NSMutableString alloc] init] autorelease];
|
||||
[info appendString:
|
||||
[NSString stringWithFormat:
|
||||
@"Exception in NSApplicationAWT:\n %@\n",
|
||||
exception]];
|
||||
[NSApplicationAWT logException:exception uncaught:uncaught forProcess:[NSProcessInfo processInfo]
|
||||
withEnv:env prefix:@"Crash: " to:info file:file line:line function:function];
|
||||
|
||||
NSArray<NSString *> *stack = [exception callStackSymbols];
|
||||
if (shouldCrashOnException()) {
|
||||
// Use JNU_Fatal macro to trigger JVM fatal error with the esception information and generate hs_err_ file
|
||||
JNU_Fatal(env, __FILE__, __LINE__, [info UTF8String]); \
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (NSUInteger i = 0; i < stack.count; i++) {
|
||||
[info appendString:stack[i]];
|
||||
[info appendString:@"\n"];
|
||||
// log exception methods
|
||||
+ (void) logException:(NSException *)exception {
|
||||
[NSApplicationAWT logException:exception NO_FILE_LINE_FUNCTION_ARGS];
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
[NSApplicationAWT logException:exception prefix:@"Log: "
|
||||
file:file line:line function:function];
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception prefix:(NSString *)prefix
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
@autoreleasepool {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
NSMutableString *info = [[[NSMutableString alloc] init] autorelease];
|
||||
[NSApplicationAWT logException:exception uncaught:NO forProcess:[NSProcessInfo processInfo]
|
||||
withEnv:env prefix:prefix to:info
|
||||
file:file line:line function:function];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception uncaught:(BOOL)uncaught forProcess:(NSProcessInfo *)processInfo
|
||||
withEnv:(JNIEnv *)env prefix:(NSString *)prefix to:(NSMutableString *)info
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
static int loggedException = 0;
|
||||
static int writtenException = 0;
|
||||
|
||||
if (loggedException < MAX_LOGGED_EXCEPTION) {
|
||||
[NSApplicationAWT toString:exception uncaught:uncaught prefix:prefix to:info
|
||||
file:file line:line function:function];
|
||||
|
||||
[NSApplicationAWT logMessage:info withEnv:env];
|
||||
|
||||
if (++loggedException == MAX_LOGGED_EXCEPTION) {
|
||||
NSLog(@"Stop logging follow-up exceptions (max %d logged exceptions reached)",
|
||||
MAX_LOGGED_EXCEPTION);
|
||||
}
|
||||
|
||||
NSLog(@"%@", info);
|
||||
if (writtenException < MAX_WRITE_EXCEPTION) {
|
||||
const NSString *homePath = [processInfo environment][@"HOME"];
|
||||
if (homePath != nil) {
|
||||
const int processID = [processInfo processIdentifier];
|
||||
const NSString *fileName = [NSString stringWithFormat:JBR_ERR_PID_FILE, homePath, processID];
|
||||
|
||||
int processID = [processInfo processIdentifier];
|
||||
NSDictionary *env = [[NSProcessInfo processInfo] environment];
|
||||
NSString *homePath = env[@"HOME"];
|
||||
if (homePath != nil) {
|
||||
NSString *fileName =
|
||||
[NSString stringWithFormat:@"%@/jbr_err_pid%d.log",
|
||||
homePath, processID];
|
||||
BOOL available = NO;
|
||||
int retry = 0;
|
||||
NSString *realFileName = nil;
|
||||
int count = 0;
|
||||
do {
|
||||
realFileName = (count == 0) ? fileName :
|
||||
[NSString stringWithFormat:JBR_ERR_PID_FILE_COUNT, homePath, processID, count];
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:fileName]) {
|
||||
[info writeToFile:fileName
|
||||
atomically:YES
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:NULL];
|
||||
available = ![[NSFileManager defaultManager] fileExistsAtPath:realFileName];
|
||||
|
||||
if (available) {
|
||||
NSLog(@"Writing exception to '%@'...", realFileName);
|
||||
// write the exception atomatically to the file:
|
||||
if ([info writeToFile:realFileName
|
||||
atomically:YES
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:NULL])
|
||||
{
|
||||
if (++writtenException == MAX_WRITE_EXCEPTION) {
|
||||
NSLog(@"Stop writing follow-up exceptions (max %d written exceptions reached)",
|
||||
MAX_WRITE_EXCEPTION);
|
||||
}
|
||||
} else {
|
||||
if (++retry >= MAX_RETRY_WRITE_EXCEPTION) {
|
||||
NSLog(@"Failed to write exception to '%@' after %d retries", realFileName,
|
||||
MAX_RETRY_WRITE_EXCEPTION);
|
||||
break;
|
||||
}
|
||||
// retry few times:
|
||||
available = NO;
|
||||
}
|
||||
}
|
||||
count++;
|
||||
} while (!available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_crashOnException:(NSException *)exception {
|
||||
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
|
||||
[NSApplicationAWT logException:exception
|
||||
forProcess:processInfo];
|
||||
// Use SIGILL to generate hs_err_ file as well
|
||||
kill([processInfo processIdentifier], SIGILL);
|
||||
+ (void) logMessage:(NSString *)message {
|
||||
[NSApplicationAWT logMessage:message NO_FILE_LINE_FUNCTION_ARGS];
|
||||
}
|
||||
|
||||
+ (void) logMessage:(NSString *)message
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
@autoreleasepool {
|
||||
[NSApplicationAWT logMessage:message
|
||||
callStackSymbols:[NSThread callStackSymbols]
|
||||
withEnv:[ThreadUtilities getJNIEnvUncached]
|
||||
file:file line:line function:function];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) logMessage:(NSString *)message callStackSymbols:(NSArray<NSString *> *)callStackSymbols withEnv:(JNIEnv *)env
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
NSMutableString *info = [[[NSMutableString alloc] init] autorelease];
|
||||
[NSApplicationAWT toString:message callStackSymbols:callStackSymbols to:info
|
||||
file:file line:line function:function];
|
||||
|
||||
[NSApplicationAWT logMessage:info withEnv:env];
|
||||
}
|
||||
|
||||
+ (void) logMessage:(NSString *)info withEnv:(JNIEnv *)env {
|
||||
// Always log to the console first:
|
||||
NSLog(@"%@", info);
|
||||
// Send to PlatformLogger too:
|
||||
lwc_plog(env, "%s", info.UTF8String);
|
||||
}
|
||||
|
||||
+ (void) toString:(NSException *)exception uncaught:(BOOL)uncaught prefix:(NSString *)prefix to:(NSMutableString *)info
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
const char* uncaughtMark = (uncaught) ? "Uncaught " : "";
|
||||
|
||||
NSString *header = (file != nil) ?
|
||||
[NSString stringWithFormat: @"%@%sException (%s:%d %s):\n%@\n", prefix, uncaughtMark, file, line, function, exception]
|
||||
: [NSString stringWithFormat: @"%@%sException (unknown):\n%@\n", prefix, uncaughtMark, exception];
|
||||
[info appendString:header];
|
||||
|
||||
[NSApplicationAWT toString:[exception callStackSymbols] to:info];
|
||||
}
|
||||
|
||||
+ (void) toString:(NSString *)message callStackSymbols:(NSArray<NSString *> *)callStackSymbols to:(NSMutableString *)info
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
NSString *header = (file != nil) ?
|
||||
[NSString stringWithFormat: @"%@ (%s:%d %s):\n", message, file, line, function]
|
||||
: [NSString stringWithFormat: @"%@ (unknown):\n", message];
|
||||
[info appendString:header];
|
||||
|
||||
[NSApplicationAWT toString:callStackSymbols to:info];
|
||||
}
|
||||
|
||||
+ (void) toString:(NSArray<NSString *> *)callStackSymbols to:(NSMutableString *)info
|
||||
{
|
||||
for (NSUInteger i = 0; i < callStackSymbols.count; i++) {
|
||||
[info appendFormat:@" %@\n", callStackSymbols[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// --- NSExceptionHandlerDelegate category to handle all exceptions ---
|
||||
- (BOOL) exceptionHandler:(NSExceptionHandler *)sender
|
||||
shouldHandleException:(NSException *)exception mask:(NSUInteger)aMask {
|
||||
/* handle all exception types to invoke reportException:exception */
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) exceptionHandler:(NSExceptionHandler *)sender
|
||||
shouldLogException:(NSException *)exception mask:(NSUInteger)aMask {
|
||||
/* disable default logging (stderr) */
|
||||
return NO;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -130,6 +130,30 @@ do { \
|
||||
/* bit flag to coalesce CGDisplayReconfigureCallbacks */
|
||||
#define MAIN_CALLBACK_CGDISPLAY_RECONFIGURE 1
|
||||
|
||||
/* log given message (with thread call stack) */
|
||||
#define NSAPP_AWT_LOG_MESSAGE(message) \
|
||||
[ThreadUtilities logMessage:message file:__FILE__ line:__LINE__ function:__FUNCTION__]
|
||||
|
||||
/* log given exception (ignored or explicitely muted) */
|
||||
#define NSAPP_AWT_LOG_EXCEPTION(exception) \
|
||||
[ThreadUtilities logException:exception file:__FILE__ line:__LINE__ function:__FUNCTION__]
|
||||
|
||||
#define NSAPP_AWT_LOG_EXCEPTION_PREFIX(exception, prefixValue) \
|
||||
[ThreadUtilities logException:exception prefix:prefixValue file:__FILE__ line:__LINE__ function:__FUNCTION__]
|
||||
|
||||
/* report the given exception (ignored or explicitely muted), may crash the JVM (JNU_Fatal) */
|
||||
#define NSAPP_AWT_REPORT_EXCEPTION(exception, uncaughtFlag) \
|
||||
[ThreadUtilities reportException:exception uncaught:uncaughtFlag file:__FILE__ line:__LINE__ function:__FUNCTION__]
|
||||
|
||||
/* CFRelease wrapper that ignores NULL argument */
|
||||
JNIEXPORT void CFRelease_even_NULL(CFTypeRef cf);
|
||||
|
||||
/* Return true if uncaught exceptions should crash JVM (JNU_Fatal) */
|
||||
BOOL shouldCrashOnException();
|
||||
|
||||
/* Get AWT's NSUncaughtExceptionHandler */
|
||||
JNIEXPORT NSUncaughtExceptionHandler* GetAWTUncaughtExceptionHandler(void);
|
||||
|
||||
@interface RunLoopCallbackQueue : NSObject
|
||||
|
||||
@property(readwrite, atomic) u_long coalesingflags;
|
||||
@@ -180,8 +204,25 @@ __attribute__((visibility("default")))
|
||||
*/
|
||||
@property (class, nonatomic, readonly) BOOL blockingEventDispatchThread;
|
||||
|
||||
+ (void (^)()) GetEmptyBlock;
|
||||
|
||||
+ (void) reportException:(NSException *)exception;
|
||||
+ (void) reportException:(NSException *)exception uncaught:(BOOL)uncaught
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
+ (void) logException:(NSException *)exception;
|
||||
+ (void) logException:(NSException *)exception
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
+ (void) logException:(NSException *)exception prefix:(NSString *)prefix
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
+ (void) logMessage:(NSString *)message;
|
||||
+ (void) logMessage:(NSString *)message
|
||||
file:(const char*)file line:(int)line function:(const char*)function;
|
||||
|
||||
+ (JNIEnv*)getJNIEnv;
|
||||
+ (JNIEnv*)getJNIEnvUncached;
|
||||
|
||||
+ (void)detachCurrentThread;
|
||||
+ (void)setAppkitThreadGroup:(jobject)group;
|
||||
+ (void)setApplicationOwner:(BOOL)owner;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#import "JNIUtilities.h"
|
||||
#import "PropertiesUtilities.h"
|
||||
#import "ThreadUtilities.h"
|
||||
#import "NSApplicationAWT.h"
|
||||
|
||||
|
||||
#define RUN_BLOCK_IF(COND, block) \
|
||||
@@ -40,6 +41,45 @@
|
||||
/* See LWCToolkit.APPKIT_THREAD_NAME */
|
||||
#define MAIN_THREAD_NAME "AppKit Thread"
|
||||
|
||||
void CFRelease_even_NULL(CFTypeRef cf) {
|
||||
if (cf != NULL) {
|
||||
CFRelease(cf);
|
||||
}
|
||||
}
|
||||
|
||||
// Global CrashOnException handling flags used by:
|
||||
// - UncaughtExceptionHandler
|
||||
// - NSApplicationAWT _crashOnException:(NSException *)exception
|
||||
static BOOL isAWTCrashOnException() {
|
||||
static int awtCrashOnException = -1;
|
||||
if (awtCrashOnException == -1) {
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
|
||||
if (env == NULL) return NO;
|
||||
NSString* awtCrashOnExceptionProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.crashOnException"
|
||||
withEnv:env];
|
||||
awtCrashOnException = [@"true" isCaseInsensitiveLike:awtCrashOnExceptionProp] ? YES : NO;
|
||||
}
|
||||
return (BOOL)awtCrashOnException;
|
||||
}
|
||||
|
||||
BOOL shouldCrashOnException() {
|
||||
static int crashOnException = -1;
|
||||
if (crashOnException == -1) {
|
||||
BOOL shouldCrashOnException = NO;
|
||||
#if defined(DEBUG)
|
||||
shouldCrashOnException = YES;
|
||||
#endif
|
||||
if (!shouldCrashOnException) {
|
||||
shouldCrashOnException = isAWTCrashOnException();
|
||||
}
|
||||
crashOnException = shouldCrashOnException;
|
||||
if (crashOnException) {
|
||||
NSLog(@"WARNING: shouldCrashOnException ENABLED");
|
||||
}
|
||||
}
|
||||
return (BOOL)crashOnException;
|
||||
}
|
||||
|
||||
/* Returns the MainThread latency threshold in milliseconds
|
||||
* used to detect slow operations that may cause high latencies or delays.
|
||||
* If negative, the MainThread monitor is disabled */
|
||||
@@ -89,6 +129,24 @@ static const int TRACE_BLOCKING_FLAGS = 0;
|
||||
/* RunLoop traceability identifier generators */
|
||||
static atomic_long mainThreadActionId = 0L;
|
||||
|
||||
// --- AWT's NSUncaughtExceptionHandler ---
|
||||
static void AWT_NSUncaughtExceptionHandler(NSException *exception) {
|
||||
// report exception to the NSApplicationAWT exception handler:
|
||||
NSAPP_AWT_REPORT_EXCEPTION(exception, YES);
|
||||
}
|
||||
|
||||
/* Get AWT's NSUncaughtExceptionHandler */
|
||||
NSUncaughtExceptionHandler* GetAWTUncaughtExceptionHandler(void) {
|
||||
static NSUncaughtExceptionHandler* _awtUncaughtExceptionHandler;
|
||||
static dispatch_once_t oncePredicate;
|
||||
|
||||
dispatch_once(&oncePredicate, ^{
|
||||
_awtUncaughtExceptionHandler = AWT_NSUncaughtExceptionHandler;
|
||||
NSSetUncaughtExceptionHandler(_awtUncaughtExceptionHandler);
|
||||
});
|
||||
return _awtUncaughtExceptionHandler;
|
||||
}
|
||||
|
||||
static inline void doLog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
if (enableTracingNSLog) {
|
||||
va_list args;
|
||||
@@ -131,6 +189,78 @@ static long eventDispatchThreadPtr = (long)nil;
|
||||
static BOOL _blockingEventDispatchThread = NO;
|
||||
static BOOL _blockingMainThread = NO;
|
||||
|
||||
// Empty block optimization using 1 single instance and avoid Block_copy macro
|
||||
|
||||
+ (void (^)()) GetEmptyBlock {
|
||||
static id _emptyBlock = nil;
|
||||
if (_emptyBlock == nil) {
|
||||
_emptyBlock = ^(){};
|
||||
}
|
||||
return _emptyBlock;
|
||||
}
|
||||
|
||||
+ (BOOL) IsEmptyBlock:(void (^)())block {
|
||||
return (block == [ThreadUtilities GetEmptyBlock]);
|
||||
}
|
||||
|
||||
#define Custom_Block_copy(block) ( ([ThreadUtilities IsEmptyBlock:block]) ? block : Block_copy(block) )
|
||||
|
||||
/*
|
||||
* When running a block where either we don't wait, or it needs to run on another thread
|
||||
* we need to copy it from stack to heap, use the copy in the call and release after use.
|
||||
* Do this only when we must because it could be expensive.
|
||||
* Note : if waiting cross-thread, possibly the stack allocated copy is accessible ?
|
||||
*/
|
||||
+ (void)invokeBlockCopy:(void (^)(void))blockCopy {
|
||||
if (![ThreadUtilities IsEmptyBlock:blockCopy]) {
|
||||
@try {
|
||||
blockCopy();
|
||||
} @finally {
|
||||
Block_release(blockCopy);
|
||||
#if (CHECK_PENDING_EXCEPTION == 1)
|
||||
__JNI_CHECK_PENDING_EXCEPTION([ThreadUtilities getJNIEnvUncached]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exception handling bridge
|
||||
+ (void) reportException:(NSException *)exception {
|
||||
[[NSApplicationAWT sharedApplicationAWT] reportException:exception];
|
||||
}
|
||||
|
||||
+ (void) reportException:(NSException *)exception uncaught:(BOOL)uncaught
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
[[NSApplicationAWT sharedApplicationAWT] reportException:exception uncaught:uncaught file:file line:line function:function];
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception {
|
||||
[NSApplicationAWT logException:exception];
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
[NSApplicationAWT logException:exception file:file line:line function:function];
|
||||
}
|
||||
|
||||
+ (void) logException:(NSException *)exception prefix:(NSString *)prefix
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
[NSApplicationAWT logException:exception prefix:prefix file:file line:line function:function];
|
||||
}
|
||||
|
||||
+ (void) logMessage:(NSString *)message {
|
||||
[NSApplicationAWT logMessage:message];
|
||||
}
|
||||
|
||||
+ (void) logMessage:(NSString *)message
|
||||
file:(const char*)file line:(int)line function:(const char*)function
|
||||
{
|
||||
[NSApplicationAWT logMessage:message file:file line:line function:function];
|
||||
}
|
||||
|
||||
static BOOL isEventDispatchThread() {
|
||||
return (long)[NSThread currentThread] == eventDispatchThreadPtr;
|
||||
}
|
||||
@@ -195,9 +325,13 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
}
|
||||
|
||||
+ (JNIEnv*)getJNIEnvUncached {
|
||||
JNIEnv *env = NULL;
|
||||
attachCurrentThread((void **)&env);
|
||||
return env;
|
||||
if ([NSThread isMainThread] && (appKitEnv != NULL)) {
|
||||
return appKitEnv;
|
||||
} else {
|
||||
JNIEnv *env = NULL;
|
||||
attachCurrentThread((void **)&env);
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)detachCurrentThread {
|
||||
@@ -245,17 +379,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
[ThreadUtilities resetTraceContext];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When running a block where either we don't wait, or it needs to run on another thread
|
||||
* we need to copy it from stack to heap, use the copy in the call and release after use.
|
||||
* Do this only when we must because it could be expensive.
|
||||
* Note : if waiting cross-thread, possibly the stack allocated copy is accessible ?
|
||||
*/
|
||||
+ (void)invokeBlockCopy:(void (^)(void))blockCopy {
|
||||
blockCopy();
|
||||
Block_release(blockCopy);
|
||||
doLog([ThreadUtilities getJNIEnvUncached], "setAppkitThreadGroup: exit");
|
||||
}
|
||||
|
||||
+ (NSString*)getCaller:(NSString*)prefixSymbol {
|
||||
@@ -299,7 +423,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
{
|
||||
RUN_BLOCK_IF([NSThread isMainThread], block);
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Custom_Block_copy(block)
|
||||
waitUntilDone:NO useJavaModes:useJavaModes];
|
||||
}
|
||||
|
||||
@@ -321,7 +445,7 @@ AWT_ASSERT_APPKIT_THREAD;
|
||||
{
|
||||
RUN_BLOCK_IF([NSThread isMainThread] && wait, block);
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Block_copy(block)
|
||||
[ThreadUtilities performOnMainThread:@selector(invokeBlockCopy:) on:self withObject:Custom_Block_copy(block)
|
||||
waitUntilDone:wait useJavaModes:useJavaModes];
|
||||
}
|
||||
|
||||
@@ -691,7 +815,7 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
if (midWarn != NULL) {
|
||||
va_list args;
|
||||
va_start(args, formatMsg);
|
||||
/* formatted message can be large (stack trace ?) => 16 kb */
|
||||
/* formatted message can be large (stack trace) => 16 kb buffer size */
|
||||
const int bufSize = 16 * 1024;
|
||||
char buf[bufSize];
|
||||
#pragma clang diagnostic push
|
||||
@@ -700,17 +824,29 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
#pragma clang diagnostic pop
|
||||
va_end(args);
|
||||
|
||||
BOOL logged = NO;
|
||||
const jstring javaString = (*env)->NewStringUTF(env, buf);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// fallback:
|
||||
NSLog(@"%s\n", buf); \
|
||||
} else {
|
||||
JNU_CHECK_EXCEPTION(env);
|
||||
|
||||
if ((javaString != NULL) && (*env)->ExceptionCheck(env) == JNI_FALSE) {
|
||||
// call PlatformLogger.warning(javaString):
|
||||
(*env)->CallVoidMethod(env, loggerObject, midWarn, javaString);
|
||||
CHECK_EXCEPTION();
|
||||
return;
|
||||
|
||||
// derived from CHECK_EXCEPTION but NO NSException raised:
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// fallback:
|
||||
(*(env))->ExceptionDescribe(env);
|
||||
// note: [NSApplicationAWT logMessage:message] is not used
|
||||
// to avoid reentrancy issues.
|
||||
NSLog(@"%@", [NSThread callStackSymbols]);
|
||||
} else {
|
||||
logged = YES;
|
||||
}
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, javaString);
|
||||
if (!logged) {
|
||||
// fallback:
|
||||
NSLog(@"%s\n", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,10 +915,22 @@ JNIEXPORT void lwc_plog(JNIEnv* env, const char *formatMsg, ...) {
|
||||
- (void)processQueuedCallbacks {
|
||||
const NSUInteger count = [self.queue count];
|
||||
if (count != 0) {
|
||||
#if (CHECK_PENDING_EXCEPTION == 1)
|
||||
JNIEnv *env = [ThreadUtilities getJNIEnv];
|
||||
#endif
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
void (^blockCopy)(void) = (void (^)())[self.queue objectAtIndex: i];
|
||||
// invoke callback:
|
||||
[ThreadUtilities invokeBlockCopy:blockCopy];
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
@try {
|
||||
void (^blockCopy)(void) = (void (^)()) [self.queue objectAtIndex:i];
|
||||
// invoke callback:
|
||||
[ThreadUtilities invokeBlockCopy:blockCopy];
|
||||
} @catch (NSException *exception) {
|
||||
// report exception to the NSApplicationAWT exception handler:
|
||||
NSAPP_AWT_REPORT_EXCEPTION(exception, NO);
|
||||
} @finally {
|
||||
__JNI_CHECK_PENDING_EXCEPTION(env);
|
||||
[pool drain];
|
||||
}
|
||||
}
|
||||
// reset queue anyway:
|
||||
[self reset];
|
||||
|
||||
Reference in New Issue
Block a user