summaryrefslogtreecommitdiffstats
path: root/libqtuitest/qalternatestack_unix.cpp
blob: 016102b17be8335784013548d891cc6bbd2458cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of QtUiTest.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#undef _FORTIFY_SOURCE

#include "qalternatestack_p.h"

#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>

#include <QDebug>

#ifdef QTUITEST_HAVE_VALGRIND
# include <valgrind/valgrind.h>
/* Make sure we have the right version of valgrind.h */
# if !defined(VALGRIND_STACK_REGISTER) || !defined(VALGRIND_STACK_DEREGISTER) || !defined(VALGRIND_STACK_CHANGE)
#  warn You have Valgrind but are missing stack annotation macros, disabling Valgrind features.
#  undef QTUITEST_HAVE_VALGRIND
# endif
#endif

struct QAlternateStackPrivate
{
    QAlternateStack* q;

    // The memory containing the alternate stack.
    QByteArray stackBuffer;
    char*      stackBegin;
    int        stackSize;

#ifdef QTUITEST_HAVE_VALGRIND
    int        stackValgrindId;
#endif

    void allocate(int);

    // Alternate and original stack objects.
    stack_t alternateStack;
    stack_t originalStack;

    // Sigaction used to jump to new stack, and original sigaction.
    struct sigaction alternateAction;
    struct sigaction originalAction;

    // jmp_buf for jumping between stacks.
    sigjmp_buf alternateJmp;
    sigjmp_buf originalJmp;

    // Starting point of the new stack.
    QAlternateStackEntryPoint entryFunction;

    // Argument to be passed to entryFunction.
    QVariant entryData;

    // All instances of QAlternateStack.
    static QList<QAlternateStack*> instances;
};
QList<QAlternateStack*> QAlternateStackPrivate::instances;

QTUITEST_EXPORT char*& qalternatestack_stackbuf()
{ static char* ret = 0; return ret; }
QTUITEST_EXPORT int& qalternatestack_stackbuf_len()
{ static int ret = 0; return ret; }

#ifndef QT_NO_DEBUG
QString qobject_to_string(QObject* object)
{
    QString ret;
    {
        QDebug dbg(&ret);
        dbg << object;
    }
    return ret;
}
#endif

void qt_sigaltstack(stack_t const* ss, stack_t* oss)
{
    if (0 != sigaltstack(ss, oss)) {
        qFatal("QAlternateStack: error in sigaltstack: %s", strerror(errno));
    }
}

void qt_sigaction(int signum, struct sigaction const* act, struct sigaction* oldact)
{
    if (0 != sigaction(signum,act,oldact)) {
        qFatal("QAlternateStack: error in sigaction: %s", strerror(errno));
    }
}

void qt_raise(int signum)
{
    if (-1 == raise(signum)) {
        qFatal("QAlternateStack: error in raise: %s", strerror(errno));
    }
}

QAlternateStackPrivate* qt_next_stack;

static const int JMP_SWITCHFROM         = 2;
static const int JMP_SWITCHTO           = 3;
static const int JMP_FINISH             = 4;
static const int QALTERNATESTACK_SIGNAL = SIGUSR2;

/*
    This wrapper function is called when we first enter an alternate stack,
    via the raising of QALTERNATESTACK_SIGNAL.
*/
void qt_entry_function_wrapper(int)
{
    // Establish this context into the alternate jmp_buf.
    // We cannot have the whole stack run during a signal handler,
    // so we store our context when we are first called, then return and allow
    // the original stack to jump back to us.

    int value = sigsetjmp(qt_next_stack->alternateJmp, 0);

    // We might be here for these reasons:
    //
    // 1. We are handling the signal and just called setjmp.
    //    value == 0.
    if (0 == value) {
        // Return from the signal handler; the original stack will jump back
        // to us via longjmp.
        return;
    }
    // 2. We are switched to from the original stack.
    //    value == JMP_SWITCHTO.
    else if (JMP_SWITCHTO == value) {
    }
    // Whoops, programmer error.
    else Q_ASSERT(0);

    QAlternateStackPrivate* stack = qt_next_stack;
    stack->entryFunction(stack->q, stack->entryData);

    // OK, jump back to the main stack and let it know that we're finished.
    // We can't just return here; if we did, we'd jump back to the signal
    // handler frame, but the signal handler is already finished.
    siglongjmp(stack->originalJmp, JMP_FINISH);
}

/*!
    \internal
    \class QAlternateStack
    \inpublicgroup QtUiTestModule
    \brief The QAlternateStack class provides a call stack.

    QAlternateStack can be used to switch between two call stacks without
    using threads.

    There is one anticipated use for this class, and that is to wait for a
    specific amount of time, while processing events, without hanging if a
    nested event loop occurs.

    Example:
    \code
    void runsInAlternateStack(QAlternateStack* stack, const QVariant& data)
    {
        int foo = data.toInt();
        // ...
        // Need to wait for about 1500 milliseconds here, while processing events.
        // Process events in the main stack to avoid hanging on nested event loops.
        QTimer::singleShot(1500, stack, SLOT(switchTo()));
        stack->switchFrom();

        // Execution resumes here in ~1500ms, as long as the main stack is
        // processing events, even if there was a nested event loop.
        // ...

        // Don't need the stack any more
        stack->deleteLater();
    }

    void runsInMainStack()
    {
        QAlternateStack* stack = new QAlternateStack;
        stack->start(runsInAlternateStack, 123);
    }
    \endcode

    Using this class makes code more difficult to understand.  Use an
    alternative where possible.
*/

