├── .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 | --------------------------------------------------------------------------------