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