├── .gitignore
├── CHANGELOG.md
├── Entitlements.plist
├── LICENSE
├── Libraries
└── RegexKitLite
│ ├── RegexKitLite.h
│ └── RegexKitLite.m
├── Makefile
├── Makefile.arm
├── Makefile.common
├── Makefile.x86_64
├── README.md
├── get_requirements.sh
├── layout
├── DEBIAN
│ └── control
└── etc
│ └── symbolicate
│ └── blame_filters.plist
└── src
├── common.c
├── common.h
├── main.m
├── symbolMaps.h
└── symbolMaps.m
/.gitignore:
--------------------------------------------------------------------------------
1 | **/theos
2 | **/theos/*
3 | **/.theos/*
4 | *.o
5 | */obj/*
6 | **/obj/*
7 | _/*
8 | *.deb
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Release Notes
2 |
3 | > # Version 1.8.0
4 | > ## Released 2018-04-23
5 | > - - -
6 | > * NEW: iOS 11
7 | > * Added initial support for iOS 11 (Electra jailbreak).
8 |
9 | - - -
10 |
11 | > # Version 1.7.0
12 | > ## Released 2015-11-30
13 | > - - -
14 | > * NEW: arm64
15 | > * Added arm64 slice.
16 | > * MOD: armv7(s)
17 | > * Removed armv7 and armv7s slices.
18 |
19 | - - -
20 |
21 | > # Version 1.6.0
22 | > ## Released 201x-xx-xx
23 | > - - -
24 | > * NEW: Added -f (filter type) switch.
25 | > * MOD: Updated to use libcrashreport.
26 |
27 | - - -
28 |
29 | > # Version 1.5.0
30 | > ## Released 201x-xx-xx
31 | > - - -
32 | > * MOD: Added armv7 and armv7s slices.
33 |
34 | - - -
35 |
36 | > # Version 1.4.0
37 | > ## Released 201x-xx-xx
38 | > - - -
39 | > * NEW: Added support for IPS files (via libsymbolicate).
40 | > * NEW: Added --sysroot option.
41 | > * NEW: Added --blame-only option.
42 | > * NEW: Added --print-blame option.
43 | > * NEW: Added x86_64 support.
44 | > * Copy the symbolicate and libsymbolicate.dylib files to a Mac OS X machine to use.
45 | > * MOD: Removed -n option.
46 | > * MOD: Moved parsing and symbolication functionality to libsymbolicate.
47 |
--------------------------------------------------------------------------------
/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | platform-application
6 |
7 | com.apple.private.skip-library-validation
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 Lance Fetters (aka. ashikase)
2 |
3 | Parsing and symbolication code was originally based upon work from CrashReporter.
4 | http://networkpx.googlecode.com/svn/trunk/CrashReporter/?r=623
5 | Copyright (C) 2009 KennyTM~
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | The GNU General Public License can be found at .
18 |
--------------------------------------------------------------------------------
/Libraries/RegexKitLite/RegexKitLite.h:
--------------------------------------------------------------------------------
1 | //
2 | // RegexKitLite.h
3 | // http://regexkit.sourceforge.net/
4 | // Licensed under the terms of the BSD License, as specified below.
5 | //
6 |
7 | /*
8 | Copyright (c) 2008-2009, John Engelhart
9 |
10 | All rights reserved.
11 |
12 | Redistribution and use in source and binary forms, with or without
13 | modification, are permitted provided that the following conditions are met:
14 |
15 | * Redistributions of source code must retain the above copyright
16 | notice, this list of conditions and the following disclaimer.
17 |
18 | * Redistributions in binary form must reproduce the above copyright
19 | notice, this list of conditions and the following disclaimer in the
20 | documentation and/or other materials provided with the distribution.
21 |
22 | * Neither the name of the Zang Industries nor the names of its
23 | contributors may be used to endorse or promote products derived from
24 | this software without specific prior written permission.
25 |
26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
32 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 | */
38 |
39 | /* Modified by KennyTM~: Settings. */
40 | #define NS_BLOCK_ASSERTIONS 1
41 | #define RKL_FAST_MUTABLE_CHECK 1
42 | /* End modification */
43 |
44 | #ifdef __OBJC__
45 | #import
46 | #import
47 | #import
48 | #import
49 | #import
50 | #endif // __OBJC__
51 |
52 | #include
53 | #include
54 | #include
55 | #include
56 | #include
57 |
58 | #ifdef __cplusplus
59 | extern "C" {
60 | #endif
61 |
62 | #ifndef REGEXKITLITE_VERSION_DEFINED
63 | #define REGEXKITLITE_VERSION_DEFINED
64 |
65 | #define _RKL__STRINGIFY(b) #b
66 | #define _RKL_STRINGIFY(a) _RKL__STRINGIFY(a)
67 | #define _RKL_JOIN_VERSION(a,b) _RKL_STRINGIFY(a##.##b)
68 | #define _RKL_VERSION_STRING(a,b) _RKL_JOIN_VERSION(a,b)
69 |
70 | #define REGEXKITLITE_VERSION_MAJOR 3
71 | #define REGEXKITLITE_VERSION_MINOR 0
72 |
73 | #define REGEXKITLITE_VERSION_CSTRING _RKL_VERSION_STRING(REGEXKITLITE_VERSION_MAJOR, REGEXKITLITE_VERSION_MINOR)
74 | #define REGEXKITLITE_VERSION_NSSTRING @REGEXKITLITE_VERSION_CSTRING
75 |
76 | #endif // REGEXKITLITE_VERSION_DEFINED
77 |
78 | // For Mac OS X < 10.5.
79 | #ifndef NSINTEGER_DEFINED
80 | #define NSINTEGER_DEFINED
81 | #if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
82 | typedef long NSInteger;
83 | typedef unsigned long NSUInteger;
84 | #define NSIntegerMin LONG_MIN
85 | #define NSIntegerMax LONG_MAX
86 | #define NSUIntegerMax ULONG_MAX
87 | #else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
88 | typedef int NSInteger;
89 | typedef unsigned int NSUInteger;
90 | #define NSIntegerMin INT_MIN
91 | #define NSIntegerMax INT_MAX
92 | #define NSUIntegerMax UINT_MAX
93 | #endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
94 | #endif // NSINTEGER_DEFINED
95 |
96 | #ifndef RKLREGEXOPTIONS_DEFINED
97 | #define RKLREGEXOPTIONS_DEFINED
98 |
99 | // These must be idential to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html
100 | enum {
101 | RKLNoOptions = 0,
102 | RKLCaseless = 2,
103 | RKLComments = 4,
104 | RKLDotAll = 32,
105 | RKLMultiline = 8,
106 | RKLUnicodeWordBoundaries = 256
107 | };
108 | typedef uint32_t RKLRegexOptions; // This must be identical to the ICU 'flags' argument type.
109 |
110 | #endif // RKLREGEXOPTIONS_DEFINED
111 |
112 | #ifndef _REGEXKITLITE_H_
113 | #define _REGEXKITLITE_H_
114 |
115 | #if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465)
116 | #define RKL_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
117 | #else
118 | #define RKL_DEPRECATED_ATTRIBUTE
119 | #endif
120 |
121 | // This requires a few levels of rewriting to get the desired results.
122 | #define _RKL_CONCAT_2(c,d) c ## d
123 | #define _RKL_CONCAT(a,b) _RKL_CONCAT_2(a,b)
124 |
125 | #ifdef RKL_PREPEND_TO_METHODS
126 | #define RKL_METHOD_PREPEND(x) _RKL_CONCAT(RKL_PREPEND_TO_METHODS, x)
127 | #else // RKL_PREPEND_TO_METHODS
128 | #define RKL_METHOD_PREPEND(x) x
129 | #endif // RKL_PREPEND_TO_METHODS
130 |
131 | // If it looks like low memory notifications might be available, add code to register and respond to them.
132 | // This is (should be) harmless if it turns out that this isn't the case, since the notification that we register for,
133 | // UIApplicationDidReceiveMemoryWarningNotification, is dynamically looked up via dlsym().
134 | #if ((defined(TARGET_OS_EMBEDDED) && (TARGET_OS_EMBEDDED != 0)) || (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0))) && (!defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) || (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS != 0))
135 | #define RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS 1
136 | #endif
137 |
138 | #ifdef __OBJC__
139 |
140 | // NSException exception name.
141 | extern NSString * const RKLICURegexException;
142 |
143 | // NSError error domains and user info keys.
144 | extern NSString * const RKLICURegexErrorDomain;
145 |
146 | extern NSString * const RKLICURegexErrorCodeErrorKey;
147 | extern NSString * const RKLICURegexErrorNameErrorKey;
148 | extern NSString * const RKLICURegexLineErrorKey;
149 | extern NSString * const RKLICURegexOffsetErrorKey;
150 | extern NSString * const RKLICURegexPreContextErrorKey;
151 | extern NSString * const RKLICURegexPostContextErrorKey;
152 | extern NSString * const RKLICURegexRegexErrorKey;
153 | extern NSString * const RKLICURegexRegexOptionsErrorKey;
154 |
155 | @interface NSString (RegexKitLiteAdditions)
156 |
157 | + (void)RKL_METHOD_PREPEND(clearStringCache);
158 |
159 | // Although these are marked as deprecated, a bug in GCC prevents a warning from being issues for + class methods. Filed bug with Apple, #6736857.
160 | + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex RKL_DEPRECATED_ATTRIBUTE;
161 | + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error RKL_DEPRECATED_ATTRIBUTE;
162 |
163 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex;
164 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range;
165 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error;
166 |
167 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex;
168 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range;
169 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error;
170 |
171 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex;
172 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture;
173 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range;
174 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
175 |
176 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex;
177 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture;
178 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range;
179 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
180 |
181 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement;
182 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange;
183 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error;
184 |
185 | ////
186 |
187 | - (NSInteger)RKL_METHOD_PREPEND(captureCount);
188 | - (NSInteger)RKL_METHOD_PREPEND(captureCountWithOptions):(RKLRegexOptions)options error:(NSError **)error;
189 |
190 | - (BOOL)RKL_METHOD_PREPEND(isRegexValid);
191 | - (BOOL)RKL_METHOD_PREPEND(isRegexValidWithOptions):(RKLRegexOptions)options error:(NSError **)error;
192 |
193 | - (void)RKL_METHOD_PREPEND(flushCachedRegexData);
194 |
195 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex;
196 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex capture:(NSInteger)capture;
197 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex range:(NSRange)range;
198 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
199 |
200 |
201 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex;
202 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range;
203 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error;
204 |
205 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex;
206 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range;
207 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error;
208 |
209 | @end
210 |
211 | @interface NSMutableString (RegexKitLiteAdditions)
212 |
213 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement;
214 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange;
215 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error;
216 |
217 | @end
218 |
219 | #endif // __OBJC__
220 |
221 | #endif // _REGEXKITLITE_H_
222 |
223 | #ifdef __cplusplus
224 | } // extern "C"
225 | #endif
226 |
--------------------------------------------------------------------------------
/Libraries/RegexKitLite/RegexKitLite.m:
--------------------------------------------------------------------------------
1 | //
2 | // RegexKitLite.m
3 | // http://regexkit.sourceforge.net/
4 | // Licensed under the terms of the BSD License, as specified below.
5 | //
6 |
7 | /*
8 | Copyright (c) 2008-2009, John Engelhart
9 |
10 | All rights reserved.
11 |
12 | Redistribution and use in source and binary forms, with or without
13 | modification, are permitted provided that the following conditions are met:
14 |
15 | * Redistributions of source code must retain the above copyright
16 | notice, this list of conditions and the following disclaimer.
17 |
18 | * Redistributions in binary form must reproduce the above copyright
19 | notice, this list of conditions and the following disclaimer in the
20 | documentation and/or other materials provided with the distribution.
21 |
22 | * Neither the name of the Zang Industries nor the names of its
23 | contributors may be used to endorse or promote products derived from
24 | this software without specific prior written permission.
25 |
26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
32 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 | */
38 |
39 | #include
40 | #include
41 | #include
42 | #import
43 | #import
44 | #import
45 | #import
46 | #import
47 | #import
48 | #ifdef __OBJC_GC__
49 | #import
50 | #define RKL_STRONG_REF __strong
51 | #else // __OBJC_GC__
52 | #define RKL_STRONG_REF
53 | #endif // __OBJC_GC__
54 |
55 | #include
56 | #include
57 | #include
58 | #include
59 | #include
60 | #include
61 | #include
62 | #include
63 | #include
64 |
65 | #import "RegexKitLite.h"
66 |
67 | // If the gcc flag -mmacosx-version-min is used with, for example, '=10.2', give a warning that the libicucore.dylib is only available on >= 10.3.
68 | // If you are reading this comment because of this warning, this is to let you know that linking to /usr/lib/libicucore.dylib will cause your executable to fail on < 10.3.
69 | // You will need to build your own version of the ICU library and link to that in order for RegexKitLite to work successfully on < 10.3. This is not simple.
70 |
71 | #if MAC_OS_X_VERSION_MIN_REQUIRED < 1030
72 | #warning The ICU dynamic shared library, /usr/lib/libicucore.dylib, is only available on Mac OS X 10.3 and later.
73 | #warning You will need to supply a version of the ICU library to use RegexKitLite on Mac OS X 10.2 and earlier.
74 | #endif
75 |
76 | ////////////
77 | #pragma mark Compile time tuneables
78 |
79 | #ifndef RKL_CACHE_SIZE
80 | #define RKL_CACHE_SIZE (23UL)
81 | #endif
82 |
83 | #ifndef RKL_FIXED_LENGTH
84 | #define RKL_FIXED_LENGTH (2048UL)
85 | #endif
86 |
87 | #ifndef RKL_STACK_LIMIT
88 | #define RKL_STACK_LIMIT (128UL * 1024UL)
89 | #endif
90 |
91 | #ifdef RKL_APPEND_TO_ICU_FUNCTIONS
92 | #define RKL_ICU_FUNCTION_APPEND(x) _RKL_CONCAT(x, RKL_APPEND_TO_ICU_FUNCTIONS)
93 | #else // RKL_APPEND_TO_ICU_FUNCTIONS
94 | #define RKL_ICU_FUNCTION_APPEND(x) x
95 | #endif // RKL_APPEND_TO_ICU_FUNCTIONS
96 |
97 | #if defined(RKL_DTRACE) && (RKL_DTRACE != 0)
98 | #define _RKL_DTRACE_ENABLED
99 | #endif // defined(RKL_DTRACE) && (RKL_DTRACE != 0)
100 |
101 | // These are internal, non-public tuneables.
102 | #define RKL_SCRATCH_BUFFERS (4UL)
103 | #define RKL_CACHE_LINE_SIZE (64UL)
104 | #define RKL_DTRACE_REGEXUTF8_SIZE (64UL)
105 |
106 | //////////////
107 | #pragma mark -
108 | #pragma mark GCC / Compiler macros
109 |
110 | #if defined (__GNUC__) && (__GNUC__ >= 4)
111 | #define RKL_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__))
112 | #define RKL_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect))
113 | #define RKL_PREFETCH(ptr) __builtin_prefetch(ptr)
114 | #define RKL_PREFETCH_UNICHAR(ptr, off) { const char *p = ((const char *)(ptr)) + ((off) * sizeof(UniChar)) + RKL_CACHE_LINE_SIZE; RKL_PREFETCH(p); RKL_PREFETCH(p + RKL_CACHE_LINE_SIZE); }
115 | #define RKL_HAVE_CLEANUP
116 | #define RKL_CLEANUP(func) __attribute__((cleanup(func)))
117 | #else // defined (__GNUC__) && (__GNUC__ >= 4)
118 | #define RKL_ATTRIBUTES(attr, ...)
119 | #define RKL_EXPECTED(cond, expect) cond
120 | #define RKL_PREFETCH(ptr)
121 | #define RKL_PREFETCH_UNICHAR(ptr, off)
122 | #define RKL_CLEANUP(func)
123 | #endif // defined (__GNUC__) && (__GNUC__ >= 4)
124 |
125 | #define RKL_STATIC_INLINE static __inline__ RKL_ATTRIBUTES(always_inline)
126 | #define RKL_UNUSED_ARG RKL_ATTRIBUTES(unused)
127 | #define RKL_NONNULL_ARGS(arg, ...) RKL_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__))
128 | #define RKL_NONNULL_ARGS_WARN_UNUSED(arg, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__))
129 |
130 | #if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
131 | #define RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as))
132 | #else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
133 | #define RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__))
134 | #endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
135 |
136 |
137 | ////////////
138 | #pragma mark -
139 | #pragma mark Assertion macros
140 |
141 | // These macros are nearly identical to their NSCParameterAssert siblings.
142 | // This is required because nearly everything is done while cacheSpinLock is locked.
143 | // We need to safely unlock before throwing any of these exceptions.
144 | // @try {} @finally {} significantly slows things down so it's not used.
145 |
146 | #define RKLCAssertDictionary(d, ...) rkl_makeAssertDictionary(__PRETTY_FUNCTION__, __FILE__, __LINE__, (d), ##__VA_ARGS__)
147 | #define RKLCDelayedHardAssert(c, e, g) do { id *_e=(e); int _c=(c); if(RKL_EXPECTED(_e == NULL, 0L) || RKL_EXPECTED(*_e != NULL, 0L)) { goto g; } if(RKL_EXPECTED(!_c, 0L)) { *_e = RKLCAssertDictionary(@"Invalid parameter not satisfying: %s", #c); goto g; } } while(0)
148 |
149 | #ifdef NS_BLOCK_ASSERTIONS
150 | #define RKLCDelayedAssert(c, e, g)
151 | #define RKL_UNUSED_ASSERTION_ARG RKL_ATTRIBUTES(unused)
152 | #else // NS_BLOCK_ASSERTIONS
153 | #define RKLCDelayedAssert(c, e, g) RKLCDelayedHardAssert(c, e, g)
154 | #define RKL_UNUSED_ASSERTION_ARG
155 | #endif // NS_BLOCK_ASSERTIONS
156 |
157 | #define RKL_EXCEPTION(e, f, ...) [NSException exceptionWithName:(e) reason:rkl_stringFromClassAndMethod((self), (_cmd), (f), ##__VA_ARGS__) userInfo:NULL]
158 | #define RKL_RAISE_EXCEPTION(e, f, ...) [RKL_EXCEPTION(e, f, ##__VA_ARGS__) raise]
159 |
160 | ////////////
161 | #pragma mark -
162 | #pragma mark Utility functions and macros
163 |
164 | //RKL_STATIC_INLINE BOOL NSRangeInsideRange(NSRange cin, NSRange win) RKL_ATTRIBUTES(warn_unused_result);
165 | //RKL_STATIC_INLINE BOOL NSRangeInsideRange(NSRange cin, NSRange win) { return((((cin.location - win.location) <= win.length) && ((NSMaxRange(cin) - win.location) <= win.length)) ? YES : NO); }
166 |
167 | #define NSMakeRange(loc, len) ((NSRange){.location=(NSUInteger)(loc), .length=(NSUInteger)(len)})
168 | #define CFMakeRange(loc, len) ((CFRange){.location= (CFIndex)(loc), .length= (CFIndex)(len)})
169 | #define NSNotFoundRange ((NSRange){.location= NSNotFound, .length= 0UL})
170 | #define NSMaxiumRange ((NSRange){.location= 0UL, .length= NSUIntegerMax})
171 |
172 | ////////////
173 | #pragma mark -
174 | #pragma mark Exported NSString symbols for exception names, error domains, error keys, etc
175 |
176 | NSString * const RKLICURegexException = @"RKLICURegexException";
177 |
178 | NSString * const RKLICURegexErrorDomain = @"RKLICURegexErrorDomain";
179 |
180 | NSString * const RKLICURegexErrorCodeErrorKey = @"RKLICURegexErrorCode";
181 | NSString * const RKLICURegexErrorNameErrorKey = @"RKLICURegexErrorName";
182 | NSString * const RKLICURegexLineErrorKey = @"RKLICURegexLine";
183 | NSString * const RKLICURegexOffsetErrorKey = @"RKLICURegexOffset";
184 | NSString * const RKLICURegexPreContextErrorKey = @"RKLICURegexPreContext";
185 | NSString * const RKLICURegexPostContextErrorKey = @"RKLICURegexPostContext";
186 | NSString * const RKLICURegexRegexErrorKey = @"RKLICURegexRegex";
187 | NSString * const RKLICURegexRegexOptionsErrorKey = @"RKLICURegexRegexOptions";
188 |
189 | ////////////
190 | #pragma mark -
191 | #pragma mark Type / struct definitions
192 |
193 | // In general, the ICU bits and pieces here must exactly match the definition in the ICU sources.
194 |
195 | #define U_ZERO_ERROR 0
196 | #define U_INDEX_OUTOFBOUNDS_ERROR 8
197 | #define U_BUFFER_OVERFLOW_ERROR 15
198 |
199 | #define U_PARSE_CONTEXT_LEN 16
200 |
201 | typedef struct uregex uregex; // Opaque ICU regex type.
202 |
203 | typedef struct UParseError { // This must be exactly the same as the 'real' ICU declaration.
204 | int32_t line;
205 | int32_t offset;
206 | UniChar preContext[U_PARSE_CONTEXT_LEN];
207 | UniChar postContext[U_PARSE_CONTEXT_LEN];
208 | } UParseError;
209 |
210 | // For use with GCC's cleanup() __attribute__.
211 | #define RKLLockedCacheSpinLock ((NSUInteger)(1UL<<0))
212 | #define RKLUnlockedCacheSpinLock ((NSUInteger)(1UL<<1))
213 |
214 | enum {
215 | RKLSplitOp = 1,
216 | RKLReplaceOp = 2,
217 | RKLRangeOp = 3,
218 | RKLArrayOfStringsOp = 4,
219 | RKLArrayOfCapturesOp = 5,
220 | RKLCapturesArrayOp = 6,
221 | RKLMaskOp = 0xf,
222 | RKLReplaceMutable = 1 << 4,
223 | RKLSubcapturesArray = 1 << 5,
224 | };
225 | typedef NSUInteger RKLRegexOp;
226 |
227 | typedef struct {
228 | NSRange *ranges, findInRange;
229 | NSInteger capacity, found, findUpTo, capture;
230 | size_t size, stackUsed;
231 | void **rangesScratchBuffer;
232 | RKL_STRONG_REF void **stringsScratchBuffer;
233 | RKL_STRONG_REF void **arraysScratchBuffer;
234 | } RKLFindAll;
235 |
236 | typedef struct {
237 | CFStringRef string;
238 | CFHashCode hash;
239 | CFIndex length;
240 | RKL_STRONG_REF UniChar *uniChar;
241 | } RKLBuffer;
242 |
243 | typedef struct {
244 | CFStringRef regexString;
245 | RKLRegexOptions options;
246 | uregex *icu_regex;
247 | NSInteger captureCount;
248 |
249 | CFStringRef setToString;
250 | CFHashCode setToHash;
251 | CFIndex setToLength;
252 | NSUInteger setToIsImmutable:1;
253 | NSUInteger setToNeedsConversion:1;
254 | const UniChar *setToUniChar;
255 | NSRange setToRange, lastFindRange, lastMatchRange;
256 | #ifndef __LP64__
257 | NSUInteger pad[1]; // For 32 bits, this makes the struct 64 bytes exactly, which is good for cache line alignment.
258 | #endif // __LP64__
259 | } RKLCacheSlot;
260 |
261 | ////////////
262 | #pragma mark -
263 | #pragma mark Translation unit scope global variables
264 |
265 | static UniChar fixedUniChar[(RKL_FIXED_LENGTH)]; // This is the fixed sized UTF-16 conversion buffer.
266 | static RKLCacheSlot rkl_cacheSlots[(RKL_CACHE_SIZE)], *lastCacheSlot;
267 | static OSSpinLock cacheSpinLock = OS_SPINLOCK_INIT;
268 | static RKLBuffer dynamicBuffer, fixedBuffer = {NULL, 0UL, 0L, &fixedUniChar[0]};
269 | static const UniChar emptyUniCharString[1]; // For safety, icu_regexes are 'set' to this when the string they were searched is cleared.
270 | static RKL_STRONG_REF void *scratchBuffer[(RKL_SCRATCH_BUFFERS)]; // Used to hold temporary allocations that are allocated via reallocf().
271 |
272 | ////////////
273 | #pragma mark -
274 | #pragma mark CFArray call backs
275 |
276 | // These are used when running under manual memory management for the array that rkl_splitArray creates.
277 | // The split strings are created, but not autoreleased. The (immutable) array is created using these callbacks, which skips the CFRetain() call, effectively transferring ownership to the CFArray object.
278 | // For each split string this saves the overhead of an autorelease, then an array retain, then an NSAutoreleasePool release. This is good for a ~30% speed increase.
279 |
280 | static void RKLCFArrayRelease (CFAllocatorRef allocator RKL_UNUSED_ARG, const void *ptr) { CFRelease((CFTypeRef)ptr); }
281 | static CFArrayCallBacks transferOwnershipArrayCallBacks = { (CFIndex)0L, NULL, RKLCFArrayRelease, CFCopyDescription, CFEqual };
282 |
283 | #if defined(__OBJC_GC__) || defined(RKL_FORCE_GC)
284 | ////////////
285 | #pragma mark -
286 | #pragma mark Low-level Garbage Collection aware memory/resource allocation utilities
287 | // If compiled with Garbage Collection, we need to be able to do a few things slightly differently.
288 | // The basic premiss is that under GC we use a trampoline function pointer which is set to a _start function to catch the first invocation.
289 | // The _start function checks if GC is running and then overwrites the function pointer with the appropriate routine. Think of it as 'lazy linking'.
290 |
291 | enum { RKLScannedOption = NSScannedOption };
292 |
293 | // rkl_collectingEnabled uses objc_getClass() to get the NSGarbageCollector class, which doesn't exist on earlier systems.
294 | // This allows for graceful failure should we find ourselves running on an earlier version of the OS without NSGarbageCollector.
295 | static BOOL rkl_collectingEnabled_first (void);
296 | static BOOL rkl_collectingEnabled_yes (void) { return(YES); }
297 | static BOOL rkl_collectingEnabled_no (void) { return(NO); }
298 | static BOOL(*rkl_collectingEnabled) (void) = rkl_collectingEnabled_first;
299 | static BOOL rkl_collectingEnabled_first (void) {
300 | BOOL gcEnabled = ([objc_getClass("NSGarbageCollector") defaultCollector] != NULL) ? YES : NO;
301 | if(gcEnabled == YES) {
302 | // This section of code is required due to what I consider to be a fundamental design flaw in Cocoas GC system.
303 | // Earlier versions of "Garbage Collection Programming Guide" stated that (paraphrased) "all globals are automatically roots".
304 | // Current versions of the guide now include the following warning:
305 | // "You may pass addresses of strong globals or statics into routines expecting pointers to object pointers (such as id* or NSError**)
306 | // only if they have first been assigned to directly, rather than through a pointer dereference."
307 | // This is a surprisingly non-trivial condition to actually meet in practice and is a recipe for impossible to debug race condition bugs.
308 | // We just happen to be very, very, very lucky in the fact that we can initilize our root set before the first use.
309 | int x;
310 | for(x = 0; x < (int)(RKL_SCRATCH_BUFFERS); x++) { scratchBuffer[x] = NSAllocateCollectable(16UL, 0UL); scratchBuffer[x] = NULL; }
311 | dynamicBuffer.uniChar = (RKL_STRONG_REF UniChar *)NSAllocateCollectable(16UL, 0UL); dynamicBuffer.uniChar = NULL;
312 | }
313 | return((rkl_collectingEnabled = (gcEnabled == YES) ? rkl_collectingEnabled_yes : rkl_collectingEnabled_no)());
314 | }
315 |
316 | // rkl_realloc()
317 | static void *rkl_realloc_first (RKL_STRONG_REF void **ptr, size_t size, NSUInteger flags);
318 | static void *rkl_realloc_std (RKL_STRONG_REF void **ptr, size_t size, NSUInteger flags RKL_UNUSED_ARG) { return((*ptr = reallocf(*ptr, size))); }
319 | static void *rkl_realloc_gc (RKL_STRONG_REF void **ptr, size_t size, NSUInteger flags) { return((*ptr = NSReallocateCollectable(*ptr, (NSUInteger)size, flags))); }
320 | static void *(*rkl_realloc) (RKL_STRONG_REF void **ptr, size_t size, NSUInteger flags) RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(2,1) = rkl_realloc_first;
321 | static void *rkl_realloc_first (RKL_STRONG_REF void **ptr, size_t size, NSUInteger flags) { return((rkl_realloc = (rkl_collectingEnabled()==YES) ? rkl_realloc_gc : rkl_realloc_std)(ptr, size, flags)); }
322 |
323 | // rkl_free()
324 | static void * rkl_free_first (RKL_STRONG_REF void **ptr);
325 | static void * rkl_free_std (RKL_STRONG_REF void **ptr) { if(*ptr != NULL) { free(*ptr); *ptr = NULL; } return(NULL); }
326 | static void * rkl_free_gc (RKL_STRONG_REF void **ptr) { if(*ptr != NULL) { *ptr = NULL; } return(NULL); }
327 | static void *(*rkl_free) (RKL_STRONG_REF void **ptr) RKL_NONNULL_ARGS(1) = rkl_free_first;
328 | static void * rkl_free_first (RKL_STRONG_REF void **ptr) { return((rkl_free = (rkl_collectingEnabled()==YES) ? rkl_free_gc : rkl_free_std)(ptr)); }
329 |
330 | // rkl_CFAutorelease()
331 | static id rkl_CFAutorelease_first (CFTypeRef obj);
332 | static id rkl_CFAutorelease_std (CFTypeRef obj) { return([(id)obj autorelease]); }
333 | static id rkl_CFAutorelease_gc (CFTypeRef obj) { return(NSMakeCollectable(obj)); }
334 | static id(*rkl_CFAutorelease) (CFTypeRef obj) = rkl_CFAutorelease_first;
335 | static id rkl_CFAutorelease_first (CFTypeRef obj) { return((rkl_CFAutorelease = (rkl_collectingEnabled()==YES) ? rkl_CFAutorelease_gc : rkl_CFAutorelease_std)(obj)); }
336 |
337 | // rkl_CreateStringWithSubstring()
338 | static id rkl_CreateStringWithSubstring_first (id string, NSRange range);
339 | static id rkl_CreateStringWithSubstring_std (id string, NSRange range) { return((id)CFStringCreateWithSubstring(NULL, (CFStringRef)string, CFMakeRange((CFIndex)range.location, (CFIndex)range.length))); }
340 | static id rkl_CreateStringWithSubstring_gc (id string, NSRange range) { return([string substringWithRange:range]); }
341 | static id(*rkl_CreateStringWithSubstring) (id string, NSRange range) RKL_NONNULL_ARGS_WARN_UNUSED(1) = rkl_CreateStringWithSubstring_first;
342 | static id rkl_CreateStringWithSubstring_first (id string, NSRange range) { return((rkl_CreateStringWithSubstring = (rkl_collectingEnabled()==YES) ? rkl_CreateStringWithSubstring_gc : rkl_CreateStringWithSubstring_std)(string, range)); }
343 |
344 | // rkl_ReleaseObject()
345 | static id rkl_ReleaseObject_first (id obj);
346 | static id rkl_ReleaseObject_std (id obj) { CFRelease((CFTypeRef)obj); return(NULL); }
347 | static id rkl_ReleaseObject_gc (id obj RKL_UNUSED_ARG) { return(NULL); }
348 | static id (*rkl_ReleaseObject) (id obj) RKL_NONNULL_ARGS(1) = rkl_ReleaseObject_first;
349 | static id rkl_ReleaseObject_first (id obj) { return((rkl_ReleaseObject = (rkl_collectingEnabled()==YES) ? rkl_ReleaseObject_gc : rkl_ReleaseObject_std)(obj)); }
350 |
351 | // rkl_CreateArrayWithObjects()
352 | static id rkl_CreateArrayWithObjects_first (void **objects, NSUInteger count);
353 | static id rkl_CreateArrayWithObjects_std (void **objects, NSUInteger count) { return((id)CFArrayCreate(NULL, (const void **)objects, (CFIndex)count, &transferOwnershipArrayCallBacks)); }
354 | static id rkl_CreateArrayWithObjects_gc (void **objects, NSUInteger count) { return([NSArray arrayWithObjects:(const id *)objects count:count]); }
355 | static id(*rkl_CreateArrayWithObjects) (void **objects, NSUInteger count) RKL_NONNULL_ARGS_WARN_UNUSED(1) = rkl_CreateArrayWithObjects_first;
356 | static id rkl_CreateArrayWithObjects_first (void **objects, NSUInteger count) { return((rkl_CreateArrayWithObjects = (rkl_collectingEnabled()==YES) ? rkl_CreateArrayWithObjects_gc : rkl_CreateArrayWithObjects_std)(objects, count)); }
357 |
358 | // rkl_CreateAutoreleasedArray()
359 | static id rkl_CreateAutoreleasedArray_first (void **objects, NSUInteger count);
360 | static id rkl_CreateAutoreleasedArray_std (void **objects, NSUInteger count) { return((id)rkl_CFAutorelease(rkl_CreateArrayWithObjects(objects, count))); }
361 | static id rkl_CreateAutoreleasedArray_gc (void **objects, NSUInteger count) { return(rkl_CreateArrayWithObjects(objects, count)); }
362 | static id(*rkl_CreateAutoreleasedArray) (void **objects, NSUInteger count) RKL_NONNULL_ARGS_WARN_UNUSED(1) = rkl_CreateAutoreleasedArray_first;
363 | static id rkl_CreateAutoreleasedArray_first (void **objects, NSUInteger count) { return((rkl_CreateAutoreleasedArray = (rkl_collectingEnabled()==YES) ? rkl_CreateAutoreleasedArray_gc : rkl_CreateAutoreleasedArray_std)(objects, count)); }
364 |
365 | #else // __OBJC_GC__ not defined
366 | ////////////
367 | #pragma mark -
368 | #pragma mark Low-level explicit memory/resource allocation utilities
369 |
370 | enum { RKLScannedOption = 0 };
371 |
372 | #define rkl_collectingEnabled() (NO)
373 |
374 | RKL_STATIC_INLINE void *rkl_realloc (void **ptr, size_t size, NSUInteger flags) RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(2,1);
375 | RKL_STATIC_INLINE void *rkl_free (void **ptr) RKL_NONNULL_ARGS(1);
376 | RKL_STATIC_INLINE id rkl_CFAutorelease (CFTypeRef obj);
377 | RKL_STATIC_INLINE id rkl_CreateAutoreleasedArray (void **objects, NSUInteger count) RKL_NONNULL_ARGS_WARN_UNUSED(1);
378 | RKL_STATIC_INLINE id rkl_CreateArrayWithObjects (void **objects, NSUInteger count) RKL_NONNULL_ARGS_WARN_UNUSED(1);
379 | RKL_STATIC_INLINE id rkl_CreateStringWithSubstring (id string, NSRange range) RKL_NONNULL_ARGS_WARN_UNUSED(1);
380 | RKL_STATIC_INLINE id rkl_ReleaseObject (id obj) RKL_NONNULL_ARGS(1);
381 |
382 | RKL_STATIC_INLINE void *rkl_realloc (void **ptr, size_t size, NSUInteger flags RKL_UNUSED_ARG) { return((*ptr = reallocf(*ptr, size))); }
383 | RKL_STATIC_INLINE void *rkl_free (void **ptr) { if(*ptr != NULL) { free(*ptr); *ptr = NULL; } return(NULL); }
384 | RKL_STATIC_INLINE id rkl_CFAutorelease (CFTypeRef obj) { return([(id)obj autorelease]); }
385 | RKL_STATIC_INLINE id rkl_CreateArrayWithObjects (void **objects, NSUInteger count) { return((id)CFArrayCreate(NULL, (const void **)objects, (CFIndex)count, &transferOwnershipArrayCallBacks)); }
386 | RKL_STATIC_INLINE id rkl_CreateAutoreleasedArray (void **objects, NSUInteger count) { return(rkl_CFAutorelease(rkl_CreateArrayWithObjects(objects, count))); }
387 | RKL_STATIC_INLINE id rkl_CreateStringWithSubstring (id string, NSRange range) { return((id)CFStringCreateWithSubstring(NULL, (CFStringRef)string, CFMakeRange((CFIndex)range.location, (CFIndex)range.length))); }
388 | RKL_STATIC_INLINE id rkl_ReleaseObject (id obj) { CFRelease((CFTypeRef)obj); return(NULL); }
389 |
390 | #endif // __OBJC_GC__
391 |
392 | ////////////
393 | #pragma mark -
394 | #pragma mark ICU function prototypes
395 |
396 | // ICU functions. See http://www.icu-project.org/apiref/icu4c/uregex_8h.html Tweaked slightly from the originals, but functionally identical.
397 | const char *RKL_ICU_FUNCTION_APPEND(u_errorName) (int32_t status) RKL_ATTRIBUTES(pure);
398 | int32_t RKL_ICU_FUNCTION_APPEND(u_strlen) (const UniChar *s) RKL_ATTRIBUTES(nonnull(1), pure);
399 | int32_t RKL_ICU_FUNCTION_APPEND(uregex_appendReplacement) (uregex *regexp, const UniChar *replacementText, int32_t replacementLength, UniChar **destBuf, int32_t *destCapacity, int32_t *status) RKL_NONNULL_ARGS(1,2,4,5,6);
400 | int32_t RKL_ICU_FUNCTION_APPEND(uregex_appendTail) (uregex *regexp, UniChar **destBuf, int32_t *destCapacity, int32_t *status) RKL_NONNULL_ARGS(1,2,3,4);
401 | void RKL_ICU_FUNCTION_APPEND(uregex_close) (uregex *regexp) RKL_NONNULL_ARGS(1);
402 | int32_t RKL_ICU_FUNCTION_APPEND(uregex_end) (uregex *regexp, int32_t groupNum, int32_t *status) RKL_NONNULL_ARGS(1,3);
403 | BOOL RKL_ICU_FUNCTION_APPEND(uregex_find) (uregex *regexp, int32_t location, int32_t *status) RKL_NONNULL_ARGS(1,3);
404 | BOOL RKL_ICU_FUNCTION_APPEND(uregex_findNext) (uregex *regexp, int32_t *status) RKL_NONNULL_ARGS(1,2);
405 | int32_t RKL_ICU_FUNCTION_APPEND(uregex_groupCount) (uregex *regexp, int32_t *status) RKL_NONNULL_ARGS(1,2);
406 | uregex *RKL_ICU_FUNCTION_APPEND(uregex_open) (const UniChar *pattern, int32_t patternLength, RKLRegexOptions flags, UParseError *parseError, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,4,5);
407 | void RKL_ICU_FUNCTION_APPEND(uregex_reset) (uregex *regexp, int32_t newIndex, int32_t *status) RKL_NONNULL_ARGS(1,3);
408 | void RKL_ICU_FUNCTION_APPEND(uregex_setText) (uregex *regexp, const UniChar *text, int32_t textLength, int32_t *status) RKL_NONNULL_ARGS(1,2,4);
409 | int32_t RKL_ICU_FUNCTION_APPEND(uregex_start) (uregex *regexp, int32_t groupNum, int32_t *status) RKL_NONNULL_ARGS(1,3);
410 |
411 | ////////////
412 | #pragma mark -
413 | #pragma mark RegexKitLite internal, private function prototypes
414 |
415 | static RKLCacheSlot *rkl_getCachedRegex (NSString *regexString, RKLRegexOptions options, NSError **error, id *exception) RKL_NONNULL_ARGS_WARN_UNUSED(1,4);
416 | static NSUInteger rkl_setCacheSlotToString (RKLCacheSlot *cacheSlot, const NSRange *range, int32_t *status, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,3,4);
417 | static RKLCacheSlot *rkl_getCachedRegexSetToString (NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,3,4,5,7,8);
418 | static id rkl_performRegexOp (id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result) RKL_NONNULL_ARGS(1,2);
419 | static void rkl_handleDelayedAssert (id self, SEL _cmd, id exception) RKL_NONNULL_ARGS(1,2,3);
420 |
421 | static NSUInteger rkl_search (RKLCacheSlot *cacheSlot, NSRange *searchRange, NSUInteger updateSearchRange, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,4,5);
422 |
423 | static BOOL rkl_findRanges (RKLCacheSlot *cacheSlot, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,3,4,5);
424 | static NSUInteger rkl_growFindRanges (RKLCacheSlot *cacheSlot, NSUInteger lastLocation, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_NONNULL_ARGS_WARN_UNUSED(1,3,4);
425 | static NSArray *rkl_makeArray (RKLCacheSlot *cacheSlot, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_NONNULL_ARGS_WARN_UNUSED(1,3,4);
426 |
427 | static NSString *rkl_replaceString (RKLCacheSlot *cacheSlot, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSUInteger *replacedCount, NSUInteger replaceMutable, id *exception, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,4,8,9);
428 | static int32_t rkl_replaceAll (RKLCacheSlot *cacheSlot, const UniChar *replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSUInteger *replacedCount, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,4,7,8);
429 |
430 | static NSUInteger rkl_isRegexValid (id self, SEL _cmd, NSString *regex, RKLRegexOptions options, NSInteger *captureCountPtr, NSError **error) RKL_NONNULL_ARGS(1,2);
431 |
432 | static void rkl_clearStringCache (void);
433 | static void rkl_clearBuffer (RKLBuffer *buffer, NSUInteger freeDynamicBuffer) RKL_NONNULL_ARGS(1);
434 | static void rkl_clearCacheSlotRegex (RKLCacheSlot *cacheSlot) RKL_NONNULL_ARGS(1);
435 | static void rkl_clearCacheSlotSetTo (RKLCacheSlot *cacheSlot) RKL_NONNULL_ARGS(1);
436 |
437 | static NSDictionary *rkl_userInfoDictionary (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, ...) RKL_ATTRIBUTES(sentinel, nonnull(1), warn_unused_result);
438 | static NSError *rkl_NSErrorForRegex (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) RKL_NONNULL_ARGS_WARN_UNUSED(1);
439 | static NSException *rkl_NSExceptionForRegex (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) RKL_NONNULL_ARGS_WARN_UNUSED(1);
440 | static NSDictionary *rkl_makeAssertDictionary (const char *function, const char *file, int line, NSString *format, ...) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,4);
441 | static NSString *rkl_stringFromClassAndMethod (id object, SEL selector, NSString *format, ...) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,3);
442 |
443 | RKL_STATIC_INLINE int32_t rkl_getRangeForCapture(RKLCacheSlot *cs, int32_t *s, int32_t c, NSRange *r) RKL_NONNULL_ARGS_WARN_UNUSED(1,2,4);
444 | RKL_STATIC_INLINE int32_t rkl_getRangeForCapture(RKLCacheSlot *cs, int32_t *s, int32_t c, NSRange *r) { uregex *re = cs->icu_regex; int32_t start = RKL_ICU_FUNCTION_APPEND(uregex_start)(re, c, s); if(RKL_EXPECTED((*s > U_ZERO_ERROR), 0L) || (start == -1)) { *r = NSNotFoundRange; } else { r->location = (NSUInteger)start; r->length = (NSUInteger)RKL_ICU_FUNCTION_APPEND(uregex_end)(re, c, s) - r->location; r->location += cs->setToRange.location; } return(*s); }
445 |
446 | RKL_STATIC_INLINE RKLFindAll rkl_makeFindAll(NSRange *r, NSRange fir, NSInteger c, size_t s, size_t su, void **rsb, RKL_STRONG_REF void **ssb, RKL_STRONG_REF void **asb, NSInteger f, NSInteger cap, NSInteger fut) RKL_ATTRIBUTES(warn_unused_result);
447 | RKL_STATIC_INLINE RKLFindAll rkl_makeFindAll(NSRange *r, NSRange fir, NSInteger c, size_t s, size_t su, void **rsb, RKL_STRONG_REF void **ssb, RKL_STRONG_REF void **asb, NSInteger f, NSInteger cap, NSInteger fut) { return(((RKLFindAll){ .ranges=r, .findInRange=fir, .capacity=c, .found=f, .findUpTo=fut, .capture=cap, .size=s, .stackUsed=su, .rangesScratchBuffer=rsb, .stringsScratchBuffer=ssb, .arraysScratchBuffer=asb})); }
448 |
449 | ////////////
450 | #pragma mark -
451 | #pragma mark RKL_FAST_MUTABLE_CHECK implementation
452 |
453 | #ifdef RKL_FAST_MUTABLE_CHECK
454 | // We use a trampoline function pointer to check at run time if the function __CFStringIsMutable is available.
455 | // If it is, the trampoline function pointer is replaced with the address of that function.
456 | // Otherwise, we assume the worst case that every string is mutable.
457 | // This hopefully helps to protect us since we're using an undocumented, non-public API call.
458 | // We will keep on working if it ever does go away, just with a bit less performance due to the overhead of mutable checks.
459 |
460 | static BOOL rkl_CFStringIsMutable_first (CFStringRef str);
461 | static BOOL rkl_CFStringIsMutable_yes (CFStringRef str RKL_UNUSED_ARG) { return(YES); }
462 | static BOOL(*rkl_CFStringIsMutable) (CFStringRef str) = rkl_CFStringIsMutable_first;
463 | static BOOL rkl_CFStringIsMutable_first (CFStringRef str) { if((rkl_CFStringIsMutable = (BOOL(*)(CFStringRef))dlsym(RTLD_DEFAULT, "__CFStringIsMutable")) == NULL) { rkl_CFStringIsMutable = rkl_CFStringIsMutable_yes; } return(rkl_CFStringIsMutable(str)); }
464 | #else // RKL_FAST_MUTABLE_CHECK is not defined. Assume that all strings are potentially mutable.
465 | #define rkl_CFStringIsMutable(s) (YES)
466 | #endif // RKL_FAST_MUTABLE_CHECK
467 |
468 |
469 | ////////////
470 | #pragma mark -
471 | #pragma mark iPhone / iPod touch low memory notification handler
472 |
473 | #if defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) && (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS == 1)
474 |
475 | // The next few lines are specifically for the iPhone to catch low memory conditions.
476 | // The basic idea is that rkl_RegisterForLowMemoryNotifications() is set to be run once by the linker at load time via __attribute((constructor)).
477 | // rkl_RegisterForLowMemoryNotifications() tries to find the iPhone low memory notification symbol. If it can find it,
478 | // it registers with the default NSNotificationCenter to call the RKLLowMemoryWarningObserver class method +lowMemoryWarning:.
479 | // rkl_RegisterForLowMemoryNotifications() uses an atomic compare and swap to guarantee that it initalizes exactly once.
480 | // +lowMemoryWarning tries to acquire the cache lock. If it gets the lock, it clears the cache. If it can't, it calls performSelector:
481 | // with a delay of half a second to try again. This will hopefully prevent any deadlocks, such as a RegexKitLite request for
482 | // memory triggering a notifcation while the lock is held.
483 |
484 | static void rkl_RegisterForLowMemoryNotifications(void) RKL_ATTRIBUTES(used);
485 |
486 | @interface RKLLowMemoryWarningObserver : NSObject +(void)lowMemoryWarning:(id)notification; @end
487 | @implementation RKLLowMemoryWarningObserver
488 | +(void)lowMemoryWarning:(id)notification {
489 | if(OSSpinLockTry(&cacheSpinLock)) { rkl_clearStringCache(); OSSpinLockUnlock(&cacheSpinLock); }
490 | else { [[RKLLowMemoryWarningObserver class] performSelector:@selector(lowMemoryWarning:) withObject:NULL afterDelay:(NSTimeInterval)0.1]; }
491 | }
492 | @end
493 |
494 | static int rkl_HaveRegisteredForLowMemoryNotifications = 0;
495 |
496 | __attribute__((constructor)) static void rkl_RegisterForLowMemoryNotifications(void) {
497 | void **memoryWarningNotification = NULL;
498 |
499 | if(OSAtomicCompareAndSwapIntBarrier(0, 1, &rkl_HaveRegisteredForLowMemoryNotifications)) {
500 | if((memoryWarningNotification = (void **)dlsym(RTLD_DEFAULT, "UIApplicationDidReceiveMemoryWarningNotification")) != NULL) {
501 | [[NSNotificationCenter defaultCenter] addObserver:[RKLLowMemoryWarningObserver class] selector:@selector(lowMemoryWarning:) name:(NSString *)*memoryWarningNotification object:NULL];
502 | }
503 | }
504 | }
505 |
506 | #endif // defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) && (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS == 1)
507 |
508 | #ifdef _RKL_DTRACE_ENABLED
509 |
510 | // compiledRegexCache(unsigned long eventID, const char *regexUTF8, int options, int captures, int hitMiss, int icuStatusCode, const char *icuErrorMessage, double *hitRate);
511 | // utf16ConversionCache(unsigned long eventID, unsigned int lookupResultFlags, double *hitRate, const void *string, unsigned long NSRange.location, unsigned long NSRange.length, long length);
512 |
513 | /*
514 | provider RegexKitLite {
515 | probe compiledRegexCache(unsigned long, const char *, unsigned int, int, int, int, const char *, double *);
516 | probe utf16ConversionCache(unsigned long, unsigned int, double *, const void *, unsigned long, unsigned long, long);
517 | };
518 |
519 | #pragma D attributes Unstable/Unstable/Common provider RegexKitLite provider
520 | #pragma D attributes Private/Private/Common provider RegexKitLite module
521 | #pragma D attributes Private/Private/Common provider RegexKitLite function
522 | #pragma D attributes Unstable/Unstable/Common provider RegexKitLite name
523 | #pragma D attributes Unstable/Unstable/Common provider RegexKitLite args
524 | */
525 |
526 | #define REGEXKITLITE_STABILITY "___dtrace_stability$RegexKitLite$v1$4_4_5_1_1_5_1_1_5_4_4_5_4_4_5"
527 | #define REGEXKITLITE_TYPEDEFS "___dtrace_typedefs$RegexKitLite$v1"
528 | #define REGEXKITLITE_COMPILEDREGEXCACHE(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { __asm__ volatile(".reference " REGEXKITLITE_TYPEDEFS); __dtrace_probe$RegexKitLite$compiledRegexCache$v1$756e7369676e6564206c6f6e67$63686172202a$756e7369676e656420696e74$696e74$696e74$696e74$63686172202a$646f75626c65202a(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); __asm__ volatile(".reference " REGEXKITLITE_STABILITY); }
529 | #define REGEXKITLITE_COMPILEDREGEXCACHE_ENABLED() __dtrace_isenabled$RegexKitLite$compiledRegexCache$v1()
530 | #define REGEXKITLITE_CONVERTEDSTRINGU16CACHE(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { __asm__ volatile(".reference " REGEXKITLITE_TYPEDEFS); __dtrace_probe$RegexKitLite$utf16ConversionCache$v1$756e7369676e6564206c6f6e67$756e7369676e656420696e74$646f75626c65202a$766f6964202a$756e7369676e6564206c6f6e67$756e7369676e6564206c6f6e67$6c6f6e67(arg0, arg1, arg2, arg3, arg4, arg5, arg6); __asm__ volatile(".reference " REGEXKITLITE_STABILITY); }
531 | #define REGEXKITLITE_CONVERTEDSTRINGU16CACHE_ENABLED() __dtrace_isenabled$RegexKitLite$utf16ConversionCache$v1()
532 |
533 | extern void __dtrace_probe$RegexKitLite$compiledRegexCache$v1$756e7369676e6564206c6f6e67$63686172202a$756e7369676e656420696e74$696e74$696e74$696e74$63686172202a$646f75626c65202a(unsigned long, const char *, unsigned int, int, int, int, const char *, double *);
534 | extern int __dtrace_isenabled$RegexKitLite$compiledRegexCache$v1(void);
535 | extern void __dtrace_probe$RegexKitLite$utf16ConversionCache$v1$756e7369676e6564206c6f6e67$756e7369676e656420696e74$646f75626c65202a$766f6964202a$756e7369676e6564206c6f6e67$756e7369676e6564206c6f6e67$6c6f6e67(unsigned long, unsigned int, double *, const void *, unsigned long, unsigned long, long);
536 | extern int __dtrace_isenabled$RegexKitLite$utf16ConversionCache$v1(void);
537 |
538 | ////////////////////////////
539 |
540 | enum {
541 | RKLCacheHitLookupFlag = 1 << 0,
542 | RKLConversionRequiredLookupFlag = 1 << 1,
543 | RKLSetTextLookupFlag = 1 << 2,
544 | RKLDynamicBufferLookupFlag = 1 << 3,
545 | RKLErrorLookupFlag = 1 << 4,
546 | };
547 |
548 | #define rkl_dtrace_addLookupFlag(a,b) do { a |= (unsigned int)(b); } while(0)
549 |
550 | static char rkl_dtrace_regexUTF8[(RKL_CACHE_SIZE) + 1][(RKL_DTRACE_REGEXUTF8_SIZE)];
551 | static NSUInteger rkl_dtrace_eventID, rkl_dtrace_compiledCacheLookups, rkl_dtrace_compiledCacheHits, rkl_dtrace_conversionBufferLookups, rkl_dtrace_conversionBufferHits;
552 |
553 | #define rkl_dtrace_incrementEventID() do { rkl_dtrace_eventID++; } while(0)
554 | #define rkl_dtrace_compiledRegexCache(a0, a1, a2, a3, a4, a5) do { int _a3 = (a3); rkl_dtrace_compiledCacheLookups++; if(_a3 == 1) { rkl_dtrace_compiledCacheHits++; } if(RKL_EXPECTED(REGEXKITLITE_COMPILEDREGEXCACHE_ENABLED(), 0L)) { double hitRate = 0.0; if(rkl_dtrace_compiledCacheLookups > 0UL) { hitRate = ((double)rkl_dtrace_compiledCacheHits / (double)rkl_dtrace_compiledCacheLookups) * 100.0; } REGEXKITLITE_COMPILEDREGEXCACHE(rkl_dtrace_eventID, a0, a1, a2, _a3, a4, a5, &hitRate); } } while(0)
555 | #define rkl_dtrace_utf16ConversionCache(a0, a1, a2, a3, a4) do { unsigned int _a0 = (a0); if((_a0 & RKLConversionRequiredLookupFlag) != 0U) { rkl_dtrace_conversionBufferLookups++; if((_a0 & RKLCacheHitLookupFlag) != 0U) { rkl_dtrace_conversionBufferHits++; } } if(RKL_EXPECTED(REGEXKITLITE_CONVERTEDSTRINGU16CACHE_ENABLED(), 0L)) { double hitRate = 0.0; if(rkl_dtrace_conversionBufferLookups > 0UL) { hitRate = ((double)rkl_dtrace_conversionBufferHits / (double)rkl_dtrace_conversionBufferLookups) * 100.0; } REGEXKITLITE_CONVERTEDSTRINGU16CACHE(rkl_dtrace_eventID, _a0, &hitRate, a1, a2, a3, a4); } } while(0)
556 |
557 |
558 | // \342\200\246 == UTF8 for HORIZONTAL ELLIPSIS, aka triple dots '...'
559 | #define RKL_UTF8_ELLIPSE "\342\200\246"
560 |
561 | // rkl_dtrace_getRegexUTF8 will copy the str argument to utf8Buffer using UTF8 as the string encoding.
562 | // If the utf8 encoding would take up more bytes than the utf8Buffers length, then the unicode character 'HORIZONTAL ELLIPSIS' ('...') is appened to indicate truncation occured.
563 | static void rkl_dtrace_getRegexUTF8(CFStringRef str, char *utf8Buffer) RKL_NONNULL_ARGS(2);
564 | static void rkl_dtrace_getRegexUTF8(CFStringRef str, char *utf8Buffer) {
565 | if((str == NULL) || (utf8Buffer == NULL)) { return; }
566 | CFIndex maxLength = ((CFIndex)(RKL_DTRACE_REGEXUTF8_SIZE) - 2L), maxBytes = (maxLength - (CFIndex)sizeof(RKL_UTF8_ELLIPSE) - 1L), stringU16Length = CFStringGetLength(str), usedBytes = 0L;
567 | CFStringGetBytes(str, CFMakeRange(0L, ((stringU16Length < maxLength) ? stringU16Length : maxLength)), kCFStringEncodingUTF8, (UInt8)'?', (Boolean)0, (UInt8 *)utf8Buffer, maxBytes, &usedBytes);
568 | if(usedBytes == maxBytes) { strncpy(utf8Buffer + usedBytes, RKL_UTF8_ELLIPSE, ((size_t)(RKL_DTRACE_REGEXUTF8_SIZE) - (size_t)usedBytes) - 2UL); } else { utf8Buffer[usedBytes] = (char)0; }
569 | }
570 |
571 | #else // _RKL_DTRACE_ENABLED
572 |
573 | #define rkl_dtrace_incrementEventID()
574 | #define rkl_dtrace_compiledRegexCache(a0, a1, a2, a3, a4, a5)
575 | #define rkl_dtrace_utf16ConversionCache(a0, a1, a2, a3, a4)
576 | #define rkl_dtrace_getRegexUTF8(str, buf)
577 | #define rkl_dtrace_addLookupFlag(a,b)
578 |
579 | #endif // _RKL_DTRACE_ENABLED
580 |
581 | ////////////
582 | #pragma mark -
583 | #pragma mark RegexKitLite low-level internal functions
584 |
585 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
586 | // IMPORTANT! Should only be called with cacheSpinLock already locked!
587 | // ----------
588 |
589 | static RKLCacheSlot *rkl_getCachedRegex(NSString *regexString, RKLRegexOptions options, NSError **error, id *exception) {
590 | RKLCacheSlot *cacheSlot = NULL;
591 | CFHashCode regexHash = 0UL;
592 | int32_t status = 0;
593 |
594 | RKLCDelayedAssert((cacheSpinLock != 0) && (regexString != NULL), exception, exitNow);
595 |
596 | // Fast path the common case where this regex is exactly the same one used last time.
597 | // The pointer equality test is valid under these circumstances since the cacheSlot->regexString is an immutable copy.
598 | // If the regexString argument is mutable, this test will fail, and we'll use the the slow path cache check below.
599 | if(RKL_EXPECTED(lastCacheSlot != NULL, 1L) && RKL_EXPECTED(lastCacheSlot->options == options, 1L) && RKL_EXPECTED(lastCacheSlot->icu_regex != NULL, 1L) && RKL_EXPECTED(lastCacheSlot->regexString != NULL, 1L) && RKL_EXPECTED(lastCacheSlot->regexString == (CFStringRef)regexString, 1L)) {
600 | rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(lastCacheSlot - &rkl_cacheSlots[0])][0], lastCacheSlot->options, (int)lastCacheSlot->captureCount, 1, 0, NULL);
601 | return(lastCacheSlot);
602 | }
603 |
604 | lastCacheSlot = NULL;
605 | regexHash = CFHash((CFTypeRef)regexString);
606 | cacheSlot = &rkl_cacheSlots[(regexHash % (CFHashCode)(RKL_CACHE_SIZE))]; // Retrieve the cache slot for this regex.
607 |
608 | // Return the cached entry if it's a match, otherwise clear the slot and create a new ICU regex in its place.
609 | // If regexString is mutable, the pointer equality test will fail, and CFEqual() is used to determine true
610 | // equality with the immutable cacheSlot copy. CFEqual() performs a slow character by character check.
611 | if(RKL_EXPECTED(cacheSlot->options == options, 1L) && RKL_EXPECTED(cacheSlot->icu_regex != NULL, 1L) && RKL_EXPECTED(cacheSlot->regexString != NULL, 1L) && (RKL_EXPECTED(cacheSlot->regexString == (CFStringRef)regexString, 1L) || RKL_EXPECTED(CFEqual((CFTypeRef)regexString, (CFTypeRef)cacheSlot->regexString) == YES, 1L))) {
612 | lastCacheSlot = cacheSlot;
613 | rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(lastCacheSlot - &rkl_cacheSlots[0])][0], lastCacheSlot->options, (int)lastCacheSlot->captureCount, 1, 0, NULL);
614 | return(cacheSlot);
615 | }
616 |
617 | rkl_clearCacheSlotRegex(cacheSlot);
618 |
619 | if(RKL_EXPECTED((cacheSlot->regexString = CFStringCreateCopy(NULL, (CFStringRef)regexString)) == NULL, 0L)) { goto exitNow; } ; // Get a cheap immutable copy.
620 | rkl_dtrace_getRegexUTF8(cacheSlot->regexString, &rkl_dtrace_regexUTF8[(cacheSlot - &rkl_cacheSlots[0])][0]);
621 | cacheSlot->options = options;
622 |
623 | CFIndex regexStringU16Length = CFStringGetLength(cacheSlot->regexString); // In UTF16 code units.
624 | UParseError parseError = (UParseError){-1, -1, {0}, {0}};
625 | const UniChar *regexUniChar = NULL;
626 |
627 | if(RKL_EXPECTED(regexStringU16Length >= (CFIndex)INT_MAX, 0L)) { *exception = [NSException exceptionWithName:NSRangeException reason:@"Regex string length exceeds INT_MAX" userInfo:NULL]; goto exitNow; }
628 |
629 | // Try to quickly obtain regexString in UTF16 format.
630 | if((regexUniChar = CFStringGetCharactersPtr(cacheSlot->regexString)) == NULL) { // We didn't get the UTF16 pointer quickly and need to perform a full conversion in a temp buffer.
631 | UniChar *uniCharBuffer = NULL;
632 | if(((size_t)regexStringU16Length * sizeof(UniChar)) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((uniCharBuffer = (UniChar *)alloca( (size_t)regexStringU16Length * sizeof(UniChar) )) == NULL, 0L)) { goto exitNow; } } // Try to use the stack.
633 | else { if(RKL_EXPECTED((uniCharBuffer = (UniChar *)rkl_realloc(&scratchBuffer[0], (size_t)regexStringU16Length * sizeof(UniChar), 0UL)) == NULL, 0L)) { goto exitNow; } } // Otherwise use the heap.
634 | CFStringGetCharacters(cacheSlot->regexString, CFMakeRange(0L, regexStringU16Length), uniCharBuffer); // Convert regexString to UTF16.
635 | regexUniChar = uniCharBuffer;
636 | }
637 |
638 | // Create the ICU regex.
639 | if(RKL_EXPECTED((cacheSlot->icu_regex = RKL_ICU_FUNCTION_APPEND(uregex_open)(regexUniChar, (int32_t)regexStringU16Length, options, &parseError, &status)) == NULL, 0L)) { goto exitNow; }
640 | if(RKL_EXPECTED(status <= U_ZERO_ERROR, 1L)) { cacheSlot->captureCount = (NSInteger)RKL_ICU_FUNCTION_APPEND(uregex_groupCount)(cacheSlot->icu_regex, &status); }
641 | if(RKL_EXPECTED(status <= U_ZERO_ERROR, 1L)) { lastCacheSlot = cacheSlot; }
642 |
643 | exitNow:
644 | if(RKL_EXPECTED(scratchBuffer[0] != NULL, 0L)) { scratchBuffer[0] = rkl_free(&scratchBuffer[0]); }
645 | if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L)) { rkl_clearCacheSlotRegex(cacheSlot); cacheSlot = NULL; if(error != NULL) { *error = rkl_NSErrorForRegex(regexString, options, &parseError, status); } }
646 |
647 | #ifdef _RKL_DTRACE_ENABLED
648 | if(RKL_EXPECTED(cacheSlot != NULL, 1L)) { rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(cacheSlot - &rkl_cacheSlots[0])][0], cacheSlot->options, (int)cacheSlot->captureCount, 0, status, NULL); }
649 | else { char regexUTF8[(RKL_DTRACE_REGEXUTF8_SIZE)]; const char *err = NULL; if(status != U_ZERO_ERROR) { err = RKL_ICU_FUNCTION_APPEND(u_errorName)(status); } rkl_dtrace_getRegexUTF8((CFStringRef)regexString, regexUTF8); rkl_dtrace_compiledRegexCache(regexUTF8, options, -1, -1, status, err); }
650 | #endif // _RKL_DTRACE_ENABLED
651 |
652 | return(cacheSlot);
653 | }
654 |
655 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
656 | // IMPORTANT! Should only be called with cacheSpinLock already locked!
657 | // ----------
658 |
659 | static NSUInteger rkl_setCacheSlotToString(RKLCacheSlot *cacheSlot, const NSRange *range, int32_t *status, id *exception RKL_UNUSED_ASSERTION_ARG) {
660 | RKLCDelayedAssert((cacheSlot != NULL) && (cacheSlot->setToString != NULL) && ((range != NULL) && (NSEqualRanges(*range, NSNotFoundRange) == NO)) && (status != NULL), exception, exitNow);
661 | const UniChar *stringUniChar = NULL;
662 | #ifdef _RKL_DTRACE_ENABLED
663 | unsigned int lookupResultFlags = 0U;
664 | #endif
665 |
666 | if(cacheSlot->setToNeedsConversion == 0U) {
667 | if(RKL_EXPECTED((stringUniChar = CFStringGetCharactersPtr(cacheSlot->setToString)) == NULL, 0L)) { cacheSlot->setToNeedsConversion = 1U; }
668 | else { if(RKL_EXPECTED(cacheSlot->setToUniChar != stringUniChar, 0L)) { cacheSlot->setToRange = NSNotFoundRange; cacheSlot->setToUniChar = stringUniChar; } goto setRegexText; }
669 | }
670 | rkl_dtrace_addLookupFlag(lookupResultFlags, RKLConversionRequiredLookupFlag);
671 |
672 | NSUInteger useFixedBuffer = (cacheSlot->setToLength < (CFIndex)(RKL_FIXED_LENGTH)) ? 1UL : 0UL;
673 | RKLBuffer *buffer = useFixedBuffer ? &fixedBuffer : &dynamicBuffer;
674 | rkl_dtrace_addLookupFlag(lookupResultFlags, (useFixedBuffer ? 0U : RKLDynamicBufferLookupFlag));
675 |
676 | if((cacheSlot->setToUniChar != NULL) && ((cacheSlot->setToString == buffer->string) || ((cacheSlot->setToLength == buffer->length) && (cacheSlot->setToHash == buffer->hash)))) { rkl_dtrace_addLookupFlag(lookupResultFlags, RKLCacheHitLookupFlag); goto setRegexText; }
677 |
678 | if(RKL_EXPECTED((stringUniChar = CFStringGetCharactersPtr(cacheSlot->setToString)) != NULL, 0L)) { cacheSlot->setToNeedsConversion = 0U; cacheSlot->setToRange = NSNotFoundRange; cacheSlot->setToUniChar = stringUniChar; goto setRegexText; }
679 |
680 | rkl_clearBuffer(buffer, 0UL);
681 |
682 | if(useFixedBuffer == 0U) {
683 | RKLCDelayedAssert(buffer == &dynamicBuffer, exception, exitNow);
684 | RKL_STRONG_REF void *p = (RKL_STRONG_REF void *)dynamicBuffer.uniChar;
685 | if(RKL_EXPECTED((dynamicBuffer.uniChar = (RKL_STRONG_REF UniChar *)rkl_realloc(&p, ((size_t)cacheSlot->setToLength * sizeof(UniChar)), 0UL)) == NULL, 0L)) { goto exitNow; } // Resize the buffer.
686 | }
687 |
688 | RKLCDelayedAssert(buffer->uniChar != NULL, exception, exitNow);
689 | CFStringGetCharacters(cacheSlot->setToString, CFMakeRange(0L, cacheSlot->setToLength), (UniChar *)buffer->uniChar); // Convert to a UTF16 string.
690 |
691 | RKLCDelayedAssert(buffer->string == NULL, exception, exitNow);
692 | if(RKL_EXPECTED((buffer->string = (CFStringRef)CFRetain((CFTypeRef)cacheSlot->setToString)) == NULL, 0L)) { goto exitNow; }
693 | buffer->hash = cacheSlot->setToHash;
694 | buffer->length = cacheSlot->setToLength;
695 |
696 | cacheSlot->setToUniChar = buffer->uniChar;
697 | cacheSlot->setToRange = NSNotFoundRange;
698 |
699 | setRegexText:
700 | if(NSEqualRanges(cacheSlot->setToRange, *range) == NO) {
701 | RKLCDelayedAssert((cacheSlot->icu_regex != NULL) && (cacheSlot->setToUniChar != NULL) && (NSMaxRange(*range) <= (NSUInteger)cacheSlot->setToLength) && (cacheSlot->setToRange.length <= INT_MAX), exception, exitNow);
702 | cacheSlot->lastFindRange = cacheSlot->lastMatchRange = NSNotFoundRange;
703 | cacheSlot->setToRange = *range;
704 | RKL_ICU_FUNCTION_APPEND(uregex_setText)(cacheSlot->icu_regex, cacheSlot->setToUniChar + cacheSlot->setToRange.location, (int32_t)cacheSlot->setToRange.length, status);
705 | rkl_dtrace_addLookupFlag(lookupResultFlags, RKLSetTextLookupFlag);
706 | if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto exitNow; }
707 | }
708 |
709 | rkl_dtrace_utf16ConversionCache(lookupResultFlags, cacheSlot->setToString, cacheSlot->setToRange.location, cacheSlot->setToRange.length, cacheSlot->setToLength);
710 | return(1UL);
711 |
712 | exitNow:
713 | return(0UL);
714 | }
715 |
716 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
717 | // IMPORTANT! Should only be called with cacheSpinLock already locked!
718 | // ----------
719 |
720 | static RKLCacheSlot *rkl_getCachedRegexSetToString(NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status) {
721 | RKLCacheSlot *cacheSlot = NULL;
722 | RKLCDelayedAssert((regexString != NULL) && (exception != NULL) && (status != NULL) && (matchLengthPtr != NULL), exception, exitNow);
723 |
724 | // Fast path the common case where this regex is exactly the same one used last time.
725 | if(RKL_EXPECTED(lastCacheSlot != NULL, 1L) && RKL_EXPECTED(lastCacheSlot->icu_regex != NULL, 1L) && RKL_EXPECTED(lastCacheSlot->regexString == (CFStringRef)regexString, 1L) && RKL_EXPECTED(lastCacheSlot->options == options, 1L)) { cacheSlot = lastCacheSlot; rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(cacheSlot - &rkl_cacheSlots[0])][0], cacheSlot->options, (int)cacheSlot->captureCount, 1, 0, NULL); }
726 | else { lastCacheSlot = NULL; if(RKL_EXPECTED((cacheSlot = rkl_getCachedRegex(regexString, options, error, exception)) == NULL, 0L)) { goto exitNow; } }
727 | RKLCDelayedAssert((cacheSlot != NULL) && (cacheSlot->icu_regex != NULL) && (cacheSlot->regexString != NULL) && (cacheSlot->captureCount >= 0L) && (cacheSlot == lastCacheSlot), exception, exitNow);
728 |
729 | // Optimize the case where the string to search (matchString) is immutable and the setToString immutable copy is the same string with its reference count incremented.
730 | NSUInteger isSetTo = ((cacheSlot->setToString != NULL) && (cacheSlot->setToString == (CFStringRef)matchString)) ? 1UL : 0UL;
731 | CFIndex matchLength = ((isSetTo == 1UL) && (cacheSlot->setToIsImmutable == 1U)) ? cacheSlot->setToLength : CFStringGetLength((CFStringRef)matchString);
732 |
733 | *matchLengthPtr = (NSUInteger)matchLength;
734 | if(matchRange->length == NSUIntegerMax) { matchRange->length = (NSUInteger)matchLength; } // For convenience, allow NSUIntegerMax == string length.
735 |
736 | if(RKL_EXPECTED((NSUInteger)matchLength < NSMaxRange(*matchRange), 0L)) { goto exitNow; } // The match range is out of bounds for the string. performRegexOp will catch and report the problem.
737 |
738 | if((cacheSlot->setToIsImmutable == 0U) && (cacheSlot->setToString != NULL) && ((cacheSlot->setToLength != CFStringGetLength(cacheSlot->setToString)) || (cacheSlot->setToHash != CFHash((CFTypeRef)cacheSlot->setToString)))) { isSetTo = 0UL; }
739 | else { // If the first pointer equality check failed, check the hash and length.
740 | if(((isSetTo == 0UL) || (cacheSlot->setToIsImmutable == 0U)) && (cacheSlot->setToString != NULL)) { isSetTo = ((cacheSlot->setToLength == matchLength) && (cacheSlot->setToHash == CFHash((CFTypeRef)matchString))) ? 1UL : 0UL; }
741 |
742 | if(isSetTo == 1UL) { if(RKL_EXPECTED(rkl_setCacheSlotToString(cacheSlot, matchRange, status, exception) == 0UL, 0L)) { cacheSlot = NULL; if(*exception == NULL) { *exception = (id)RKLCAssertDictionary(@"Failed to set up UTF16 buffer."); } } goto exitNow; }
743 | }
744 |
745 | // Sometimes the range that the regex is set to isn't right, in which case we don't want to clear the cache slot. Otherwise, flush it out.
746 | if((cacheSlot->setToString != NULL) && (isSetTo == 0UL)) { rkl_clearCacheSlotSetTo(cacheSlot); }
747 |
748 | if(cacheSlot->setToString == NULL) {
749 | cacheSlot->setToString = (CFStringRef)CFRetain((CFTypeRef)matchString);
750 | RKLCDelayedAssert(cacheSlot->setToString != NULL, exception, exitNow);
751 | cacheSlot->setToUniChar = CFStringGetCharactersPtr(cacheSlot->setToString);
752 | cacheSlot->setToNeedsConversion = (cacheSlot->setToUniChar == NULL) ? 1U : 0U;
753 | cacheSlot->setToIsImmutable = (rkl_CFStringIsMutable(cacheSlot->setToString) == YES) ? 0U : 1U; // If RKL_FAST_MUTABLE_CHECK is not defined then setToIsImmutable will always be set to '0', or in other words mutable..
754 | cacheSlot->setToHash = CFHash((CFTypeRef)cacheSlot->setToString);
755 | cacheSlot->setToRange = NSNotFoundRange;
756 | cacheSlot->setToLength = matchLength;
757 | }
758 |
759 | if(RKL_EXPECTED(rkl_setCacheSlotToString(cacheSlot, matchRange, status, exception) == 0UL, 0L)) { cacheSlot = NULL; if(*exception == NULL) { *exception = (id)RKLCAssertDictionary(@"Failed to set up UTF16 buffer."); } goto exitNow; }
760 |
761 | exitNow:
762 | return(cacheSlot);
763 | }
764 |
765 | #ifdef RKL_HAVE_CLEANUP
766 |
767 | // rkl_cleanup_cacheSpinLockStatus takes advantage of GCC's 'cleanup' variable attribute. When an 'auto' variable with the 'cleanup' attribute goes out of scope,
768 | // GCC arranges to have the designated function called. In this case, we make sure that if rkl_cacheSpinLock was locked that it was also unlocked.
769 | // If rkl_cacheSpinLock was locked, but the cacheSpinLockStatus unlocked flag was not set, we force cacheSpinLock unlocked with a call to OSSpinLockUnlock.
770 | // This is not a panacea for preventing mutex usage errors. Old style ObjC exceptions will bypass the cleanup call, but newer C++ style ObjC exceptions should cause the cleanup function to be called during the stack unwind.
771 |
772 | // We do not depend on this cleanup function being called. It is used only as an extra safety net. It is probably a bug in RegexKitLite if it is ever invoked and forced to take some kind of protective action.
773 |
774 | volatile NSUInteger rkl_debugCacheSpinLockCount = 0UL;
775 |
776 | void rkl_debugCacheSpinLock (void) RKL_ATTRIBUTES(used, noinline, visibility("default"));
777 | static void rkl_cleanup_cacheSpinLockStatus (volatile NSUInteger *cacheSpinLockStatusPtr) RKL_ATTRIBUTES(used);
778 |
779 | void rkl_debugCacheSpinLock(void) {
780 | rkl_debugCacheSpinLockCount++; // This is here primarily to prevent the optimizer from optimizing away the function.
781 | }
782 |
783 | static void rkl_cleanup_cacheSpinLockStatus(volatile NSUInteger *cacheSpinLockStatusPtr) {
784 | static NSUInteger didPrintForcedUnlockWarning = 0UL, didPrintNotLockedWarning = 0UL;
785 | NSUInteger cacheSpinLockStatus = *cacheSpinLockStatusPtr;
786 |
787 | if(RKL_EXPECTED((cacheSpinLockStatus & RKLUnlockedCacheSpinLock) == 0UL, 0L) && RKL_EXPECTED((cacheSpinLockStatus & RKLLockedCacheSpinLock) != 0UL, 1L)) {
788 | if(cacheSpinLock != 0) {
789 | if(didPrintForcedUnlockWarning == 0UL) { didPrintForcedUnlockWarning = 1UL; NSLog(@"[RegexKitLite] Unusual condition detected: Recorded that cacheSpinLock was locked, but for some reason it was not unlocked. Forcibly unlocking cacheSpinLock. Set a breakpoint at rkl_debugCacheSpinLock to debug. This warning is only printed once."); }
790 | rkl_debugCacheSpinLock(); // Since this is an unusual condition, offer an attempt to catch it before we unlock.
791 | OSSpinLockUnlock(&cacheSpinLock);
792 | } else {
793 | if(didPrintNotLockedWarning == 0UL) { didPrintNotLockedWarning = 1UL; NSLog(@"[RegexKitLite] Unusual condition detected: Recorded that cacheSpinLock was locked, but for some reason it was not unlocked, yet cacheSpinLock is currently not locked? Set a breakpoint at rkl_debugCacheSpinLock to debug. This warning is only printed once."); }
794 | rkl_debugCacheSpinLock();
795 | }
796 | }
797 | }
798 |
799 | #endif // RKL_HAVE_CLEANUP
800 |
801 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
802 | // ----------
803 |
804 | static id rkl_performRegexOp(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result) {
805 | volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) cacheSpinLockStatus = 0UL;
806 |
807 | NSUInteger replaceMutable = 0UL;
808 | RKLRegexOp maskedRegexOp = (regexOp & RKLMaskOp);
809 |
810 | if((error != NULL) && (*error != NULL)) { *error = NULL; }
811 |
812 | if(RKL_EXPECTED(regexString == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The regular expression argument is NULL."); }
813 | if(RKL_EXPECTED(matchString == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInternalInconsistencyException, @"The match string argument is NULL."); }
814 | if((maskedRegexOp == RKLReplaceOp) && (replacementString == NULL)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The replacement string argument is NULL."); }
815 |
816 | id resultObject = NULL, exception = NULL;
817 | int32_t status = U_ZERO_ERROR;
818 | RKLCacheSlot *cacheSlot = NULL;
819 | NSUInteger stringU16Length = 0UL;
820 | NSRange stackRanges[2048];
821 | RKLFindAll findAll;
822 |
823 |
824 | // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS!
825 | // ----------
826 | OSSpinLockLock(&cacheSpinLock); // Grab the lock and get cache entry.
827 | cacheSpinLockStatus |= RKLLockedCacheSpinLock;
828 | rkl_dtrace_incrementEventID();
829 |
830 | if(RKL_EXPECTED((cacheSlot = rkl_getCachedRegexSetToString(regexString, options, matchString, &stringU16Length, matchRange, error, &exception, &status)) == NULL, 0L)) { stringU16Length = (NSUInteger)CFStringGetLength((CFStringRef)matchString); }
831 | if(RKL_EXPECTED(matchRange->length == NSUIntegerMax, 1L)) { matchRange->length = stringU16Length; } // For convenience.
832 | if(RKL_EXPECTED(stringU16Length < NSMaxRange(*matchRange), 0L) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"Range or index out of bounds"); goto exitNow; }
833 | if(RKL_EXPECTED(stringU16Length >= (NSUInteger)INT_MAX, 0L) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"String length exceeds INT_MAX"); goto exitNow; }
834 | if(((maskedRegexOp == RKLRangeOp) || (maskedRegexOp == RKLArrayOfStringsOp)) && RKL_EXPECTED(cacheSlot != NULL, 1L) && (RKL_EXPECTED(capture < 0L, 0L) || RKL_EXPECTED(capture > cacheSlot->captureCount, 0L)) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The capture argument is not valid."); goto exitNow; }
835 | if(RKL_EXPECTED(cacheSlot == NULL, 0L) || RKL_EXPECTED(status > U_ZERO_ERROR, 0L) || RKL_EXPECTED(exception != NULL, 0L)) { goto exitNow; }
836 |
837 | RKLCDelayedAssert((cacheSlot != NULL) && (cacheSlot->icu_regex != NULL) && (cacheSlot->regexString != NULL) && (cacheSlot->captureCount >= 0L) && (cacheSlot->setToString != NULL) && (cacheSlot->setToLength >= 0L) && (cacheSlot->setToUniChar != NULL) && ((CFIndex)NSMaxRange(cacheSlot->setToRange) <= cacheSlot->setToLength), &exception, exitNow);
838 |
839 | #ifndef NS_BLOCK_ASSERTIONS
840 | if(cacheSlot->setToNeedsConversion == 0U) { RKLCDelayedAssert((cacheSlot->setToUniChar == CFStringGetCharactersPtr(cacheSlot->setToString)), &exception, exitNow); }
841 | else {
842 | RKLBuffer *buffer = (cacheSlot->setToLength < (CFIndex)(RKL_FIXED_LENGTH)) ? &fixedBuffer : &dynamicBuffer;
843 | RKLCDelayedAssert((cacheSlot->setToHash == buffer->hash) && (cacheSlot->setToLength == buffer->length) && (cacheSlot->setToUniChar == buffer->uniChar), &exception, exitNow);
844 | }
845 | #endif
846 |
847 | switch(maskedRegexOp) {
848 | case RKLRangeOp:
849 | if((rkl_search(cacheSlot, matchRange, 0UL, &exception, &status) == NO) || (RKL_EXPECTED(status > U_ZERO_ERROR, 0L))) { *(NSRange *)result = NSNotFoundRange; goto exitNow; }
850 | if(RKL_EXPECTED(capture == 0L, 1L)) { *(NSRange *)result = cacheSlot->lastMatchRange; } else { if(RKL_EXPECTED(rkl_getRangeForCapture(cacheSlot, &status, (int32_t)capture, (NSRange *)result) > U_ZERO_ERROR, 0L)) { goto exitNow; } }
851 | break;
852 |
853 | case RKLSplitOp: // Fall-thru...
854 | case RKLArrayOfStringsOp: // Fall-thru...
855 | case RKLCapturesArrayOp: // Fall-thru...
856 | case RKLArrayOfCapturesOp:
857 | findAll = rkl_makeFindAll(stackRanges, *matchRange, 2048L, (2048UL * sizeof(NSRange)), 0UL, (void **)&scratchBuffer[0], &scratchBuffer[1], &scratchBuffer[2], 0L, capture, ((maskedRegexOp == RKLCapturesArrayOp) ? 1L : NSIntegerMax));
858 |
859 | if(RKL_EXPECTED(rkl_findRanges(cacheSlot, regexOp, &findAll, &exception, &status) == NO, 1L)) {
860 | if(RKL_EXPECTED(findAll.found == 0L, 0L)) { resultObject = [NSArray array]; } else { resultObject = rkl_makeArray(cacheSlot, regexOp, &findAll, &exception); }
861 | }
862 |
863 | if(RKL_EXPECTED(scratchBuffer[0] != NULL, 0L)) { scratchBuffer[0] = rkl_free(&scratchBuffer[0]); }
864 | if(RKL_EXPECTED(scratchBuffer[1] != NULL, 0L)) { scratchBuffer[1] = rkl_free(&scratchBuffer[1]); }
865 | if(RKL_EXPECTED(scratchBuffer[2] != NULL, 0L)) { scratchBuffer[2] = rkl_free(&scratchBuffer[2]); }
866 |
867 | break;
868 |
869 | case RKLReplaceOp: resultObject = rkl_replaceString(cacheSlot, matchString, stringU16Length, replacementString, (NSUInteger)CFStringGetLength((CFStringRef)replacementString), (NSUInteger *)result, (replaceMutable = (((regexOp & RKLReplaceMutable) != 0) ? 1UL : 0UL)), &exception, &status); break;
870 | default: exception = RKLCAssertDictionary(@"Unknown regexOp code."); break;
871 | }
872 |
873 | exitNow:
874 | OSSpinLockUnlock(&cacheSpinLock);
875 | cacheSpinLockStatus |= RKLUnlockedCacheSpinLock;
876 |
877 | if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L) && RKL_EXPECTED(exception == NULL, 0L)) { exception = rkl_NSExceptionForRegex(regexString, options, NULL, status); } // If we had a problem, prepare an exception to be thrown.
878 | if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); } // If there is an exception, throw it at this point.
879 | // If we're working on a mutable string and there were successful matches/replacements, then we still have work to do.
880 | // This is done outside the cache lock and with the objc replaceCharactersInRange:withString: method because Core Foundation
881 | // does not assert that the string we are attempting to update is actually a mutable string, whereas Foundation ensures
882 | // the object receiving the message is a mutable string and throws an exception if we're attempting to modify an immutable string.
883 | if(RKL_EXPECTED(replaceMutable == 1UL, 0L) && RKL_EXPECTED(*((NSUInteger *)result) > 0UL, 1L)) { NSCParameterAssert(resultObject != NULL); [matchString replaceCharactersInRange:*matchRange withString:resultObject]; }
884 |
885 | return(resultObject);
886 | }
887 |
888 | static void rkl_handleDelayedAssert(id self, SEL _cmd, id exception) {
889 | if(RKL_EXPECTED(exception != NULL, 0L)) {
890 | if([exception isKindOfClass:[NSException class]]) { [[NSException exceptionWithName:[exception name] reason:rkl_stringFromClassAndMethod(self, _cmd, [exception reason]) userInfo:[exception userInfo]] raise]; }
891 | else {
892 | id functionString = [exception objectForKey:@"function"], fileString = [exception objectForKey:@"file"], descriptionString = [exception objectForKey:@"description"], lineNumber = [exception objectForKey:@"line"];
893 | NSCParameterAssert((functionString != NULL) && (fileString != NULL) && (descriptionString != NULL) && (lineNumber != NULL));
894 | [[NSAssertionHandler currentHandler] handleFailureInFunction:functionString file:fileString lineNumber:(NSInteger)[lineNumber longValue] description:@"%@", descriptionString];
895 | }
896 | }
897 | }
898 |
899 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
900 | // IMPORTANT! Should only be called from rkl_performRegexOp() or rkl_findRanges().
901 | // ----------
902 |
903 | static NSUInteger rkl_search(RKLCacheSlot *cacheSlot, NSRange *searchRange, NSUInteger updateSearchRange, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) {
904 | NSUInteger foundMatch = 0UL, searchEqualsEndOfRange = (RKL_EXPECTED(NSEqualRanges(*searchRange, NSMakeRange(NSMaxRange(cacheSlot->setToRange), 0UL)) == YES, 0L) ? 1UL : 0UL);
905 |
906 | if((NSEqualRanges(*searchRange, cacheSlot->lastFindRange) == YES) || (searchEqualsEndOfRange == 1UL)) { foundMatch = (((cacheSlot->lastMatchRange.location == NSNotFound) || (searchEqualsEndOfRange == 1UL)) ? 0UL : 1UL);}
907 | else { // Only perform an expensive 'find' operation iff the current find range is different than the last find range.
908 | NSUInteger findLocation = (searchRange->location - cacheSlot->setToRange.location);
909 | RKLCDelayedAssert(((searchRange->location >= cacheSlot->setToRange.location)) && (NSRangeInsideRange(*searchRange, cacheSlot->setToRange) == YES) && (findLocation < INT_MAX) && (findLocation <= cacheSlot->setToRange.length), exception, exitNow);
910 |
911 | RKL_PREFETCH_UNICHAR(cacheSlot->setToUniChar, searchRange->location); // Spool up the CPU caches.
912 |
913 | // Using uregex_findNext can be a slight performance win.
914 | NSUInteger useFindNext = ((searchRange->location == (NSMaxRange(cacheSlot->lastMatchRange) + (((cacheSlot->lastMatchRange.length == 0UL) && (cacheSlot->lastMatchRange.location < NSMaxRange(cacheSlot->setToRange))) ? 1UL : 0UL))) ? 1UL : 0UL);
915 |
916 | cacheSlot->lastFindRange = *searchRange;
917 | if(RKL_EXPECTED(useFindNext == 0UL, 0L)) { if(RKL_EXPECTED((RKL_ICU_FUNCTION_APPEND(uregex_find) (cacheSlot->icu_regex, (int32_t)findLocation, status) == NO), 0L) || RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto finishedFind; } }
918 | else { if(RKL_EXPECTED((RKL_ICU_FUNCTION_APPEND(uregex_findNext)(cacheSlot->icu_regex, status) == NO), 0L) || RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto finishedFind; } }
919 | foundMatch = 1UL;
920 |
921 | if(RKL_EXPECTED(rkl_getRangeForCapture(cacheSlot, status, 0, &cacheSlot->lastMatchRange) > U_ZERO_ERROR, 0L)) { goto finishedFind; }
922 | RKLCDelayedAssert(NSRangeInsideRange(cacheSlot->lastMatchRange, *searchRange) == YES, exception, exitNow);
923 | }
924 |
925 | finishedFind:
926 | if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { foundMatch = 0UL; cacheSlot->lastFindRange = NSNotFoundRange; }
927 |
928 | if(foundMatch == 0UL) { cacheSlot->lastMatchRange = NSNotFoundRange; if(updateSearchRange == 1UL) { *searchRange = NSMakeRange(NSMaxRange(*searchRange), 0UL); } }
929 | else {
930 | RKLCDelayedAssert(NSRangeInsideRange(cacheSlot->lastMatchRange, *searchRange) == YES, exception, exitNow);
931 | if(updateSearchRange == 1UL) {
932 | NSUInteger nextLocation = (NSMaxRange(cacheSlot->lastMatchRange) + (((cacheSlot->lastMatchRange.length == 0UL) && (cacheSlot->lastMatchRange.location < NSMaxRange(cacheSlot->setToRange))) ? 1UL : 0UL)), locationDiff = nextLocation - searchRange->location;
933 | RKLCDelayedAssert((((locationDiff > 0UL) || ((locationDiff == 0UL) && (cacheSlot->lastMatchRange.location == NSMaxRange(cacheSlot->setToRange)))) && (locationDiff <= searchRange->length)), exception, exitNow);
934 | searchRange->location = nextLocation;
935 | searchRange->length -= locationDiff;
936 | }
937 | }
938 |
939 | #ifndef NS_BLOCK_ASSERTIONS
940 | exitNow:
941 | #endif
942 | return(foundMatch);
943 | }
944 |
945 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
946 | // IMPORTANT! Should only be called from rkl_doFindOp().
947 | // ----------
948 |
949 | static BOOL rkl_findRanges(RKLCacheSlot *cacheSlot, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception, int32_t *status) {
950 | BOOL returnWithError = YES;
951 | RKLCDelayedAssert((((cacheSlot != NULL) && (cacheSlot->icu_regex != NULL) && (cacheSlot->setToUniChar != NULL) && (cacheSlot->captureCount >= 0L) && (cacheSlot->setToRange.location != NSNotFound)) && (status != NULL) && ((findAll != NULL) && (findAll->found == 0L) && ((findAll->capacity >= 0L) && (((findAll->capacity > 0L) || (findAll->size > 0UL)) ? ((findAll->ranges != NULL) && (findAll->capacity > 0L) && (findAll->size > 0UL)) : 1)) && (findAll->rangesScratchBuffer != NULL) && ((findAll->capture >= 0L) && (findAll->capture <= cacheSlot->captureCount)))), exception, exitNow);
952 |
953 | if(RKL_EXPECTED(cacheSlot->setToLength == 0L, 0L) || RKL_EXPECTED(cacheSlot->setToRange.length == 0UL, 0L)) { returnWithError = NO; goto exitNow; }
954 |
955 | NSInteger captureCount = cacheSlot->captureCount;
956 | RKLRegexOp maskedRegexOp = (regexOp & RKLMaskOp);
957 | NSUInteger lastLocation = findAll->findInRange.location;
958 | NSRange searchRange = findAll->findInRange;
959 |
960 | for(findAll->found = 0L; (findAll->found < findAll->findUpTo) && ((findAll->found < findAll->capacity) || (findAll->found == 0L)); findAll->found++) {
961 | NSInteger loopCapture, shouldBreak = 0L;
962 |
963 | if(RKL_EXPECTED(findAll->found >= ((findAll->capacity - ((captureCount + 2L) * 4L)) - 4L), 0L)) { if(RKL_EXPECTED(rkl_growFindRanges(cacheSlot, lastLocation, findAll, exception) == 0UL, 0L)) { goto exitNow; } }
964 |
965 | RKLCDelayedAssert((searchRange.location != NSNotFound) && (NSRangeInsideRange(searchRange, cacheSlot->setToRange) == YES) && (NSRangeInsideRange(findAll->findInRange, cacheSlot->setToRange) == YES), exception, exitNow);
966 |
967 | // This fixes a 'bug' that is also present in ICU's uregex_split(). 'Bug', in this case, means that the results of a split operation can differ from those that perl's split() creates for the same input.
968 | // "I|at|ice I eat rice" split using the regex "\b\s*" demonstrates the problem. ICU bug http://bugs.icu-project.org/trac/ticket/6826
969 | // ICU : "", "I", "|", "at", "|", "ice", "", "I", "", "eat", "", "rice" <- Results that RegexKitLite used to produce.
970 | // PERL: "I", "|", "at", "|", "ice", "I", "eat", "rice" <- Results that RegexKitLite now produces.
971 | do { if((rkl_search(cacheSlot, &searchRange, 1UL, exception, status) == NO) || (RKL_EXPECTED(*status > U_ZERO_ERROR, 0L))) { shouldBreak = 1L; } }
972 | while((maskedRegexOp == RKLSplitOp) && RKL_EXPECTED(shouldBreak == 0L, 1L) && RKL_EXPECTED(cacheSlot->lastMatchRange.length == 0UL, 0L) && RKL_EXPECTED((cacheSlot->lastMatchRange.location - lastLocation) == 0UL, 0L));
973 | if(RKL_EXPECTED(shouldBreak == 1L, 0L)) { break; }
974 |
975 | RKLCDelayedAssert((searchRange.location != NSNotFound) && (NSRangeInsideRange(searchRange, cacheSlot->setToRange) == YES) && (NSRangeInsideRange(findAll->findInRange, cacheSlot->setToRange) == YES) && (NSRangeInsideRange(searchRange, findAll->findInRange) == YES), exception, exitNow);
976 | RKLCDelayedAssert((NSRangeInsideRange(cacheSlot->lastFindRange, cacheSlot->setToRange) == YES) && (NSRangeInsideRange(cacheSlot->lastMatchRange, cacheSlot->setToRange) == YES) && (NSRangeInsideRange(cacheSlot->lastMatchRange, findAll->findInRange) == YES), exception, exitNow);
977 | RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->capacity >= 0L) && ((findAll->found + (captureCount + 3L) + 1L) < (findAll->capacity - 2L)), exception, exitNow);
978 |
979 | switch(maskedRegexOp) {
980 | case RKLArrayOfStringsOp:
981 | if(findAll->capture == 0L) { findAll->ranges[findAll->found] = cacheSlot->lastMatchRange; } else { if(RKL_EXPECTED(rkl_getRangeForCapture(cacheSlot, status, (int32_t)findAll->capture, &findAll->ranges[findAll->found]) > U_ZERO_ERROR, 0L)) { goto exitNow; } }
982 | break;
983 |
984 | case RKLSplitOp: // Fall-thru...
985 | case RKLCapturesArrayOp: // Fall-thru...
986 | case RKLArrayOfCapturesOp:
987 | findAll->ranges[findAll->found] = ((maskedRegexOp == RKLSplitOp) ? NSMakeRange(lastLocation, cacheSlot->lastMatchRange.location - lastLocation) : cacheSlot->lastMatchRange);
988 |
989 | for(loopCapture = 1L; loopCapture <= captureCount; loopCapture++) {
990 | RKLCDelayedAssert((findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)) && (loopCapture < INT_MAX), exception, exitNow);
991 | if(RKL_EXPECTED(rkl_getRangeForCapture(cacheSlot, status, (int32_t)loopCapture, &findAll->ranges[++findAll->found]) > U_ZERO_ERROR, 0L)) { goto exitNow; }
992 | }
993 | break;
994 |
995 | default: if(*exception != NULL) { *exception = RKLCAssertDictionary(@"Unknown regexOp."); } goto exitNow; break;
996 | }
997 |
998 | lastLocation = NSMaxRange(cacheSlot->lastMatchRange);
999 | }
1000 |
1001 | if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto exitNow; }
1002 |
1003 | RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)), exception, exitNow);
1004 | if((maskedRegexOp == RKLSplitOp) && (lastLocation != NSMaxRange(findAll->findInRange))) { findAll->ranges[findAll->found++] = NSMakeRange(lastLocation, NSMaxRange(findAll->findInRange) - lastLocation); }
1005 |
1006 | RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)), exception, exitNow);
1007 | returnWithError = NO;
1008 |
1009 | exitNow:
1010 | return(returnWithError);
1011 | }
1012 |
1013 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
1014 | // IMPORTANT! Should only be called from rkl_findRanges().
1015 | // ----------
1016 |
1017 | static NSUInteger rkl_growFindRanges(RKLCacheSlot *cacheSlot, NSUInteger lastLocation, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) {
1018 | NSUInteger didGrowRanges = 0UL;
1019 | RKLCDelayedAssert((((cacheSlot != NULL) && (cacheSlot->captureCount >= 0L)) && ((findAll != NULL) && (findAll->capacity >= 0L) && (findAll->rangesScratchBuffer != NULL) && (findAll->found >= 0L) && (((findAll->capacity > 0L) || (findAll->size > 0UL) || (findAll->ranges != NULL)) ? ((findAll->capacity > 0L) && (findAll->size > 0UL) && (findAll->ranges != NULL) && (((size_t)findAll->capacity * sizeof(NSRange)) == findAll->size)) : 1))), exception, exitNow);
1020 |
1021 | // Attempt to guesstimate the required capacity based on: the total length needed to search / (length we've searched so far / ranges found so far).
1022 | NSInteger newCapacity = (findAll->capacity + (findAll->capacity / 2L)), estimate = (NSInteger)((float)cacheSlot->setToLength / (((float)lastLocation + 1.0f) / ((float)findAll->found + 1.0f)));
1023 | newCapacity = (((newCapacity + ((estimate > newCapacity) ? estimate : newCapacity)) / 2L) + ((cacheSlot->captureCount + 2L) * 4L) + 4L);
1024 |
1025 | NSUInteger needToCopy = ((findAll->ranges != NULL) && (*findAll->rangesScratchBuffer != findAll->ranges)) ? 1UL : 0UL; // If findAll->ranges is set to a stack allocation then we need to manually copy the data from the stack to the new heap allocation.
1026 | size_t newSize = ((size_t)newCapacity * sizeof(NSRange));
1027 | NSRange *newRanges = NULL;
1028 |
1029 | if(RKL_EXPECTED((newRanges = (NSRange *)rkl_realloc((RKL_STRONG_REF void **)findAll->rangesScratchBuffer, newSize, 0UL)) == NULL, 0L)) { findAll->capacity = 0L; findAll->size = 0UL; findAll->ranges = NULL; *findAll->rangesScratchBuffer = rkl_free((RKL_STRONG_REF void **)findAll->rangesScratchBuffer); goto exitNow; } else { didGrowRanges = 1UL; }
1030 | if(needToCopy == 1UL) { memcpy(newRanges, findAll->ranges, findAll->size); } // If necessary, copy the existing data to the new heap allocation.
1031 |
1032 | findAll->capacity = newCapacity;
1033 | findAll->size = newSize;
1034 | findAll->ranges = newRanges;
1035 |
1036 | exitNow:
1037 | return(didGrowRanges);
1038 | }
1039 |
1040 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
1041 | // IMPORTANT! Should only be called from rkl_doFindOp().
1042 | // ----------
1043 |
1044 | static NSArray *rkl_makeArray(RKLCacheSlot *cacheSlot, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) {
1045 | NSUInteger createdStringsCount = 0UL, createdArraysCount = 0UL, transferedStringsCount = 0UL;
1046 | id *matchedStrings = NULL, *subcaptureArrays = NULL, emptyString = @"";
1047 | NSArray *resultArray = NULL;
1048 |
1049 | RKLCDelayedAssert((cacheSlot != NULL) && ((findAll != NULL) && (findAll->found >= 0L) && (findAll->stringsScratchBuffer != NULL) && (findAll->arraysScratchBuffer != NULL)), exception, exitNow);
1050 |
1051 | size_t matchedStringsSize = ((size_t)findAll->found * sizeof(id));
1052 | CFStringRef setToString = cacheSlot->setToString;
1053 |
1054 | if((findAll->stackUsed + matchedStringsSize) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((matchedStrings = (id *)alloca(matchedStringsSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += matchedStringsSize; }
1055 | else { if(RKL_EXPECTED((matchedStrings = (id *)rkl_realloc(findAll->stringsScratchBuffer, matchedStringsSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } }
1056 |
1057 | { // This sub-block (and its local variables) is here for the benefit of the optimizer.
1058 | NSUInteger found = (NSUInteger)findAll->found;
1059 | const NSRange *rangePtr = findAll->ranges;
1060 | id *matchedStringsPtr = matchedStrings;
1061 |
1062 | for(createdStringsCount = 0UL; createdStringsCount < found; createdStringsCount++) {
1063 | NSRange range = *rangePtr++;
1064 | if(RKL_EXPECTED(((*matchedStringsPtr++ = RKL_EXPECTED(range.length == 0UL, 0L) ? emptyString : rkl_CreateStringWithSubstring((id)setToString, range)) == NULL), 0L)) { goto exitNow; }
1065 | }
1066 | }
1067 |
1068 | NSUInteger arrayCount = createdStringsCount;
1069 | id *arrayObjects = matchedStrings;
1070 |
1071 | if((regexOp & RKLSubcapturesArray) != 0UL) {
1072 | RKLCDelayedAssert(((createdStringsCount % ((NSUInteger)cacheSlot->captureCount + 1UL)) == 0UL) && (createdArraysCount == 0UL), exception, exitNow);
1073 |
1074 | NSUInteger captureCount = ((NSUInteger)cacheSlot->captureCount + 1UL);
1075 | NSUInteger subcaptureArraysCount = (createdStringsCount / captureCount);
1076 | size_t subcaptureArraysSize = ((size_t)subcaptureArraysCount * sizeof(id));
1077 |
1078 | if((findAll->stackUsed + subcaptureArraysSize) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((subcaptureArrays = (id *)alloca(subcaptureArraysSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += subcaptureArraysSize; }
1079 | else { if(RKL_EXPECTED((subcaptureArrays = (id *)rkl_realloc(findAll->arraysScratchBuffer, subcaptureArraysSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } }
1080 |
1081 | { // This sub-block (and its local variables) is here for the benefit of the optimizer.
1082 | id *subcaptureArraysPtr = subcaptureArrays;
1083 | id *matchedStringsPtr = matchedStrings;
1084 |
1085 | for(createdArraysCount = 0UL; createdArraysCount < subcaptureArraysCount; createdArraysCount++) {
1086 | if(RKL_EXPECTED((*subcaptureArraysPtr++ = rkl_CreateArrayWithObjects((void **)matchedStringsPtr, captureCount)) == NULL, 0L)) { goto exitNow; }
1087 | matchedStringsPtr += captureCount;
1088 | transferedStringsCount += captureCount;
1089 | }
1090 | }
1091 |
1092 | RKLCDelayedAssert((transferedStringsCount == createdStringsCount), exception, exitNow);
1093 | arrayCount = createdArraysCount;
1094 | arrayObjects = subcaptureArrays;
1095 | }
1096 |
1097 | RKLCDelayedAssert((arrayObjects != NULL), exception, exitNow);
1098 | resultArray = rkl_CreateAutoreleasedArray((void **)arrayObjects, (NSUInteger)arrayCount);
1099 |
1100 | exitNow:
1101 | if(RKL_EXPECTED(resultArray == NULL, 0L) && (rkl_collectingEnabled() == NO)) { // If we did not create an array then we need to make sure that we release any objects we created.
1102 | NSUInteger x;
1103 | if(matchedStrings != NULL) { for(x = transferedStringsCount; x < createdStringsCount; x++) { if((matchedStrings[x] != NULL) && (matchedStrings[x] != emptyString)) { matchedStrings[x] = rkl_ReleaseObject(matchedStrings[x]); } } }
1104 | if(subcaptureArrays != NULL) { for(x = 0UL; x < createdArraysCount; x++) { if(subcaptureArrays[x] != NULL) { subcaptureArrays[x] = rkl_ReleaseObject(subcaptureArrays[x]); } } }
1105 | }
1106 |
1107 | return(resultArray);
1108 | }
1109 |
1110 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
1111 | // IMPORTANT! Should only be called from rkl_performRegexOp().
1112 | // ----------
1113 |
1114 | static NSString *rkl_replaceString(RKLCacheSlot *cacheSlot, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSUInteger *replacedCountPtr, NSUInteger replaceMutable, id *exception, int32_t *status) {
1115 | uint64_t searchU16Length64 = (uint64_t)searchU16Length, replacementU16Length64 = (uint64_t)replacementU16Length;
1116 | int32_t resultU16Length = 0, tempUniCharBufferU16Capacity = 0;
1117 | UniChar *tempUniCharBuffer = NULL;
1118 | const UniChar *replacementUniChar = NULL;
1119 | id resultObject = NULL;
1120 | NSUInteger replacedCount = 0UL;
1121 |
1122 | if((RKL_EXPECTED(replacementU16Length64 >= (uint64_t)INT_MAX, 0L) || RKL_EXPECTED(((searchU16Length64 / 2ULL) + (replacementU16Length64 * 2ULL)) >= (uint64_t)INT_MAX, 0L))) { *exception = [NSException exceptionWithName:NSRangeException reason:@"Replacement string length exceeds INT_MAX" userInfo:NULL]; goto exitNow; }
1123 |
1124 | RKLCDelayedAssert((searchU16Length64 < (uint64_t)INT_MAX) && (replacementU16Length64 < (uint64_t)INT_MAX) && (((searchU16Length64 / 2ULL) + (replacementU16Length64 * 2ULL)) < (uint64_t)INT_MAX), exception, exitNow);
1125 |
1126 | // Zero order approximation of the buffer sizes for holding the replaced string or split strings and split strings pointer offsets. As UTF16 code units.
1127 | tempUniCharBufferU16Capacity = (int32_t)(16UL + (searchU16Length + (searchU16Length / 2UL)) + (replacementU16Length * 2UL));
1128 |
1129 | // Buffer sizes converted from native units to bytes.
1130 | size_t stackSize = 0UL, replacementSize = ((size_t)replacementU16Length * sizeof(UniChar)), tempUniCharBufferSize = ((size_t)tempUniCharBufferU16Capacity * sizeof(UniChar));
1131 |
1132 | // For the various buffers we require, we first try to allocate from the stack if we're not over the RKL_STACK_LIMIT. If we are, switch to using the heap for the buffer.
1133 | if((stackSize + tempUniCharBufferSize) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((tempUniCharBuffer = (UniChar *)alloca(tempUniCharBufferSize)) == NULL, 0L)) { goto exitNow; } stackSize += tempUniCharBufferSize; }
1134 | else { if(RKL_EXPECTED((tempUniCharBuffer = (UniChar *)rkl_realloc(&scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL, 0L)) { goto exitNow; } }
1135 |
1136 | // Try to get the pointer to the replacement strings UTF16 data. If we can't, allocate some buffer space, then covert to UTF16.
1137 | if((replacementUniChar = CFStringGetCharactersPtr((CFStringRef)replacementString)) == NULL) {
1138 | UniChar *uniCharBuffer = NULL;
1139 | if((stackSize + replacementSize) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((uniCharBuffer = (UniChar *)alloca(replacementSize)) == NULL, 0L)) { goto exitNow; } stackSize += replacementSize; }
1140 | else { if(RKL_EXPECTED((uniCharBuffer = (UniChar *)rkl_realloc(&scratchBuffer[1], replacementSize, 0UL)) == NULL, 0L)) { goto exitNow; } }
1141 | CFStringGetCharacters((CFStringRef)replacementString, CFMakeRange(0L, replacementU16Length), uniCharBuffer); // Convert to a UTF16 string.
1142 | replacementUniChar = uniCharBuffer;
1143 | }
1144 |
1145 | resultU16Length = rkl_replaceAll(cacheSlot, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, exception, status);
1146 |
1147 | if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { // Our buffer guess(es) were too small. Resize the buffers and try again.
1148 | tempUniCharBufferSize = ((size_t)(tempUniCharBufferU16Capacity = resultU16Length + 4) * sizeof(UniChar));
1149 | if((stackSize + tempUniCharBufferSize) < (size_t)(RKL_STACK_LIMIT)) { if(RKL_EXPECTED((tempUniCharBuffer = (UniChar *)alloca(tempUniCharBufferSize)) == NULL, 0L)) { goto exitNow; } stackSize += tempUniCharBufferSize; }
1150 | else { if(RKL_EXPECTED((tempUniCharBuffer = (UniChar *)rkl_realloc(&scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL, 0L)) { goto exitNow; } }
1151 |
1152 | *status = U_ZERO_ERROR; // Make sure the status var is cleared and try again.
1153 | resultU16Length = rkl_replaceAll(cacheSlot, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, exception, status);
1154 | }
1155 |
1156 | if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto exitNow; } // Something went wrong.
1157 |
1158 | if(resultU16Length == 0) { resultObject = @""; } // Optimize the case where the replaced text length == 0 with a @"" string.
1159 | else if(((NSUInteger)resultU16Length == searchU16Length) && (replacedCount == 0UL)) { // Optimize the case where the replacement == original by creating a copy. Very fast if self is immutable.
1160 | if(replaceMutable == 0UL) { resultObject = rkl_CFAutorelease(CFStringCreateCopy(NULL, (CFStringRef)searchString)); } // .. but only if this is not replacing a mutable self.
1161 | } else { resultObject = rkl_CFAutorelease(CFStringCreateWithCharacters(NULL, tempUniCharBuffer, (CFIndex)resultU16Length)); } // otherwise, create a new string.
1162 |
1163 | // If replaceMutable == 1UL, we don't do the replacement here. We wait until after we return and unlock the cache lock.
1164 | // This is because we may be trying to mutate an immutable string object.
1165 | if((replacedCount > 0UL) && (replaceMutable == 1UL)) { // We're working on a mutable string and there were successfull matches with replaced text, so there's work to do.
1166 | rkl_clearBuffer((cacheSlot->setToLength < (CFIndex)(RKL_FIXED_LENGTH)) ? &fixedBuffer : &dynamicBuffer, 0UL);
1167 | rkl_clearCacheSlotSetTo(cacheSlot); // Flush any cached information about this string since it will mutate.
1168 | }
1169 |
1170 | exitNow:
1171 | if(scratchBuffer[0] != NULL) { scratchBuffer[0] = rkl_free(&scratchBuffer[0]); }
1172 | if(scratchBuffer[1] != NULL) { scratchBuffer[1] = rkl_free(&scratchBuffer[1]); }
1173 | if(replacedCountPtr != NULL) { *replacedCountPtr = replacedCount; }
1174 | return(resultObject);
1175 | }
1176 |
1177 | // IMPORTANT! Should only be called from rkl_replaceString().
1178 | // ----------
1179 | // Modified version of the ICU libraries uregex_replaceAll() that keeps count of the number of replacements made.
1180 |
1181 | static int32_t rkl_replaceAll(RKLCacheSlot *cacheSlot, const UniChar *replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSUInteger *replacedCount, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) {
1182 | NSUInteger replaced = 0UL, bufferOverflowed = 0UL;
1183 | int32_t u16Length = 0;
1184 | RKLCDelayedAssert((cacheSlot != NULL) && (replacementUniChar != NULL) && (replacedUniChar != NULL) && (status != NULL) && (replacementU16Length >= 0) && (replacedU16Capacity >= 0), exception, exitNow);
1185 |
1186 | cacheSlot->lastFindRange = cacheSlot->lastMatchRange = NSNotFoundRange; // Clear the cached find information for this regex so a subsequent find works correctly.
1187 | RKL_ICU_FUNCTION_APPEND(uregex_reset)(cacheSlot->icu_regex, 0, status);
1188 |
1189 | // Work around for ICU uregex_reset() bug, see http://bugs.icu-project.org/trac/ticket/6545
1190 | // http://sourceforge.net/tracker/index.php?func=detail&aid=2105213&group_id=204582&atid=990188
1191 | if(RKL_EXPECTED(cacheSlot->setToRange.length == 0L, 0L) && (*status == U_INDEX_OUTOFBOUNDS_ERROR)) { *status = U_ZERO_ERROR; }
1192 |
1193 | // This loop originally came from ICU source/i18n/uregex.cpp, uregex_replaceAll.
1194 | // There is a bug in that code which causes the size of the buffer required for the replaced text to not be calculated correctly.
1195 | // This contains a work around using the variable bufferOverflowed.
1196 | // ICU bug: http://bugs.icu-project.org/trac/ticket/6656
1197 | // http://sourceforge.net/tracker/index.php?func=detail&aid=2408447&group_id=204582&atid=990188
1198 | while(RKL_ICU_FUNCTION_APPEND(uregex_findNext)(cacheSlot->icu_regex, status)) {
1199 | replaced++;
1200 | u16Length += RKL_ICU_FUNCTION_APPEND(uregex_appendReplacement)(cacheSlot->icu_regex, replacementUniChar, replacementU16Length, &replacedUniChar, &replacedU16Capacity, status);
1201 | if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { bufferOverflowed = 1UL; *status = U_ZERO_ERROR; }
1202 | }
1203 | if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { bufferOverflowed = 1UL; *status = U_ZERO_ERROR; }
1204 | u16Length += RKL_ICU_FUNCTION_APPEND(uregex_appendTail)(cacheSlot->icu_regex, &replacedUniChar, &replacedU16Capacity, status);
1205 |
1206 | if(RKL_EXPECTED(*status == U_ZERO_ERROR, 1L) && RKL_EXPECTED(bufferOverflowed == 1UL, 0L)) { *status = U_BUFFER_OVERFLOW_ERROR; }
1207 | if(replacedCount != NULL) { *replacedCount = replaced; }
1208 |
1209 | #ifndef NS_BLOCK_ASSERTIONS
1210 | exitNow:
1211 | #endif
1212 | return(u16Length);
1213 | }
1214 |
1215 | static NSUInteger rkl_isRegexValid(id self, SEL _cmd, NSString *regex, RKLRegexOptions options, NSInteger *captureCountPtr, NSError **error) {
1216 | volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) cacheSpinLockStatus = 0UL;
1217 |
1218 | RKLCacheSlot *cacheSlot = NULL;
1219 | NSUInteger gotCacheSlot = 0UL;
1220 | NSInteger captureCount = -1L;
1221 | id exception = NULL;
1222 |
1223 | if((error != NULL) && (*error != NULL)) { *error = NULL; }
1224 | if(regex == NULL) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The regular expression argument is NULL."); }
1225 |
1226 | OSSpinLockLock(&cacheSpinLock);
1227 | cacheSpinLockStatus |= RKLLockedCacheSpinLock;
1228 | rkl_dtrace_incrementEventID();
1229 | if((cacheSlot = rkl_getCachedRegex(regex, options, error, &exception)) != NULL) { gotCacheSlot = 1UL; captureCount = cacheSlot->captureCount; }
1230 | cacheSlot = NULL;
1231 | OSSpinLockUnlock(&cacheSpinLock);
1232 | cacheSpinLockStatus |= RKLUnlockedCacheSpinLock;
1233 |
1234 | if(captureCountPtr != NULL) { *captureCountPtr = captureCount; }
1235 | if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); }
1236 | return(gotCacheSlot);
1237 | }
1238 |
1239 | static void rkl_clearStringCache(void) {
1240 | NSCParameterAssert(cacheSpinLock != 0);
1241 | lastCacheSlot = NULL;
1242 | NSUInteger x = 0UL;
1243 | for(x = 0UL; x < (NSUInteger)(RKL_SCRATCH_BUFFERS); x++) { if(scratchBuffer[x] != NULL) { scratchBuffer[x] = rkl_free(&scratchBuffer[x]); } }
1244 | for(x = 0UL; x < (NSUInteger)(RKL_CACHE_SIZE); x++) { rkl_clearCacheSlotRegex(&rkl_cacheSlots[x]); }
1245 | rkl_clearBuffer(&fixedBuffer, 0UL);
1246 | rkl_clearBuffer(&dynamicBuffer, 1UL);
1247 | }
1248 |
1249 | static void rkl_clearBuffer(RKLBuffer *buffer, NSUInteger freeDynamicBuffer) {
1250 | if(buffer == NULL) { return; }
1251 | if((freeDynamicBuffer == 1UL) && (buffer->uniChar != NULL) && (buffer == &dynamicBuffer)) { RKL_STRONG_REF void *p = (RKL_STRONG_REF void *)dynamicBuffer.uniChar; dynamicBuffer.uniChar = (RKL_STRONG_REF UniChar *)rkl_free(&p); }
1252 | if(buffer->string != NULL) { CFRelease((CFTypeRef)buffer->string); buffer->string = NULL; }
1253 | buffer->length = 0L;
1254 | buffer->hash = 0UL;
1255 | }
1256 |
1257 | static void rkl_clearCacheSlotRegex(RKLCacheSlot *cacheSlot) {
1258 | if(cacheSlot == NULL) { return; }
1259 | if(cacheSlot->setToString != NULL) { rkl_clearCacheSlotSetTo(cacheSlot); }
1260 | if(cacheSlot->icu_regex != NULL) { RKL_ICU_FUNCTION_APPEND(uregex_close)(cacheSlot->icu_regex); cacheSlot->icu_regex = NULL; cacheSlot->captureCount = -1L; }
1261 | if(cacheSlot->regexString != NULL) { CFRelease((CFTypeRef)cacheSlot->regexString); cacheSlot->regexString = NULL; cacheSlot->options = 0U; }
1262 | }
1263 |
1264 | static void rkl_clearCacheSlotSetTo(RKLCacheSlot *cacheSlot) {
1265 | if(cacheSlot == NULL) { return; }
1266 | if(cacheSlot->icu_regex != NULL) { int32_t status = 0; RKL_ICU_FUNCTION_APPEND(uregex_setText)(cacheSlot->icu_regex, &emptyUniCharString[0], 0, &status); }
1267 | if(cacheSlot->setToString != NULL) { CFRelease((CFTypeRef)cacheSlot->setToString); cacheSlot->setToString = NULL; }
1268 | cacheSlot->lastFindRange = cacheSlot->lastMatchRange = cacheSlot->setToRange = NSNotFoundRange;
1269 | cacheSlot->setToIsImmutable = cacheSlot->setToNeedsConversion = 0U;
1270 | cacheSlot->setToUniChar = NULL;
1271 | cacheSlot->setToHash = 0UL;
1272 | cacheSlot->setToLength = 0L;
1273 | }
1274 |
1275 | // Helps to keep things tidy.
1276 | #define addKeyAndObject(objs, keys, i, k, o) ({id _o=(o), _k=(k); if((_o != NULL) && (_k != NULL)) { objs[i] = _o; keys[i] = _k; i++; } })
1277 |
1278 | static NSDictionary *rkl_userInfoDictionary(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, ...) {
1279 | va_list varArgsList;
1280 | va_start(varArgsList, status);
1281 | if(regexString == NULL) { va_end(varArgsList); return(NULL); }
1282 |
1283 | id objects[64], keys[64];
1284 | NSUInteger count = 0UL;
1285 |
1286 | NSString *errorNameString = [NSString stringWithUTF8String:RKL_ICU_FUNCTION_APPEND(u_errorName)(status)];
1287 |
1288 | addKeyAndObject(objects, keys, count, RKLICURegexRegexErrorKey, regexString);
1289 | addKeyAndObject(objects, keys, count, RKLICURegexRegexOptionsErrorKey, [NSNumber numberWithUnsignedInt:options]);
1290 | addKeyAndObject(objects, keys, count, RKLICURegexErrorCodeErrorKey, [NSNumber numberWithInt:status]);
1291 | addKeyAndObject(objects, keys, count, RKLICURegexErrorNameErrorKey, errorNameString);
1292 |
1293 | if((parseError != NULL) && (parseError->line != -1)) {
1294 | NSString *preContextString = [NSString stringWithCharacters:&parseError->preContext[0] length:(NSUInteger)RKL_ICU_FUNCTION_APPEND(u_strlen)(&parseError->preContext[0])];
1295 | NSString *postContextString = [NSString stringWithCharacters:&parseError->postContext[0] length:(NSUInteger)RKL_ICU_FUNCTION_APPEND(u_strlen)(&parseError->postContext[0])];
1296 |
1297 | addKeyAndObject(objects, keys, count, RKLICURegexLineErrorKey, [NSNumber numberWithInt:parseError->line]);
1298 | addKeyAndObject(objects, keys, count, RKLICURegexOffsetErrorKey, [NSNumber numberWithInt:parseError->offset]);
1299 | addKeyAndObject(objects, keys, count, RKLICURegexPreContextErrorKey, preContextString);
1300 | addKeyAndObject(objects, keys, count, RKLICURegexPostContextErrorKey, postContextString);
1301 | addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred at line %d, column %d: %@<>%@", errorNameString, parseError->line, parseError->offset, preContextString, postContextString]));
1302 | } else {
1303 | addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred.", errorNameString]));
1304 | }
1305 |
1306 | while(count < 62UL) { id obj = va_arg(varArgsList, id), key = va_arg(varArgsList, id); if((obj != NULL) && (key != NULL)) { addKeyAndObject(objects, keys, count, key, obj); } else { break; } }
1307 | va_end(varArgsList);
1308 |
1309 | return([NSDictionary dictionaryWithObjects:&objects[0] forKeys:&keys[0] count:count]);
1310 | }
1311 |
1312 | static NSError *rkl_NSErrorForRegex(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) {
1313 | return([NSError errorWithDomain:RKLICURegexErrorDomain code:(NSInteger)status userInfo:rkl_userInfoDictionary(regexString, options, parseError, status, @"There was an error compiling the regular expression.", @"NSLocalizedDescription", NULL)]);
1314 | }
1315 |
1316 | static NSException *rkl_NSExceptionForRegex(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) {
1317 | return([NSException exceptionWithName:RKLICURegexException reason:[NSString stringWithFormat:@"ICU regular expression error #%d, %s", status, RKL_ICU_FUNCTION_APPEND(u_errorName)(status)] userInfo:rkl_userInfoDictionary(regexString, options, parseError, status, NULL)]);
1318 | }
1319 |
1320 | static NSDictionary *rkl_makeAssertDictionary(const char *function, const char *file, int line, NSString *format, ...) {
1321 | va_list varArgsList;
1322 | va_start(varArgsList, format);
1323 | NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
1324 | va_end(varArgsList);
1325 | NSString *functionString = [NSString stringWithUTF8String:function], *fileString = [NSString stringWithUTF8String:file];
1326 | return([NSDictionary dictionaryWithObjectsAndKeys:formatString, @"description", functionString, @"function", fileString, @"file", [NSNumber numberWithInt:line], @"line", NSInternalInconsistencyException, @"exceptionName", NULL]);
1327 | }
1328 |
1329 | static NSString *rkl_stringFromClassAndMethod(id object, SEL selector, NSString *format, ...) {
1330 | va_list varArgsList;
1331 | va_start(varArgsList, format);
1332 | NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
1333 | va_end(varArgsList);
1334 | Class objectsClass = [object class];
1335 | return([NSString stringWithFormat:@"*** %c[%@ %@]: %@", (object == objectsClass) ? '+' : '-', NSStringFromClass(objectsClass), NSStringFromSelector(selector), formatString]);
1336 | }
1337 |
1338 | #pragma mark -
1339 | #pragma mark Objective-C Public Interface
1340 | #pragma mark -
1341 |
1342 | @implementation NSString (RegexKitLiteAdditions)
1343 |
1344 | #pragma mark +clearStringCache
1345 |
1346 | + (void)RKL_METHOD_PREPEND(clearStringCache)
1347 | {
1348 | volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) cacheSpinLockStatus = 0UL;
1349 | OSSpinLockLock(&cacheSpinLock);
1350 | cacheSpinLockStatus |= RKLLockedCacheSpinLock;
1351 | rkl_clearStringCache();
1352 | OSSpinLockUnlock(&cacheSpinLock);
1353 | cacheSpinLockStatus |= RKLUnlockedCacheSpinLock;
1354 | }
1355 |
1356 | #pragma mark +captureCountForRegex:
1357 |
1358 | + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex
1359 | {
1360 | NSInteger captureCount = -1L;
1361 | rkl_isRegexValid(self, _cmd, regex, RKLNoOptions, &captureCount, NULL);
1362 | return(captureCount);
1363 | }
1364 |
1365 | + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error
1366 | {
1367 | NSInteger captureCount = -1L;
1368 | rkl_isRegexValid(self, _cmd, regex, options, &captureCount, error);
1369 | return(captureCount);
1370 | }
1371 |
1372 | #pragma mark -captureCount:
1373 |
1374 | - (NSInteger)RKL_METHOD_PREPEND(captureCount)
1375 | {
1376 | NSInteger captureCount = -1L;
1377 | rkl_isRegexValid(self, _cmd, self, RKLNoOptions, &captureCount, NULL);
1378 | return(captureCount);
1379 | }
1380 |
1381 | - (NSInteger)RKL_METHOD_PREPEND(captureCountWithOptions):(RKLRegexOptions)options error:(NSError **)error
1382 | {
1383 | NSInteger captureCount = -1L;
1384 | rkl_isRegexValid(self, _cmd, self, options, &captureCount, error);
1385 | return(captureCount);
1386 | }
1387 |
1388 | #pragma mark -componentsSeparatedByRegex:
1389 |
1390 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex
1391 | {
1392 | NSRange range = NSMaxiumRange;
1393 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL));
1394 | }
1395 |
1396 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range
1397 | {
1398 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL));
1399 | }
1400 |
1401 | - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error
1402 | {
1403 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, options, 0L, self, &range, NULL, error, NULL));
1404 | }
1405 |
1406 | #pragma mark -isMatchedByRegex:
1407 |
1408 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex
1409 | {
1410 | NSRange result = NSNotFoundRange, range = NSMaxiumRange;
1411 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result);
1412 | return((result.location == NSNotFound) ? NO : YES);
1413 | }
1414 |
1415 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range
1416 | {
1417 | NSRange result = NSNotFoundRange;
1418 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result);
1419 | return((result.location == NSNotFound) ? NO : YES);
1420 | }
1421 |
1422 | - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error
1423 | {
1424 | NSRange result = NSNotFoundRange;
1425 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, 0L, self, &range, NULL, error, &result);
1426 | return((result.location == NSNotFound) ? NO : YES);
1427 | }
1428 |
1429 | #pragma mark -isRegexValid
1430 |
1431 | - (BOOL)RKL_METHOD_PREPEND(isRegexValid)
1432 | {
1433 | return(rkl_isRegexValid(self, _cmd, self, RKLNoOptions, NULL, NULL) == 1UL ? YES : NO);
1434 | }
1435 |
1436 | - (BOOL)RKL_METHOD_PREPEND(isRegexValidWithOptions):(RKLRegexOptions)options error:(NSError **)error
1437 | {
1438 | return(rkl_isRegexValid(self, _cmd, self, options, NULL, error) == 1UL ? YES : NO);
1439 | }
1440 |
1441 | #pragma mark -flushCachedRegexData
1442 |
1443 | - (void)RKL_METHOD_PREPEND(flushCachedRegexData)
1444 | {
1445 | volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) cacheSpinLockStatus = 0UL;
1446 |
1447 | CFIndex selfLength = CFStringGetLength((CFStringRef)self);
1448 | CFHashCode selfHash = CFHash((CFTypeRef)self);
1449 |
1450 | OSSpinLockLock(&cacheSpinLock);
1451 | cacheSpinLockStatus |= RKLLockedCacheSpinLock;
1452 | rkl_dtrace_incrementEventID();
1453 |
1454 | NSUInteger slot;
1455 | for(slot = 0UL; slot < (NSUInteger)(RKL_CACHE_SIZE); slot++) {
1456 | RKLCacheSlot *cacheSlot = &rkl_cacheSlots[slot];
1457 | if((cacheSlot->setToString != NULL) && ( (cacheSlot->setToString == (CFStringRef)self) || ((cacheSlot->setToLength == selfLength) && (cacheSlot->setToHash == selfHash)) ) ) { rkl_clearCacheSlotSetTo(cacheSlot); }
1458 | }
1459 |
1460 | RKLBuffer *buffer = (selfLength < (CFIndex)(RKL_FIXED_LENGTH)) ? &fixedBuffer : &dynamicBuffer;
1461 | if((buffer->string != NULL) && ((buffer->string == (CFStringRef)self) || ((buffer->length == selfLength) && (buffer->hash == selfHash)))) { rkl_clearBuffer(buffer, 0UL); }
1462 |
1463 | OSSpinLockUnlock(&cacheSpinLock);
1464 | cacheSpinLockStatus |= RKLUnlockedCacheSpinLock;
1465 | }
1466 |
1467 | #pragma mark -rangeOfRegex:
1468 |
1469 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex
1470 | {
1471 | NSRange result = NSNotFoundRange, range = NSMaxiumRange;
1472 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result);
1473 | return(result);
1474 | }
1475 |
1476 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture
1477 | {
1478 | NSRange result = NSNotFoundRange, range = NSMaxiumRange;
1479 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, capture, self, &range, NULL, NULL, &result);
1480 | return(result);
1481 | }
1482 |
1483 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range
1484 | {
1485 | NSRange result = NSNotFoundRange;
1486 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result);
1487 | return(result);
1488 | }
1489 |
1490 | - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error
1491 | {
1492 | NSRange result = NSNotFoundRange;
1493 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, capture, self, &range, NULL, error, &result);
1494 | return(result);
1495 | }
1496 |
1497 | #pragma mark -stringByMatching:
1498 |
1499 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex
1500 | {
1501 | NSRange matchedRange = NSNotFoundRange, range = NSMaxiumRange;
1502 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &matchedRange);
1503 | return((matchedRange.location == NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length))));
1504 | }
1505 |
1506 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture
1507 | {
1508 | NSRange matchedRange = NSNotFoundRange, range = NSMaxiumRange;
1509 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, capture, self, &range, NULL, NULL, &matchedRange);
1510 | return((matchedRange.location == NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length))));
1511 | }
1512 |
1513 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range
1514 | {
1515 | NSRange matchedRange = NSNotFoundRange;
1516 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &matchedRange);
1517 | return((matchedRange.location == NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length))));
1518 | }
1519 |
1520 | - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error
1521 | {
1522 | NSRange matchedRange = NSNotFoundRange;
1523 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, capture, self, &range, NULL, error, &matchedRange);
1524 | return((matchedRange.location == NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length))));
1525 | }
1526 |
1527 | #pragma mark -stringByReplacingOccurrencesOfRegex:
1528 |
1529 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement
1530 | {
1531 | NSRange searchRange = NSMaxiumRange;
1532 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, NULL));
1533 | }
1534 |
1535 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange
1536 | {
1537 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, NULL));
1538 | }
1539 |
1540 | - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error
1541 | {
1542 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, options, 0L, self, &searchRange, replacement, error, NULL));
1543 | }
1544 |
1545 | #pragma mark -componentsMatchedByRegex:
1546 |
1547 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex
1548 | {
1549 | NSRange searchRange = NSMaxiumRange;
1550 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL));
1551 | }
1552 |
1553 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex capture:(NSInteger)capture
1554 | {
1555 | NSRange searchRange = NSMaxiumRange;
1556 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, capture, self, &searchRange, NULL, NULL, NULL));
1557 | }
1558 |
1559 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex range:(NSRange)range
1560 | {
1561 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL));
1562 | }
1563 |
1564 | - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range capture:(NSInteger)capture error:(NSError **)error
1565 | {
1566 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, options, capture, self, &range, NULL, error, NULL));
1567 | }
1568 |
1569 | #pragma mark -captureComponentsMatchedByRegex:
1570 |
1571 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex
1572 | {
1573 | NSRange searchRange = NSMaxiumRange;
1574 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL));
1575 | }
1576 |
1577 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range
1578 | {
1579 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL));
1580 | }
1581 |
1582 | - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error
1583 | {
1584 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, options, 0L, self, &range, NULL, error, NULL));
1585 | }
1586 |
1587 | #pragma mark -arrayOfCaptureComponentsMatchedByRegex:
1588 |
1589 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex
1590 | {
1591 | NSRange searchRange = NSMaxiumRange;
1592 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL));
1593 | }
1594 |
1595 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range
1596 | {
1597 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL));
1598 | }
1599 |
1600 | - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error
1601 | {
1602 | return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, options, 0L, self, &range, NULL, error, NULL));
1603 | }
1604 |
1605 | @end
1606 |
1607 |
1608 | @implementation NSMutableString (RegexKitLiteAdditions)
1609 |
1610 | #pragma mark -replaceOccurrencesOfRegex:
1611 |
1612 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement
1613 | {
1614 | NSRange searchRange = NSMaxiumRange;
1615 | NSUInteger replacedCount = 0UL;
1616 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount));
1617 | return(replacedCount);
1618 | }
1619 |
1620 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange
1621 | {
1622 | NSUInteger replacedCount = 0UL;
1623 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount));
1624 | return(replacedCount);
1625 | }
1626 |
1627 | - (NSUInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error
1628 | {
1629 | NSUInteger replacedCount = 0UL;
1630 | rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, options, 0L, self, &searchRange, replacement, error, (void **)((void *)&replacedCount));
1631 | return(replacedCount);
1632 | }
1633 |
1634 | @end
1635 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | make -f Makefile.x86_64
3 | make -f Makefile.arm
4 | lipo -create obj/symbolicate obj/macosx/symbolicate -output symbolicate
5 | mv symbolicate obj/symbolicate
6 |
7 | clean:
8 | make -f Makefile.x86_64 clean
9 | make -f Makefile.arm clean
10 |
11 | distclean:
12 | make -f Makefile.x86_64 distclean
13 | make -f Makefile.arm distclean
14 |
15 | package: build
16 | make -f Makefile.arm package
17 |
18 | install:
19 | make -f Makefile.arm install
20 |
--------------------------------------------------------------------------------
/Makefile.arm:
--------------------------------------------------------------------------------
1 | export ARCHS = armv6 arm64
2 | export SDKVERSION = 8.4
3 | export TARGET = iphone:clang
4 | export TARGET_IPHONEOS_DEPLOYMENT_VERSION = 3.0
5 |
6 | include Makefile.common
7 |
--------------------------------------------------------------------------------
/Makefile.common:
--------------------------------------------------------------------------------
1 | TOOL_NAME = symbolicate
2 | PKG_ID = jp.ashikase.symbolicate
3 |
4 | symbolicate_INSTALL_PATH = /usr/bin
5 | symbolicate_OBJC_FILES = \
6 | src/common.c \
7 | src/main.m \
8 | src/symbolMaps.m
9 | symbolicate_LIBRARIES = bz2 crashreport
10 | symbolicate_CODESIGN_FLAGS="-SEntitlements.plist"
11 |
12 | ADDITIONAL_CFLAGS = -DPKG_ID=\"$(PKG_ID)\" -ILibraries
13 |
14 | include theos/makefiles/common.mk
15 | include $(THEOS)/makefiles/tool.mk
16 |
17 | after-stage::
18 | # Optimize png files
19 | - find $(THEOS_STAGING_DIR) -iname '*.png' -exec pincrush -i {} \;
20 | # Convert plist files to binary
21 | - find $(THEOS_STAGING_DIR)/ -type f -iname '*.plist' -exec plutil -convert binary1 {} \;
22 | # Remove repository-related files
23 | - find $(THEOS_STAGING_DIR) -name '.gitkeep' -delete
24 |
25 | distclean: clean
26 | - rm -f $(THEOS_PROJECT_DIR)/$(APP_ID)*.deb
27 | - rm -f $(THEOS_PROJECT_DIR)/.theos/packages/*
28 |
--------------------------------------------------------------------------------
/Makefile.x86_64:
--------------------------------------------------------------------------------
1 | export ARCHS = x86_64
2 | export TARGET = macosx
3 | export TARGET_MACOSX_DEPLOYMENT_VERSION = 10.6
4 |
5 | include Makefile.common
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | About
2 | =====
3 |
4 | Crash log files generated by iOS's ReportCrash daemon do not contain symbol names, making them difficult to interpret.
5 | Symbolication is the act of looking up symbol names and adding them to these files.
6 |
7 | This project provides a tool for symbolicating crash logs on and off device.
8 |
9 | Usage
10 | =====
11 |
12 | To symbolicate a file and have the result printed to stdout:
13 |
14 | /usr/bin/symbolicate input_file
15 |
16 | To instead have the result written to a file:
17 |
18 | /usr/bin/symbolicate -o output_file input_file
19 |
20 | Configuration
21 | =============
22 |
23 | Besides symbolication, this tool also attempts to determine and assign blame to the binary image most likey responsible for the crash.
24 |
25 | Selected binary images can be filtered out so that they will not be blamed.
26 | These filters are defined in:
27 |
28 | /etc/symbolicate/blame_filters.plist
29 |
30 | Requirements
31 | =====
32 |
33 | This tool requires the libsymbolicate library:
34 | * [libsymbolicate](http://github.com/ashikase/libsymbolicate)
35 |
36 | Credit
37 | =====
38 |
39 | This project also makes use of the following:
40 | * [RegexKitLite framework](http://regexkit.sourceforge.net)
41 |
--------------------------------------------------------------------------------
/get_requirements.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Description: Script to fetch requirements for building this project.
4 | # Last-modified: 2013-02-20 14:31:57
5 | #
6 | # Note that this script is not perfect and does not handle all errors.
7 | # Any improvements are welcome.
8 |
9 |
10 | # Either Git or Subversion is needed to retrieve Theos.
11 | GIT=$(type -P git)
12 | SVN=$(type -P svn)
13 | if [ -z "$GIT" -a -z "$SVN" ]; then
14 | echo "ERROR: This script requires either 'git' or 'svn' to be installed."
15 | exit 1
16 | fi
17 |
18 | # Either wget or curl is needed to download package list and ldid.
19 | WGET=$(type -P wget)
20 | CURL=$(type -P curl)
21 | if [ -z "$WGET" -a -z "$CURL" ]; then
22 | echo "ERROR: This script requires either 'wget' or 'curl' to be installed."
23 | exit 1
24 | fi
25 |
26 | # Download Theos
27 | echo "Checking for Theos..."
28 | if [ -z "$THEOS" ]; then
29 | echo "Downloading Theos..."
30 | if [ ! -z "$GIT" ]; then
31 | git clone --quiet git://github.com/DHowett/theos.git theos
32 | else
33 | svn co http://svn.howett.net/svn/theos/trunk theos
34 | fi
35 | else
36 | ln -sn "$THEOS" theos
37 | fi
38 |
39 | # Download ldid
40 | echo "Checking for ldid..."
41 | if [ -z "$(type -P ldid)" ]; then
42 | echo "Downloading ldid..."
43 | if [ "$(uname)" == "Darwin" ]; then
44 | if [ ! -z "$WGET" ]; then
45 | wget -q http://dl.dropbox.com/u/3157793/ldid
46 | else
47 | curl -s http://dl.dropbox.com/u/3157793/ldid > ldid
48 | fi
49 | mv ldid theos/bin/ldid
50 | chmod +x theos/bin/ldid
51 | else
52 | echo "... No pre-built version of ldid is available for your system."
53 | echo "... You will need to provide your own copy of ldid."
54 | fi
55 | fi
56 |
57 | # Check if .deb creation tools are available (optional)
58 | echo "Checking for dpkg-deb..."
59 | if [ -z "$(type -P dpkg-deb)" ]; then
60 | echo "... dpkg-deb not found."
61 | echo "... If you wish to create a .deb package, you will need the 'dpkg-deb' tool."
62 | fi
63 |
64 | echo "Done."
65 |
--------------------------------------------------------------------------------
/layout/DEBIAN/control:
--------------------------------------------------------------------------------
1 | Package: jp.ashikase.symbolicate
2 | Name: symbolicate
3 | Version: 1.8.0
4 | Description: Tool to symbolicate crash logs.
5 | A command-line tool for adding symbol names to iOS crash logs.
6 | This tool is based upon CrashReporter by kennytm.
7 | Architecture: iphoneos-arm
8 | Pre-Depends: firmware (>=3.0)
9 | Depends: jp.ashikase.libcrashreport (>= 1.1.0)
10 | Depiction: http://moreinfo.thebigboss.org/moreinfo/depiction.php?file=symbolicateData
11 | Homepage: http://github.com/ashikase/symbolicate
12 | Maintainer: BigBoss
13 | Author: Lance Fetters (ashikase)
14 | Sponsor: thebigboss.org
15 | Section: Development
16 | Tag: role::developer
17 | dev: ashikase
18 |
--------------------------------------------------------------------------------
/layout/etc/symbolicate/blame_filters.plist:
--------------------------------------------------------------------------------
1 | {
2 | Whitelisted = {
3 | Binaries = (
4 | "/Library/MobileSubstrate/MobileSafety.dylib",
5 | "/Library/MobileSubstrate/MobileSubstrate.dylib",
6 | "/Library/MobileSubstrate/DynamicLibraries/CydgetLoader.dylib",
7 | "/usr/lib/dyld",
8 | "/usr/lib/libAccessibility.dylib",
9 | "/usr/lib/libafc.dylib",
10 | "/usr/lib/libbsm.0.dylib",
11 | "/usr/lib/libbz2.1.0.4.dylib",
12 | "/usr/lib/libcharset.1.0.0.dylib",
13 | "/usr/lib/libcharset.1.dylib",
14 | "/usr/lib/libedit.2.dylib",
15 | "/usr/lib/libexslt.0.dylib",
16 | "/usr/lib/libform.5.4.dylib",
17 | "/usr/lib/libgcc_s.1.dylib",
18 | "/usr/lib/libgermantok.dylib",
19 | "/usr/lib/libiconv.2.4.0.dylib",
20 | "/usr/lib/libiconv.2.dylib",
21 | "/usr/lib/libicucore.A.dylib",
22 | "/usr/lib/libIOAccessoryManager.dylib",
23 | "/usr/lib/libIOKit.A.dylib",
24 | "/usr/lib/libipsec.A.dylib",
25 | "/usr/lib/liblangid.dylib",
26 | "/usr/lib/liblockdown.dylib",
27 | "/usr/lib/libmecab_em.dylib",
28 | "/usr/lib/libmecabra.dylib",
29 | "/usr/lib/libmis.dylib",
30 | "/usr/lib/libncurses.5.4.dylib",
31 | "/usr/lib/libobjc.A.dylib",
32 | "/usr/lib/libQLCharts.dylib",
33 | "/usr/lib/libresolv.9.dylib",
34 | "/usr/lib/libsqlite3.dylib",
35 | "/usr/lib/libstdc++.6.0.9.dylib",
36 | "/usr/lib/libstdc++.6.dylib",
37 | "/usr/lib/libsubstrate.dylib",
38 | "/usr/lib/libSystem.B.dylib",
39 | "/usr/lib/libSystem.dylib",
40 | "/usr/lib/libtidy.A.dylib",
41 | "/usr/lib/libutil1.0.dylib",
42 | "/usr/lib/libxml2.2.dylib",
43 | "/usr/lib/libxslt.1.dylib",
44 | "/usr/lib/system/libkeymgr.dylib",
45 | "/usr/lib/libz.1.2.3.dylib",
46 | "/usr/lib/libz.1.dylib",
47 | "/usr/lib/system/libkxld.dylib",
48 | "/usr/lib/libcycript.dylib",
49 | "/usr/lib/libffi.dylib",
50 | );
51 | BinaryPathPrefixes = (
52 | "/System/Library/CoreServices",
53 | "/System/Library/Frameworks",
54 | "/System/Library/PrivateFrameworks",
55 | "/System/Library/TextInput",
56 | );
57 | Exceptions = (
58 | SIGQUIT,
59 | SIGEMT,
60 | SIGTRAP,
61 | );
62 | Functions = (
63 | "mach_msg_trap",
64 | "semaphore_wait_signal_trap",
65 | "semaphore_timedwait_signal_trap",
66 | "__semwait_signal",
67 | "select$DARWIN_EXTSN",
68 | accept,
69 | recvfrom,
70 | read,
71 | kevent,
72 | );
73 | };
74 | Blacklisted = {
75 | Functions = (
76 | "__kill",
77 | );
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/src/common.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Name: symbolicate
3 | * Type: iOS/OS X shared command line tool
4 | * Desc: Tool for parsing and symbolicating iOS crash log files.
5 | *
6 | * Author: Lance Fetters (aka. ashikase)
7 | * License: GPL v3 (See LICENSE file for details)
8 | */
9 |
10 | static unsigned char nibble(char c) {
11 | if (c >= '0' && c <= '9') {
12 | return c - '0';
13 | } else if (c >= 'a' && c <= 'f') {
14 | return c - 'a' + 10;
15 | } else if (c >= 'A' && c <= 'F') {
16 | return c - 'A' + 10;
17 | } else {
18 | return 0xFF;
19 | }
20 | }
21 |
22 | unsigned long long unsignedLongLongFromHexString(const char* str, int len) {
23 | unsigned long long res = 0;
24 | int i;
25 | for (i = 0; i < len; ++ i) {
26 | unsigned char n = nibble(str[i]);
27 | if (n != 0xFF) {
28 | res = res * 16 + n;
29 | }
30 | }
31 | return res;
32 | }
33 |
--------------------------------------------------------------------------------
/src/common.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Name: symbolicate
3 | * Type: iOS/OS X shared command line tool
4 | * Desc: Tool for parsing and symbolicating iOS crash log files.
5 | *
6 | * Author: Lance Fetters (aka. ashikase)
7 | * License: GPL v3 (See LICENSE file for details)
8 | */
9 |
10 | #ifndef CR_COMMON_H
11 | #define CR_COMMON_H
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif
16 |
17 | unsigned long long unsignedLongLongFromHexString(const char* str, int len);
18 |
19 | #ifdef __cplusplus
20 | }
21 | #endif
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/src/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Name: symbolicate
3 | * Type: iOS/OS X shared command line tool
4 | * Desc: Tool for parsing and symbolicating iOS crash log files.
5 | *
6 | * Author: Lance Fetters (aka. ashikase)
7 | * License: GPL v3 (See LICENSE file for details)
8 | */
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #import
15 | #import "symbolMaps.h"
16 |
17 | static void print_usage() {
18 | fprintf(stderr,
19 | "Usage: symbolicate [] \n"
20 | "\n"
21 | "Options:\n"
22 | " --blame-only Process blame without symbolicating.\n"
23 | " Note that function filters will not work in this case.\n"
24 | " -f Choose the method of blame filtering: \"none\", \"file\", \"package\".\n"
25 | " (The default is \"file\", which will use the included filter file.)\n"
26 | " -m Provide symbol map file for specified binary image path.\n"
27 | " If file ends with \".bz2\", bzip2 compression is assumed.\n"
28 | " -o Write output to file instead of to stdout.\n"
29 | " --print-blame Print just list of suspects, from most to least likely.\n"
30 | " --sysroot= Use 'path' as the root path when loading binaries and shared caches.\n"
31 | " (e.g. /System/Library/Caches/com.apple.dyld/dyld...)\n"
32 | "\n"
33 | );
34 | }
35 |
36 | int main(int argc, char *argv[]) {
37 | int ret = 1;
38 |
39 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
40 |
41 | if (argc == 1) {
42 | print_usage();
43 | } else {
44 | const char *outputFile = NULL;
45 | const char *sysroot = NULL;
46 | NSMutableDictionary *mapFiles = [NSMutableDictionary new];
47 | CRCrashReportFilterType filterType = CRCrashReportFilterTypeFile;
48 | BOOL shouldSymbolicate = YES;
49 | BOOL shouldPrintBlame = NO;
50 |
51 | int blameOnlyFlag = 0;
52 | int printBlameFlag = 0;
53 | struct option longopts[] = {
54 | { "blame-only", no_argument, &blameOnlyFlag, 1 },
55 | { "print-blame", no_argument, &printBlameFlag, 1 },
56 | { "sysroot", required_argument, NULL, '/' },
57 | { NULL, 0, NULL, 0 }
58 | };
59 |
60 | int c;
61 | while ((c = getopt_long(argc, argv, "f:m:n:o:", longopts, NULL)) != -1) {
62 | switch (c) {
63 | case 'f':
64 | if (strcmp(optarg, "package") == 0) {
65 | filterType = CRCrashReportFilterTypePackage;
66 | } else if (strcmp(optarg, "none") == 0) {
67 | filterType = CRCrashReportFilterTypeNone;
68 | }
69 | case 'm': {
70 | char *path = strtok(optarg, ",");
71 | char *file = strtok(NULL, ",");
72 | if (path != NULL && file != NULL) {
73 | [mapFiles setObject:[NSString stringWithCString:file encoding:NSUTF8StringEncoding]
74 | forKey:[NSString stringWithCString:path encoding:NSUTF8StringEncoding]];
75 | }
76 | break;
77 | }
78 | case 'n':
79 | break;
80 | case 'o':
81 | outputFile = optarg;
82 | break;
83 | case '/':
84 | sysroot = optarg;
85 | break;
86 | case 0:
87 | shouldSymbolicate = (blameOnlyFlag == 0);
88 | shouldPrintBlame = (printBlameFlag == 1);
89 | break;
90 | default:
91 | print_usage();
92 | goto exit;
93 | }
94 | }
95 |
96 | const char *inputFile = (optind < argc) ? argv[optind] : NULL;
97 | if (inputFile == NULL) {
98 | print_usage();
99 | } else {
100 | // Parse the log file.
101 | NSString *inputFileString = [[NSString alloc] initWithUTF8String:inputFile];
102 | CRCrashReport *report = [CRCrashReport crashReportWithFile:inputFileString filterType:filterType];
103 | [inputFileString release];
104 | if (report == nil) {
105 | goto exit;
106 | }
107 |
108 | if (shouldSymbolicate) {
109 | // Parse map files (optional).
110 | NSMutableDictionary *symbolMaps = [NSMutableDictionary new];
111 | for (NSString *imagePath in mapFiles) {
112 | NSString *mapFile = [mapFiles objectForKey:imagePath];
113 | NSDictionary *result = parseMapFile(mapFile);
114 | if (result != nil) {
115 | [symbolMaps setObject:result forKey:imagePath];
116 | } else {
117 | fprintf(stderr, "WARNING: Unable to read map file \"%s\".\n", [mapFile UTF8String]);
118 | }
119 | }
120 | [mapFiles release];
121 |
122 | // Set system root to use.
123 | NSString *systemRoot = nil;
124 | if (sysroot != NULL) {
125 | systemRoot = [[NSString alloc] initWithFormat:@"%s", sysroot];
126 | }
127 |
128 | // Symbolicate threads in the report.
129 | if (![report symbolicateUsingSystemRoot:systemRoot symbolMaps:symbolMaps]) {
130 | fprintf(stderr, "WARNING: Failed to symbolicate.\n");
131 | }
132 | [systemRoot release];
133 | [symbolMaps release];
134 | }
135 |
136 | // Load blame filters.
137 | NSDictionary *filters = [[NSDictionary alloc] initWithContentsOfFile:@"/etc/symbolicate/blame_filters.plist"];
138 |
139 | // Process blame.
140 | if (![report blameUsingFilters:filters]) {
141 | fprintf(stderr, "WARNING: Failed to process blame.\n");
142 | }
143 | [filters release];
144 |
145 | // Determine what to output.
146 | NSString *outputString = nil;
147 | if (shouldPrintBlame) {
148 | // Output the blame.
149 | NSMutableString *string = [NSMutableString string];
150 | NSArray *blame = [[report properties] objectForKey:kCrashReportBlame];
151 | for (NSString *suspect in blame) {
152 | [string appendString:suspect];
153 | [string appendString:@"\n"];
154 | }
155 | outputString = string;
156 | } else {
157 | // Output the log file.
158 | outputString = [report stringRepresentation];
159 | }
160 |
161 | // Write out the output.
162 | NSString *filepath = (outputFile != NULL) ? [[NSString alloc] initWithUTF8String:outputFile] : nil;
163 | if (filepath != nil) {
164 | // Write to file.
165 | NSError *error = nil;
166 | if ([outputString writeToFile:filepath atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
167 | fprintf(stderr, "INFO: Result written to %s.\n", [filepath UTF8String]);
168 | } else {
169 | fprintf(stderr, "ERROR: Unable to write to file: %s.\n", [[error localizedDescription] UTF8String]);
170 | }
171 | [filepath release];
172 | } else {
173 | // Print to screen.
174 | printf("%s", [outputString UTF8String]);
175 | }
176 |
177 | // Send notification of completion.
178 | // NOTE: This is for backwards-compatibility. Some packages that
179 | // call symbolicate expect to be notified with status updates.
180 | int token;
181 | notify_register_check(PKG_ID".progress", &token);
182 | notify_set_state(token, 100);
183 | notify_post(PKG_ID".progress");
184 |
185 | ret = 0;
186 | }
187 | }
188 |
189 | exit:
190 | // FIXME: Is it actually necessary to drain the pool on exit?
191 | // Draining the pool is actually quite slow.
192 | [pool drain];
193 | return ret;
194 | }
195 |
196 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */
197 |
--------------------------------------------------------------------------------
/src/symbolMaps.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Name: symbolicate
3 | * Type: iOS/OS X shared command line tool
4 | * Desc: Tool for parsing and symbolicating iOS crash log files.
5 | *
6 | * Author: Lance Fetters (aka. ashikase)
7 | * License: GPL v3 (See LICENSE file for details)
8 | */
9 |
10 | NSDictionary *parseMapFile(NSString *mapFile);
11 |
12 | /* vim: set ft=objcpp ff=unix sw=4 ts=4 tw=80 expandtab: */
13 |
--------------------------------------------------------------------------------
/src/symbolMaps.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Name: symbolicate
3 | * Type: iOS/OS X shared command line tool
4 | * Desc: Tool for parsing and symbolicating iOS crash log files.
5 | *
6 | * Author: Lance Fetters (aka. ashikase)
7 | * License: GPL v3 (See LICENSE file for details)
8 | */
9 |
10 | #import "symbolMaps.h"
11 |
12 | #import
13 | #include "common.h"
14 |
15 | // NOTE: It seems that older SDKs (iOS 3.0 and earlier?) do not include bzlib.h.
16 | //#include
17 | #define BZ_OK 0
18 | #define BZ_STREAM_END 4
19 |
20 | typedef struct {
21 | char *next_in;
22 | unsigned int avail_in;
23 | unsigned int total_in_lo32;
24 | unsigned int total_in_hi32;
25 | char *next_out;
26 | unsigned int avail_out;
27 | unsigned int total_out_lo32;
28 | unsigned int total_out_hi32;
29 | void *state;
30 | void *(*bzalloc)(void *,int,int);
31 | void (*bzfree)(void *,void *);
32 | void *opaque;
33 | } bz_stream;
34 |
35 | extern int BZ2_bzDecompressInit(bz_stream *strm, int verbosity, int small);
36 | extern int BZ2_bzDecompress(bz_stream* strm);
37 | extern int BZ2_bzDecompressEnd(bz_stream *strm);
38 |
39 | static NSData *bunzip2(NSData *inputData) {
40 | NSMutableData *outputData = [NSMutableData data];
41 |
42 | const int bufSize = 4096;
43 | NSMutableData *buf = [NSMutableData dataWithLength:bufSize];
44 | bz_stream stream = {0};
45 | stream.next_in = (char *)[inputData bytes];
46 | stream.avail_in = [inputData length];
47 |
48 | BZ2_bzDecompressInit(&stream, 0, 0);
49 | int ret;
50 | do {
51 | stream.next_out = [buf mutableBytes];
52 | stream.avail_out = bufSize;
53 | ret = BZ2_bzDecompress(&stream);
54 | if (ret != BZ_OK && ret != BZ_STREAM_END) {
55 | break;
56 | }
57 | [outputData appendBytes:[buf bytes] length:(bufSize - stream.avail_out)];
58 | } while (ret != BZ_STREAM_END);
59 | BZ2_bzDecompressEnd(&stream);
60 |
61 | return outputData;
62 | }
63 |
64 | NSDictionary *parseMapFile(NSString *mapFile) {
65 | NSMutableDictionary *result = [NSMutableDictionary dictionary];
66 |
67 | if (![[NSFileManager defaultManager] fileExistsAtPath:mapFile]) {
68 | return nil;
69 | }
70 |
71 | NSString *content = nil;
72 | if ([[mapFile pathExtension] isEqualToString:@"bz2"]) {
73 | NSData *data = [[NSData alloc] initWithContentsOfFile:mapFile];
74 | content = [[NSString alloc] initWithData:bunzip2(data) encoding:NSASCIIStringEncoding];
75 | [data release];
76 | } else {
77 | content = [[NSString alloc] initWithContentsOfFile:mapFile encoding:NSASCIIStringEncoding error:NULL];
78 | }
79 |
80 | if (content != nil) {
81 | BOOL foundSymbols = NO;
82 | for (NSString *line in [content componentsSeparatedByString:@"\n"]) {
83 | if (!foundSymbols) {
84 | foundSymbols = [line hasPrefix:@"# Symbols:"];
85 | } else {
86 | if ([line length] > 0) {
87 | NSArray *array = [line captureComponentsMatchedByRegex:@"^0x([0-9a-fA-F]+)\\s+0x[0-9a-fA-F]+\\s+\\[\\s*\\d+\\] (.*)$"];
88 | if ([array count] == 3) {
89 | NSString *matches[2];
90 | [array getObjects:matches range:NSMakeRange(1, 2)];
91 | if (!(
92 | [matches[1] isEqualToString:@"anon"] ||
93 | [matches[1] isEqualToString:@"CFString"] ||
94 | [matches[1] hasPrefix:@"literal string:"] ||
95 | [matches[1] rangeOfString:@"-"].location != NSNotFound
96 | )) {
97 | unsigned long long address = unsignedLongLongFromHexString([matches[0] UTF8String], [matches[0] length]);
98 | NSNumber *number = [[NSNumber alloc] initWithUnsignedLongLong:address];
99 | [result setObject:matches[1] forKey:number];
100 | [number release];
101 | }
102 | }
103 | }
104 | }
105 | }
106 | [content release];
107 | }
108 |
109 | return result;
110 | }
111 |
112 | /* vim: set ft=objcpp ff=unix sw=4 ts=4 tw=80 expandtab: */
113 |
--------------------------------------------------------------------------------