Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qioseventdispatcher.mm
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7#include "qiosglobal.h"
8
9#if defined(Q_OS_VISIONOS)
10#include "qiosswiftintegration.h"
11#endif
12
13#include <QtCore/qprocessordetection.h>
14#include <QtCore/private/qcoreapplication_p.h>
15#include <QtCore/private/qsystemerror_p.h>
16#include <QtCore/private/qthread_p.h>
17
18#include <qpa/qwindowsysteminterface.h>
19
20#import <Foundation/NSArray.h>
21#import <Foundation/NSString.h>
22#import <Foundation/NSProcessInfo.h>
23#import <Foundation/NSThread.h>
24#import <Foundation/NSNotification.h>
25
26#import <UIKit/UIApplication.h>
27
28#include <setjmp.h> // Here be dragons
29
30#include <sys/mman.h>
31
32#define qAlignDown(val, align) val & ~(align - 1)
33#define qAlignUp(val, align) qAlignDown(val + (align - 1), align)
34
35static const size_t kBytesPerKiloByte = 1024;
36static const long kPageSize = sysconf(_SC_PAGESIZE);
37
38using namespace QT_PREPEND_NAMESPACE(QtPrivate);
39
40/*
41 The following diagram shows the layout of the reserved
42 stack in relation to the regular stack, and the basic
43 flow of the initial startup sequence. Note how we end
44 up back in applicationDidLaunch after the user's main
45 recurses into qApp-exec(), which allows us to return
46 from applicationDidLaunch and spin the run-loop at the
47 same level (UIApplicationMain) as iOS nativly does.
48
49 +-----------------------------+
50 | qtmn() |
51 | +--------------------+ <-- base
52 | +----> main() | |
53 | | +--------------------+ |
54 | | | ... | |
55 | | +--------------------+ |
56 | | | qApp->exec() | |
57 | | +--------------------+ |
58 | | | processEvents() | |
59 | | | | |
60 | | +--+ longjmp(a) | |
61 | | | | | |
62 | | | +--------------------+ |
63 | | | | | |
64 | | | | | |
65 | | | | unused | |
66 | | | | | |
67 | | | | | |
68 | | | +--------------------+ <-- limit
69 | | | | memory guard | |
70 | | | +--------------------+ <-- reservedStack
71 +-|-|-------------------------+
72 | | | UIApplicationMain() |
73 +-|-|-------------------------+
74 | | | applicationDidLaunch() |
75 | | | |
76 | | +--> setjmp(a) |
77 | +----+ trampoline() |
78 | |
79 +-----------------------------+
80
81 Note: the diagram does not reflect alignment issues.
82*/
83
84namespace
85{
86 rlimit stackLimit = {0, 0};
87 rlim_t originalStackSize = 0;
88
89 struct Stack
90 {
91 uintptr_t base;
92 uintptr_t limit;
93
94 static size_t computeSize(size_t requestedSize)
95 {
96 if (!requestedSize)
97 return 0;
98
99 // The stack size must be a multiple of 4 KB
100 size_t stackSize = qAlignUp(requestedSize, 4 * kBytesPerKiloByte);
101
102 // Be at least 16 KB
103 stackSize = qMax(16 * kBytesPerKiloByte, stackSize);
104
105 // Have enough extra space for our (aligned) memory guard
106 stackSize += (2 * kPageSize);
107
108 // But not exceed the 1MB maximum (adjusted to account for current stack usage)
109 stackSize = qMin(stackSize, ((1024 - 64) * kBytesPerKiloByte));
110
111 // Which we verify, just in case
112 if (Q_UNLIKELY(stackSize > originalStackSize))
113 qFatal("Unexpectedly exceeded stack limit");
114
115 return stackSize;
116 }
117
118 void adopt(void* memory, size_t size)
119 {
120 uintptr_t memoryStart = uintptr_t(memory);
121
122 // Add memory guard at the end of the reserved stack, so that any stack
123 // overflow during the user's main will trigger an exception at that point,
124 // and not when we return and find that the current stack has been smashed.
125 // We allow read though, so that garbage-collection can pass through our
126 // stack in its mark phase without triggering access violations.
127 uintptr_t memoryGuardStart = qAlignUp(memoryStart, kPageSize);
128 if (mprotect((void*)memoryGuardStart, kPageSize, PROT_READ))
129 qWarning() << "Failed to add memory guard:" << strerror(errno);
130
131 // We don't consider the memory guard part of the usable stack space
132 limit = memoryGuardStart + kPageSize;
133
134 // The stack grows downwards in memory, so the stack base is actually
135 // at the end of the reserved stack space. And, as the memory guard
136 // was page aligned, we need to align down the base as well, to
137 // keep the requirement that the stack size is a multiple of 4K.
138 base = qAlignDown(memoryStart + size, kPageSize);
139 }
140
141 bool isValid()
142 {
143 return base && limit;
144 }
145
146 size_t size()
147 {
148 return base - limit;
149 }
150
151 static const int kScribblePattern;
152
153 void scribble()
154 {
155 memset_pattern4((void*)limit, &kScribblePattern, size());
156 }
157
158 void printUsage()
159 {
160 uintptr_t highWaterMark = limit;
161 for (; highWaterMark < base; highWaterMark += 4) {
162 if (memcmp((void*)highWaterMark, &kScribblePattern, 4))
163 break;
164 }
165
166 qDebug("main() used roughly %lu bytes of stack space", (base - highWaterMark));
167 }
168 };
169
170 const int Stack::kScribblePattern = 0xfafafafa;
171
172 Stack userMainStack;
173
174 jmp_buf processEventEnterJumpPoint;
175 jmp_buf processEventExitJumpPoint;
176
177 bool applicationAboutToTerminate = false;
178 jmp_buf applicationWillTerminateJumpPoint;
179
180 bool debugStackUsage = false;
181
182 struct {
183 QAppleLogActivity UIApplicationMain;
184 QAppleLogActivity applicationDidFinishLaunching;
185 } logActivity;
186
187 static bool s_isQtApplication = false;
188
189 void updateStackLimit()
190 {
191 qCDebug(lcEventDispatcher) << "Updating RLIMIT_STACK soft limit from"
192 << originalStackSize << "to" << userMainStack.size();
193
194 stackLimit.rlim_cur = userMainStack.size();
195 if (setrlimit(RLIMIT_STACK, &stackLimit) != 0) {
196 qCWarning(lcEventDispatcher) << "Failed to update RLIMIT_STACK soft limit"
197 << QSystemError::stdString();
198 }
199 }
200
201 void restoreStackLimit()
202 {
203 qCDebug(lcEventDispatcher) << "Restoring RLIMIT_STACK soft limit from"
204 << stackLimit.rlim_cur << "back to" << originalStackSize;
205
206 stackLimit.rlim_cur = originalStackSize;
207 if (setrlimit(RLIMIT_STACK, &stackLimit) != 0) {
208 qCWarning(lcEventDispatcher) << "Failed to update RLIMIT_STACK soft limit"
209 << QSystemError::stdString();
210 }
211 }
212}
213
214extern "C" int qt_main_wrapper(int argc, char *argv[])
215{
216 s_isQtApplication = true;
217
218 @autoreleasepool {
219 if (Q_UNLIKELY(getrlimit(RLIMIT_STACK, &stackLimit) != 0))
220 qFatal("Failed to get stack limits");
221
222 originalStackSize = stackLimit.rlim_cur;
223
224 size_t defaultStackSize = 512 * kBytesPerKiloByte; // Same as secondary threads
225
226 uint requestedStackSize = qMax(0, infoPlistValue(@"QtRunLoopIntegrationStackSize", defaultStackSize));
227
228 if (infoPlistValue(@"QtRunLoopIntegrationDisableSeparateStack", false))
229 requestedStackSize = 0;
230
231 QT_WARNING_PUSH
232#if Q_CC_CLANG >= 1800
233 QT_WARNING_DISABLE_CLANG("-Wvla-cxx-extension")
234#endif
235 // The user-main stack _must_ live on the stack, so that the stack pointer
236 // during user-main is within pthread_get_stackaddr_np/pthread_get_stacksize_np.
237 char reservedStack[Stack::computeSize(requestedStackSize)];
238 QT_WARNING_POP
239
240 if (sizeof(reservedStack) > 0) {
241 userMainStack.adopt(reservedStack, sizeof(reservedStack));
242
243 if (infoPlistValue(@"QtRunLoopIntegrationDebugStackUsage", false)) {
244 debugStackUsage = true;
245 userMainStack.scribble();
246 qDebug("Effective stack size is %lu bytes", userMainStack.size());
247 }
248 }
249
250 logActivity.UIApplicationMain = QT_APPLE_LOG_ACTIVITY(
251 lcEventDispatcher().isDebugEnabled(), "UIApplicationMain").enter();
252
253#if defined(Q_OS_VISIONOS)
254 Q_UNUSED(argc);
255 Q_UNUSED(argv);
256 qCDebug(lcEventDispatcher) << "Starting Swift app";
257 QIOSIntegrationPluginSwift::runSwiftAppMain();
258 Q_UNREACHABLE();
259#else
260 qCDebug(lcEventDispatcher) << "Running UIApplicationMain";
261 return UIApplicationMain(argc, argv, nil, NSStringFromClass([QIOSApplicationDelegate class]));
262#endif
263 }
264}
265
273
274extern "C" int main(int argc, char *argv[]);
275
276static void __attribute__((noinline, noreturn)) user_main_trampoline()
277{
278 NSArray<NSString *> *arguments = [[NSProcessInfo processInfo] arguments];
279 int argc = arguments.count;
280 char **argv = new char*[argc];
281
282 for (int i = 0; i < argc; ++i) {
283 NSString *arg = [arguments objectAtIndex:i];
284
285 NSStringEncoding cStringEncoding = [NSString defaultCStringEncoding];
286 unsigned int bufferSize = [arg lengthOfBytesUsingEncoding:cStringEncoding] + 1;
287 argv[i] = reinterpret_cast<char *>(malloc(bufferSize));
288
289 if (Q_UNLIKELY(![arg getCString:argv[i] maxLength:bufferSize encoding:cStringEncoding]))
290 qFatal("Could not convert argv[%d] to C string", i);
291 }
292
293 updateStackLimit();
294
295 int exitCode = main(argc, argv);
296 delete[] argv;
297
298 logActivity.applicationDidFinishLaunching.enter();
299 qCDebug(lcEventDispatcher) << "Returned from main with exit code " << exitCode;
300
301 if (Q_UNLIKELY(debugStackUsage))
302 userMainStack.printUsage();
303
304 restoreStackLimit();
305
306 logActivity.applicationDidFinishLaunching.leave();
307
308 if (applicationAboutToTerminate)
309 longjmp(applicationWillTerminateJumpPoint, kJumpedFromUserMainTrampoline);
310
311 // We end up here if the user's main() never calls QApplication::exec(),
312 // or if we return from exec() after quitting the application. If so we
313 // follow the expected behavior from the point of the user's main(), which
314 // is to exit with the given exit code.
315 exit(exitCode);
316}
317
318// If we don't have a stack set up, we're not running inside
319// iOS' native/root level run-loop in UIApplicationMain.
321{
322 return userMainStack.isValid();
323}
324
325@interface QIOSApplicationStateTracker : NSObject
326@end
327
328@implementation QIOSApplicationStateTracker
329
330+ (void)load
331{
332 [[NSNotificationCenter defaultCenter]
333 addObserver:self
334 selector:@selector(applicationDidFinishLaunching:)
335 name:UIApplicationDidFinishLaunchingNotification
336 object:nil];
337
338 [[NSNotificationCenter defaultCenter]
339 addObserver:self
340 selector:@selector(applicationWillTerminate)
341 name:UIApplicationWillTerminateNotification
342 object:nil];
343}
344
345#if defined(Q_PROCESSOR_X86)
346# define FUNCTION_CALL_ALIGNMENT 16
347# if defined(Q_PROCESSOR_X86_32)
348# define SET_STACK_POINTER "mov %0, %%esp"
349# elif defined(Q_PROCESSOR_X86_64)
350# define SET_STACK_POINTER "movq %0, %%rsp"
351# endif
352#elif defined(Q_PROCESSOR_ARM)
353# // Valid for both 32 and 64-bit ARM
354# define FUNCTION_CALL_ALIGNMENT 4
355# define SET_STACK_POINTER "mov sp, %0"
356#else
357# error "Unknown processor family"
358#endif
359
360+ (void)applicationDidFinishLaunching:(NSNotification *)notification
361{
362 logActivity.applicationDidFinishLaunching = QT_APPLE_LOG_ACTIVITY_WITH_PARENT(
363 lcEventDispatcher().isDebugEnabled(), "applicationDidFinishLaunching", logActivity.UIApplicationMain).enter();
364
365 qCDebug(lcEventDispatcher) << "Application launched with options" << notification.userInfo;
366
367 if (!isQtApplication())
368 return;
369
370 if (!rootLevelRunLoopIntegration()) {
371 // We schedule the main-redirection for the next run-loop pass, so that we
372 // can return from this function and let UIApplicationMain finish its job.
373 // This results in running Qt's application eventloop as a nested runloop.
374 qCDebug(lcEventDispatcher) << "Scheduling main() on next run-loop pass";
375 CFRunLoopTimerRef userMainTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
376 CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef) { user_main_trampoline(); });
377 CFRunLoopAddTimer(CFRunLoopGetMain(), userMainTimer, kCFRunLoopCommonModes);
378 CFRelease(userMainTimer);
379 return;
380 }
381
382 switch (setjmp(processEventEnterJumpPoint)) {
383 case kJumpPointSetSuccessfully: {
384 qCDebug(lcEventDispatcher) << "Running main() on separate stack";
385 QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "main()");
386
387 // Redirect the stack pointer to the start of the reserved stack. This ensures
388 // that when we longjmp out of the event dispatcher and continue execution, the
389 // 'Qt main' call-stack will not be smashed, as it lives in a part of the stack
390 // that was allocated back in main().
391 __asm__ __volatile__(
392 SET_STACK_POINTER
393 : /* no outputs */
394 : "r" (qAlignDown(userMainStack.base, FUNCTION_CALL_ALIGNMENT))
395 );
396
397 user_main_trampoline();
398
399 Q_UNREACHABLE();
400 break;
401 }
402 case kJumpedFromEventDispatcherProcessEvents:
403 // We've returned from the longjmp in the event dispatcher,
404 // and the stack has been restored to its old self.
405 logActivity.UIApplicationMain.enter();
406 qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to exec";
407
408 if (Q_UNLIKELY(debugStackUsage))
409 userMainStack.printUsage();
410
411 restoreStackLimit();
412
413 break;
414 default:
415 qFatal("Unexpected jump result in event loop integration");
416 }
417}
418
419// We treat applicationWillTerminate as SIGTERM, even if it can't be ignored,
420// and follow the bash convention of encoding the signal number in the upper
421// four bits of the exit code (exit(3) will only pass on the lower 8 bits).
422static const char kApplicationWillTerminateExitCode = char(SIGTERM | 0x80);
423
424+ (void)applicationWillTerminate
425{
426 QAppleLogActivity applicationWillTerminateActivity = QT_APPLE_LOG_ACTIVITY_WITH_PARENT(
427 lcEventDispatcher().isDebugEnabled(), "applicationWillTerminate", logActivity.UIApplicationMain).enter();
428 qCDebug(lcEventDispatcher) << "Application about to be terminated by iOS";
429
430 if (!isQtApplication())
431 return;
432
433 if (!rootLevelRunLoopIntegration())
434 return;
435
436 // Normally iOS just sends SIGKILL to quit applications, in which case there's
437 // no chance for us to clean up anything, but in some rare cases iOS will tell
438 // us that the application is about to be terminated.
439
440 // We try to play nice with Qt by ending the main event loop, which will result
441 // in QCoreApplication::aboutToQuit() being emitted, and main() returning to the
442 // trampoline. The trampoline then redirects us back here, so that we can return
443 // to UIApplicationMain instead of calling exit().
444
445 applicationAboutToTerminate = true;
446 switch (setjmp(applicationWillTerminateJumpPoint)) {
447 case kJumpPointSetSuccessfully:
448 qCDebug(lcEventDispatcher) << "Exiting qApp with SIGTERM exit code";
449 qApp->exit(kApplicationWillTerminateExitCode);
450
451 // The runloop will not exit when the application is about to terminate,
452 // so we'll never see the exit activity and have a chance to return from
453 // QEventLoop::exec(). We initiate the return manually as a workaround.
454 qCDebug(lcEventDispatcher) << "Manually triggering return from event loop exec";
455 applicationWillTerminateActivity.leave();
456 static_cast<QIOSJumpingEventDispatcher *>(qApp->eventDispatcher())->interruptEventLoopExec();
457 break;
458 case kJumpedFromUserMainTrampoline:
459 applicationWillTerminateActivity.enter();
460 // The user's main has returned, so we're ready to let iOS terminate the application
461 qCDebug(lcEventDispatcher) << "kJumpedFromUserMainTrampoline, allowing iOS to terminate";
462 applicationWillTerminateActivity.leave();
463 break;
464 default:
465 qFatal("Unexpected jump result in event loop integration");
466 }
467}
468
469@end
470
471QT_BEGIN_NAMESPACE
472QT_USE_NAMESPACE
473
474QIOSEventDispatcher *QIOSEventDispatcher::create()
475{
476 if (isQtApplication() && rootLevelRunLoopIntegration())
477 return new QIOSJumpingEventDispatcher;
478
479 return new QIOSEventDispatcher;
480}
481
482QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent)
483 : QEventDispatcherCoreFoundation(parent)
484{
485 // We want all delivery of events from the system to be handled synchronously
486 QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
487}
488
489bool QIOSEventDispatcher::isQtApplication()
490{
491 return s_isQtApplication;
492}
493
494/*!
495 Override of the CoreFoundation posted events runloop source callback
496 so that we can send window system (QPA) events in addition to sending
497 normal Qt events.
498*/
499bool QIOSEventDispatcher::processPostedEvents()
500{
501 // Don't send window system events if the base CF dispatcher has determined
502 // that events should not be sent for this pass of the runloop source.
503 if (!QEventDispatcherCoreFoundation::processPostedEvents())
504 return false;
505
506 QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "sendWindowSystemEvents");
507 QEventLoop::ProcessEventsFlags flags
508 = QEventLoop::ProcessEventsFlags(m_processEvents.flags.loadRelaxed());
509 qCDebug(lcEventDispatcher) << "Sending window system events for" << flags;
510 QWindowSystemInterface::sendWindowSystemEvents(flags);
511
512 return true;
513}
514
515QIOSJumpingEventDispatcher::QIOSJumpingEventDispatcher(QObject *parent)
516 : QIOSEventDispatcher(parent)
517 , m_processEventLevel(0)
518 , m_runLoopExitObserver(this, &QIOSJumpingEventDispatcher::handleRunLoopExit, kCFRunLoopExit)
519{
520}
521
522bool __attribute__((returns_twice)) QIOSJumpingEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
523{
524 if (applicationAboutToTerminate) {
525 qCDebug(lcEventDispatcher) << "Detected QEventLoop exec after application termination";
526 // Re-issue exit, and return immediately
527 qApp->exit(kApplicationWillTerminateExitCode);
528 return false;
529 }
530
531 if (!m_processEventLevel && (flags & QEventLoop::EventLoopExec)) {
532 QT_APPLE_SCOPED_LOG_ACTIVITY(lcEventDispatcher().isDebugEnabled(), "processEvents");
533 qCDebug(lcEventDispatcher) << "Processing events with flags" << flags;
534
535 ++m_processEventLevel;
536
537 m_runLoopExitObserver.addToMode(kCFRunLoopCommonModes);
538
539 // We set a new jump point here that we can return to when the event loop
540 // is asked to exit, so that we can return from QEventLoop::exec().
541 switch (setjmp(processEventExitJumpPoint)) {
542 case kJumpPointSetSuccessfully:
543 qCDebug(lcEventDispatcher) << "QEventLoop exec detected, jumping back to system runloop ↵";
544 longjmp(processEventEnterJumpPoint, kJumpedFromEventDispatcherProcessEvents);
545 break;
546 case kJumpedFromEventLoopExecInterrupt:
547 // The event loop has exited (either by the hand of the user, or the iOS termination
548 // signal), and we jumped back though processEventExitJumpPoint. We return from processEvents,
549 // which will emit aboutToQuit if it's QApplication's event loop, and then return to the user's
550 // main, which can do whatever it wants, including calling exec() on the application again.
551 qCDebug(lcEventDispatcher) << "⇢ System runloop exited, returning with eventsProcessed = true";
552 updateStackLimit();
553 return true;
554 default:
555 qFatal("Unexpected jump result in event loop integration");
556 }
557
558 Q_UNREACHABLE();
559 }
560
561 ++m_processEventLevel;
562 bool processedEvents = QEventDispatcherCoreFoundation::processEvents(flags);
563 --m_processEventLevel;
564
565 return processedEvents;
566}
567
568void QIOSJumpingEventDispatcher::handleRunLoopExit(CFRunLoopActivity activity)
569{
570 Q_UNUSED(activity);
571 Q_ASSERT(activity == kCFRunLoopExit);
572
573 if (m_processEventLevel == 1 && !currentEventLoop()->isRunning())
575}
576
578{
579 Q_ASSERT(m_processEventLevel == 1);
580
581 --m_processEventLevel;
582
583 m_runLoopExitObserver.removeFromMode(kCFRunLoopCommonModes);
584
585 // We re-set applicationProcessEventsReturnPoint here so that future
586 // calls to QEventLoop::exec() will end up back here after entering
587 // processEvents, instead of back in didFinishLaunchingWithOptions.
588 switch (setjmp(processEventEnterJumpPoint)) {
589 case kJumpPointSetSuccessfully:
590 qCDebug(lcEventDispatcher) << "Jumping into processEvents due to system runloop exit ⇢";
591 logActivity.UIApplicationMain.leave();
592 longjmp(processEventExitJumpPoint, kJumpedFromEventLoopExecInterrupt);
593 break;
595 // QEventLoop was re-executed
596 logActivity.UIApplicationMain.enter();
597 qCDebug(lcEventDispatcher) << "↳ Jumped from processEvents due to re-exec";
598 restoreStackLimit();
599 break;
600 default:
601 qFatal("Unexpected jump result in event loop integration");
602 }
603}
604
605QT_END_NAMESPACE
void handleRunLoopExit(CFRunLoopActivity activity)
int main(int argc, char *argv[])
[ctor_close]
static const size_t kBytesPerKiloByte
#define qAlignUp(val, align)
#define qAlignDown(val, align)
bool __attribute__((returns_twice)) QIOSJumpingEventDispatcher
int qt_main_wrapper(int argc, char *argv[])
static const long kPageSize
static void __attribute__((noinline, noreturn)) user_main_trampoline()
static const char kApplicationWillTerminateExitCode
@ kJumpedFromUserMainTrampoline
@ kJumpedFromEventLoopExecInterrupt
@ kJumpedFromEventDispatcherProcessEvents
@ kJumpPointSetSuccessfully
static bool rootLevelRunLoopIntegration()