/*!
    \relates QAlternateStack
    \typedef QAlternateStackEntryPoint

    Typedef for a pointer to a function with the signature
    \c{void my_function(QAlternateStack*,const QVariant&)}.

    Used as an entry point to a new stack.
*/

/*!
    Creates an alternate stack of \a size bytes with the given \a parent.

    Behavior is undefined if the alternate stack exceeds \a size.
*/
QAlternateStack::QAlternateStack(quint32 size, QObject* parent)
    : QObject(parent),
      d(new QAlternateStackPrivate)
{
    Q_ASSERT_X(QAlternateStack::isAvailable(), "QAlternateStack",
            "QAlternateStack is not available on this platform!");

    d->q = this;
    d->entryFunction = 0;

    d->allocate(size);

#ifdef QTUITEST_HAVE_VALGRIND
    d->stackValgrindId = VALGRIND_STACK_REGISTER(d->stackBegin, d->stackBegin+d->stackSize);
#endif

    QAlternateStackPrivate::instances << this;
}

void QAlternateStackPrivate::allocate(int size)
{
    if (!qalternatestack_stackbuf()) {
        // Allocate space for the new stack.
        stackBuffer.resize(size);
        stackBegin = stackBuffer.data();
        stackSize  = size;
        return;
    }

    /*
        On some systems, it has been found that due to limitations in the kernel and/or threading
        implementation, setting the stack pointer to point to memory allocated anywhere other than
        the main stack causes a crash (bug 209341).

        This code works around this bug by using a pool of memory which was earlier allocated on
        the stack.  qalternatestack_stackbuf() and qalternatestack_stackbuf_len() must be
        explicitly initialized to point to a block of memory and to tell us its length.
    */

    // Find the first chunk of unused memory.
    char* begin = qalternatestack_stackbuf();
    char* end   = qalternatestack_stackbuf() + qalternatestack_stackbuf_len();
    char* found = 0;
    int total_used = 0;

    for (char* buf = begin; buf <= end - size && !found; buf += size) {
        char* buf_end = buf + size;
        // Is this block of memory definitely unused?
        bool used = false;
        foreach (QAlternateStack* st, QAlternateStackPrivate::instances) {
            char* st_buf = st->d->stackBegin;
            char* st_buf_end = st->d->stackBegin + st->d->stackSize;
            // If we start after this stack ends, or we end before it finishes, we're OK.
            if (buf >= st_buf_end || buf_end <= st_buf) {
            } else {
                used = true;
                total_used += (st_buf_end - st_buf);
                break;
            }
        }
        if (!used) found = buf;
    }

    if (!found) {
        qFatal( "QtUiTest: QAlternateStack ran out of memory! There seem to be %d stacks using a "
                "total of %d bytes (out of %d), and a stack of size %d was requested. "
                "Please increase the size of qalternatestack_stackbuf!",
                QAlternateStackPrivate::instances.count(), total_used,
                    qalternatestack_stackbuf_len(), size);
    }
    stackBegin = found;
    stackSize  = size;
}

/*!
    Destroys the alternate stack.

    Behavior is undefined if the stack is destroyed while currently active.
*/
QAlternateStack::~QAlternateStack()
{
#ifdef QTUITEST_HAVE_VALGRIND
    VALGRIND_STACK_DEREGISTER(d->stackValgrindId);
#endif

    delete d;
    d = 0;
    QAlternateStackPrivate::instances.removeAll(this);
}

/*!
    Switch from the original stack to the alternate stack, and start
    executing \a entry in the alternate stack.
    This stack and \a data will be passed as an argument to \a entry.

    This function should be called when first switching to an alternate
    stack.  When resuming a stack that is already active, use switchTo().

    \sa switchTo(), switchFrom()
*/
void QAlternateStack::start(QAlternateStackEntryPoint entry, const QVariant& data)
{
    Q_ASSERT_X(!isActive(), "QAlternateStack::start",
            qPrintable(QString("`start' called while already active. sender(): %1")
                .arg(qobject_to_string(sender()))));

    // Set up the alternate stack to be jumped to.
    d->alternateStack.ss_sp    = d->stackBegin;
    d->alternateStack.ss_flags = 0;
    d->alternateStack.ss_size  = d->stackSize;
    qt_sigaltstack(&d->alternateStack, &d->originalStack);

    // Set up signal handler to jump to entry function wrapper in alternate stack.
    d->alternateAction.sa_handler = qt_entry_function_wrapper;
    sigemptyset(&d->alternateAction.sa_mask);
    d->alternateAction.sa_flags   = SA_ONSTACK;
    qt_sigaction(QALTERNATESTACK_SIGNAL, &d->alternateAction, &d->originalAction);

    // Raise the signal, jumping to the alternate stack.
    qt_next_stack = d;
    qt_raise(QALTERNATESTACK_SIGNAL);

    // signal handler returns immediately after storing its context into
    // alternateJmp.  Restore the old signal handler.
    qt_sigaltstack(&d->originalStack, 0);
    qt_sigaction(QALTERNATESTACK_SIGNAL, &d->originalAction, 0);

    // OK, now store the entry function and data and jump to the new stack.
    d->entryFunction = entry;
    d->entryData     = data;

    switchTo();
}

