Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(191)

Side by Side Diff: chrome/browser/mac/exception_processor.mm

Issue 2543813003: [Mac] Modify the ObjC exception preprocessor to make some exceptions fatal. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/mac/exception_processor.h"
6
7 #import <Foundation/Foundation.h>
8 #include <libunwind.h>
9 #include <objc/objc-exception.h>
10
11 #include "base/debug/crash_logging.h"
12 #include "base/debug/stack_trace.h"
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "chrome/common/crash_keys.h"
18
19 namespace chrome {
20
21 // Maximum number of known named exceptions we'll support. There is
22 // no central registration, but I only find about 75 possibilities in
23 // the system frameworks, and many of them are probably not
24 // interesting to track in aggregate (those relating to distributed
25 // objects, for instance).
26 const size_t kKnownNSExceptionCount = 25;
Mark Mentovai 2016/12/01 19:02:33 Can constexpr these two also.
Robert Sesek 2016/12/06 23:18:09 Did this one, but not the latter (per comment in o
27
28 const size_t kUnknownNSException = kKnownNSExceptionCount;
29
30 size_t BinForException(NSException* exception) {
31 // A list of common known exceptions. The list position will
32 // determine where they live in the histogram, so never move them
33 // around, only add to the end.
34 static NSString* const kKnownNSExceptionNames[] = {
Mark Mentovai 2016/12/01 19:02:33 static doesn’t contribute to anything here.
Robert Sesek 2016/12/06 23:18:09 Done.
35 // Grab-bag exception, not very common. CFArray (or other
36 // container) mutated while being enumerated is one case seen in
37 // production.
38 NSGenericException,
39
40 // Out-of-range on NSString or NSArray. Quite common.
41 NSRangeException,
42
43 // Invalid arg to method, unrecognized selector. Quite common.
44 NSInvalidArgumentException,
45
46 // malloc() returned null in object creation, I think. Turns out
47 // to be very uncommon in production, because of the OOM killer.
48 NSMallocException,
49
50 // This contains things like windowserver errors, trying to draw
51 // views which aren't in windows, unable to read nib files. By
52 // far the most common exception seen on the crash server.
53 NSInternalInconsistencyException,
54
55 nil
Mark Mentovai 2016/12/01 19:02:33 Instead of a nil-terminated list, just use arraysi
Robert Sesek 2016/12/06 23:18:09 Done.
56 };
57
58 // Make sure our array hasn't outgrown our abilities to track it.
59 DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount);
Mark Mentovai 2016/12/01 19:02:33 constexpr benefit: static_cast instead of DCHECK!
Robert Sesek 2016/12/06 23:18:09 Done.
60
61 NSString* name = [exception name];
62 for (int i = 0; kKnownNSExceptionNames[i]; ++i) {
63 if (name == kKnownNSExceptionNames[i]) {
64 return i;
65 }
66 }
67 return kUnknownNSException;
68 }
69
70 void RecordExceptionWithUma(NSException* exception) {
71 UMA_HISTOGRAM_ENUMERATION("OSX.NSException",
72 BinForException(exception), kUnknownNSException);
73 }
74
75 static objc_exception_preprocessor g_next_preprocessor = nullptr;
76
77 static const char* kExceptionSinkholes[] = {
Mark Mentovai 2016/12/01 19:02:33 const the pointers too.
Robert Sesek 2016/12/06 23:18:09 Done.
78 "CFRunLoopRunSpecific",
79 "_dispatch_client_callout",
80 };
81
82 id ObjcExceptionPreprocessor(id exception) {
Mark Mentovai 2016/12/01 19:02:32 Should be static. Or introduce an unnamed namespa
Robert Sesek 2016/12/06 23:18:09 Made static because the anon namespace adds extra
83 static bool seen_first_exception = false;
84
85 // Record UMA and crash keys about the exception.
86 RecordExceptionWithUma(exception);
87
88 const char* const kExceptionKey =
89 seen_first_exception ? crash_keys::mac::kLastNSException
90 : crash_keys::mac::kFirstNSException;
91 NSString* value = [NSString stringWithFormat:@"%@ reason %@",
92 [exception name], [exception reason]];
93 base::debug::SetCrashKeyValue(kExceptionKey, base::SysNSStringToUTF8(value));
94
95 const char* const kExceptionTraceKey =
96 seen_first_exception ? crash_keys::mac::kLastNSExceptionTrace
97 : crash_keys::mac::kFirstNSExceptionTrace;
98 // This exception preprocessor runs prior to the one in libobjc, which sets
99 // the -[NSException callStackReturnAddresses].
100 base::debug::SetCrashKeyToStackTrace(kExceptionTraceKey,
101 base::debug::StackTrace());
102
103 seen_first_exception = true;
104
105 //////////////////////////////////////////////////////////////////////////////
106
107 // Unwind the stack looking for any exception handlers. If an exception
Mark Mentovai 2016/12/01 19:02:33 Oh good, new stuff!
108 // handler is encountered, test to see if it is a function known to catch-
109 // and-rethrow as a "top-level" exception handler. Various routines in
110 // Cocoa do this, and it obscures the crashing stack, since the original
111 // throw location is no longer present on the stack (just the re-throw) when
112 // Crashpad captures the crash report.
113 unw_context_t context;
114 unw_getcontext(&context);
115
116 unw_cursor_t cursor;
117 unw_init_local(&cursor, &context);
118
119 for (;;) {
120 int result = unw_step(&cursor);
Mark Mentovai 2016/12/01 19:02:33 Why not fold this into the loop condition?: while
Robert Sesek 2016/12/06 23:18:09 Done.
121 if (result <= 0) {
122 break;
123 }
124
125 unw_proc_info_t frame_info;
126 if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) {
127 continue;
128 }
129
130 // This frame has an exception handler.
131 if (frame_info.handler != 0) {
132 char proc_name[64];
133 unw_word_t name_len;
Mark Mentovai 2016/12/01 19:02:33 This is not name_len, it’s the offset of unw_get_r
Robert Sesek 2016/12/06 23:18:09 Oops, done.
134 if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name),
135 &name_len) != UNW_ESUCCESS) {
136 continue;
137 }
138
139 // Check if the function is one that is known to obscure (by way of
140 // catch-and-rethrow) exception stack traces. If it is, sinkhole it
141 // by crashing at the point of throw.
Mark Mentovai 2016/12/01 19:02:33 by crashing here at the point of throw
Robert Sesek 2016/12/06 23:18:09 Done.
142 for (const auto& sinkhole : kExceptionSinkholes) {
143 if (strcmp(sinkhole, proc_name) == 0) {
144 LOG(FATAL) << "Terminating from Objective-C exception: "
Mark Mentovai 2016/12/01 19:02:33 It might be neat if we could do something to get t
Robert Sesek 2016/12/06 23:18:09 I considered this but it may make things less clea
145 << base::SysNSStringToUTF8([exception name])
146 << ": " << base::SysNSStringToUTF8([exception reason]);
147 }
148 }
149
150 break;
151 }
152 }
153
154 // Forward to the next preprocessor.
155 if (g_next_preprocessor)
156 return g_next_preprocessor(exception);
157
158 return exception;
159 }
160
161 void InstallObjcExceptionPreprocessor() {
162 if (g_next_preprocessor)
163 return;
164
165 g_next_preprocessor =
166 objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
167 }
168
169 void UninstallObjcExceptionPreprocessor() {
170 if (!g_next_preprocessor)
Mark Mentovai 2016/12/01 19:02:33 If there was no previous preprocessor, this won’t
Robert Sesek 2016/12/06 23:18:09 Done.
171 return;
172
173 objc_setExceptionPreprocessor(g_next_preprocessor);
174 g_next_preprocessor = nullptr;
175 }
176
177 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698