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