/*!
    Switch from the original stack to the alternate stack.

    This function can be called to resume execution in an alternate stack.

    If execution has been suspended in the alternate stack by a call to
    switchFrom(), switchTo() will resume executing at that point.
    If the alternate stack has completed execution or hasn't started, this
    function does nothing and returns immediately.

    It is an error to call this function from the alternate stack.
*/
void QAlternateStack::switchTo()
{
    // We must not be the currently active stack.
    Q_ASSERT_X(!isCurrentStack(), "QAlternateStack::switchTo",
            qPrintable(QString("`switchTo' called in currently active stack. sender(): %1")
                .arg(qobject_to_string(sender()))));

    // Store where we currently are.
    int value = sigsetjmp(d->originalJmp, 1);

    // Now it really gets tricky.
    // At this particular point, we could be here for these reasons:
    //
    //  1. switchTo() was actually called from the original stack
    //     and we just returned from setjmp.
    //     value == 0.
    //
    //  2. The alternate stack switched to the original stack
    //     by calling switchFrom().
    //     value == JMP_SWITCHFROM.
    //
    //  3. The alternate stack finished execution and jumped back to us.
    //     value == JMP_FINISH.

    // Case 1: just returned from setjmp.
    //         Jump to the other stack.
    if (0 == value) {
        // If there is no entry function, that means `start' has not been called or we have
        // finished already.  In other words, there's no more to process on this stack, so
        // just return immediately.
        if (!d->entryFunction) return;

        // This function never returns; from here we will jump back to
        // the above call to setjmp() via case 2 or 3.
        siglongjmp(d->alternateJmp, JMP_SWITCHTO);
    }

    // Case 2: switchFrom() called in alternate stack.
    if (value == JMP_SWITCHFROM) {
        // Don't need to do anything, just return.
        return;
    }

    // Case 3: The alternate stack finished execution.
    // Just need to do a little cleanup.
    if (value == JMP_FINISH) {
        d->entryFunction = 0;
        d->entryData     = QVariant();
        return;
    }

    // Whoops, bad programmer.
    Q_ASSERT(0);
}

/*!
    Switch from the alternate stack to the original stack.

    Once execution takes place in the alternate stack, there are two ways
    to return to the original stack.

    The first is simply to return from the entry function passed to the
    constructor, at which point the switchTo() function in the original
    stack will return.

    The second is to call switchFrom().  This will cause the switchTo()
    function to return in the original stack.  switchFrom() will not
    return until switchTo() is called again in the original stack, which
    is not guaranteed to happen.

    It is an error to call this function from the original stack.
*/
void QAlternateStack::switchFrom()
{
    // We must be the currently active stack.
    Q_ASSERT_X(isCurrentStack(), "QAlternateStack::switchFrom",
        qPrintable(QString("`switchFrom' called from wrong stack. sender(): %1")
            .arg(qobject_to_string(sender()))));

    int value = sigsetjmp(d->alternateJmp, 1);

    // Two possibilities:
    //
    // 1. We just called setjmp and will now jump to main stack.
    //  value == 0
    if (0 == value) {
        siglongjmp(d->originalJmp, JMP_SWITCHFROM);
    }
    // 2. We just jumped to here from main stack.
    //    value == JMP_SWITCHTO
    else if (JMP_SWITCHTO == value) {
    }
    // Whoops, bad programmer.
    else Q_ASSERT(0);
}

/*!
    Returns true if the stack has started (entry function has been called)
    and not yet finished (entry function has not returned).

    \sa isCurrentStack()
*/
bool QAlternateStack::isActive() const
{ return d->entryFunction; }

/*!
    Returns true if the currently used stack is this QAlternateStack.

    \sa isActive()
*/
bool QAlternateStack::isCurrentStack() const
{
    // Test if a stack-allocated variable resides within the alternate
    // stack buffer.
    char dummy = 1;
    quint32 diff = &dummy - d->stackBegin;
    return (diff < (quint32)d->stackSize);
}

/*!
    Returns all created QAlternateStack objects.
*/
QList<QAlternateStack*> QAlternateStack::instances()
{ return QAlternateStackPrivate::instances; }

/*!
    Returns true if QAlternateStack is usable on this platform.

    Usage of QAlternateStack when this function returns false will typically
    result in a fatal error at runtime.
*/
bool QAlternateStack::isAvailable()
{
#ifdef Q_OS_MAC
    return false;
#endif
    return true;
}