summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/nsX11ErrorHandler.cpp
blob: 77f71db8f9d838cee3fbd47b2088e1964f49524f (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
/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsX11ErrorHandler.h"

#include "prenv.h"
#include "nsXULAppAPI.h"
#include "nsExceptionHandler.h"
#include "nsDebug.h"
#include "nsString.h"
#include "nsTArray.h"

#include "mozilla/X11Util.h"
#include <X11/Xlib.h>

#define BUFSIZE 2048  // What Xlib uses with XGetErrorDatabaseText

struct XExtension {
  nsCString name;
  int major_opcode;

  XExtension(const char* aName, int aCode) : name(aName), major_opcode(aCode) {}
};

static nsTArray<XExtension> sXExtensions;

// man XSetErrorHandler says "the error handler should not call any
// functions (directly or indirectly) on the display that will generate
// protocol requests or that will look for input events" so we query the
// extension list early to avoid problems.
static void QueryXExtensions(Display* aDisplay) {
  if (!sXExtensions.IsEmpty() || !aDisplay) {
    return;
  }
  int nExts = 0;
  char** extNames = XListExtensions(aDisplay, &nExts);
  if (!extNames) {
    return;
  }
  for (int i = 0; i < nExts; ++i) {
    int major_opcode, first_event, first_error;
    if (XQueryExtension(aDisplay, extNames[i], &major_opcode, &first_event,
                        &first_error)) {
      sXExtensions.EmplaceBack(extNames[i], major_opcode);
    }
  }
  XFreeExtensionList(extNames);
}

extern "C" {
int X11Error(Display* display, XErrorEvent* event) {
#ifdef DEBUG
  // Get an indication of how long ago the request that caused the error was
  // made.
  unsigned long age = NextRequest(display) - event->serial;
#endif

  // Get a string to represent the request that caused the error.
  nsAutoCString message;
  if (event->request_code < 128) {
    // Core protocol request
    message.AppendInt(event->request_code);
  } else {
    // Extension request
    for (XExtension& ext : sXExtensions) {
      if (ext.major_opcode == event->request_code) {
        message.Append(ext.name);
        message.Append('.');
        message.AppendInt(event->minor_code);
        break;
      }
    }
  }

  char buffer[BUFSIZE];
  if (message.IsEmpty()) {
    buffer[0] = '\0';
  } else {
    XGetErrorDatabaseText(display, "XRequest", message.get(), "", buffer,
                          sizeof(buffer));
  }

  nsAutoCString notes;
  if (buffer[0]) {
    notes.Append(buffer);
  } else {
    notes.AppendLiteral("Request ");
    notes.AppendInt(event->request_code);
    notes.Append('.');
    notes.AppendInt(event->minor_code);
  }

  notes.AppendLiteral(": ");

  // Get a string to describe the error.
  XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
  notes.Append(buffer);

#ifdef DEBUG
  // For requests where Xlib gets the reply synchronously, |age| will be 1
  // and the stack will include the function making the request.  For
  // asynchronous requests, the current stack will often be unrelated to the
  // point of making the request, even if |age| is 1, but sometimes this may
  // help us count back to the point of the request.  With XSynchronize on,
  // the stack will include the function making the request, even though
  // |age| will be 2 for asynchronous requests because XSynchronize is
  // implemented by an empty request from an XSync, which has not yet been
  // processed.
  if (age > 1) {
    // XSynchronize returns the previous "after function".  If a second
    // XSynchronize call returns the same function after an enable call then
    // synchronization must have already been enabled.
    if (XSynchronize(display, X11True) == XSynchronize(display, X11False)) {
      notes.AppendLiteral("; sync");
    } else {
      notes.AppendLiteral("; ");
      notes.AppendInt(uint32_t(age));
      notes.AppendLiteral(" requests ago");
    }
  }

  // The resource id is unlikely to be useful in a crash report without
  // context of other ids, but add it to the debug console output.
  notes.AppendLiteral("; id=0x");
  notes.AppendInt(uint32_t(event->resourceid), 16);
#  ifdef MOZ_X11
  // Actually, for requests where Xlib gets the reply synchronously,
  // MOZ_X_SYNC=1 will not be necessary, but we don't have a table to tell us
  // which requests get a synchronous reply.
  if (!PR_GetEnv("MOZ_X_SYNC")) {
    notes.AppendLiteral(
        "\nRe-running with MOZ_X_SYNC=1 in the environment may give a more "
        "helpful backtrace.");
  }
#  endif
#endif

  NS_WARNING(notes.get());
  return 0;
}
}

void InstallX11ErrorHandler() {
  XSetErrorHandler(X11Error);

  if (Display* display = mozilla::DefaultXDisplay()) {
    QueryXExtensions(display);
    if (PR_GetEnv("MOZ_X_SYNC")) {
      XSynchronize(display, X11True);
    }
  }
}

void CleanupX11ErrorHandler() { sXExtensions.Clear(); }